diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ef476f4d272..ec656436ee7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -94,7 +94,6 @@ /apps/shorturl/ @grafana/sharing-squad /apps/secret/ @grafana/grafana-operator-experience-squad /apps/scope/ @grafana/grafana-operator-experience-squad -/apps/investigations/ @fcjack @matryer @svennergr /apps/advisor/ @grafana/plugins-platform-backend /apps/iam/ @grafana/access-squad /apps/sdk.mk @grafana/grafana-app-platform-squad diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 79987600b9a..6356a793b36 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,7 +19,6 @@ updates: - "/apps/dashboard" - "/apps/folder" - "/apps/iam" - - "/apps/investigations" - "/apps/playlist" - "/apps/plugins" - "/apps/preferences" diff --git a/.golangci.yml b/.golangci.yml index b52a986d435..069e88632ff 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -67,14 +67,6 @@ linters: deny: - pkg: github.com/grafana/grafana/pkg desc: apiserver is not allowed to import grafana core - apps-investigation: - list-mode: lax - files: - - ./apps/investigations/* - - ./apps/investigations/**/* - deny: - - pkg: github.com/grafana/grafana/pkg - desc: apps/investigations is not allowed to import grafana core apps-playlist: list-mode: lax files: diff --git a/Dockerfile b/Dockerfile index 2b16926a836..b44b7132202 100644 --- a/Dockerfile +++ b/Dockerfile @@ -103,7 +103,6 @@ COPY apps/collections apps/collections COPY apps/provisioning apps/provisioning COPY apps/secret apps/secret COPY apps/scope apps/scope -COPY apps/investigations apps/investigations COPY apps/logsdrilldown apps/logsdrilldown COPY apps/advisor apps/advisor COPY apps/dashboard apps/dashboard diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 0c91d66c945..e35a733d350 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -24,8 +24,6 @@ replace github.com/grafana/grafana/apps/alerting/historian => ../alerting/histor replace github.com/grafana/grafana/apps/correlations => ../correlations -replace github.com/grafana/grafana/apps/investigations => ../investigations - replace github.com/grafana/grafana/apps/logsdrilldown => ../logsdrilldown replace github.com/grafana/grafana/apps/playlist => ../playlist diff --git a/apps/investigations/Makefile b/apps/investigations/Makefile deleted file mode 100644 index bc8d6d30cb5..00000000000 --- a/apps/investigations/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -include ../sdk.mk - -.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/investigations/example.json b/apps/investigations/example.json deleted file mode 100644 index 0a390fb074f..00000000000 --- a/apps/investigations/example.json +++ /dev/null @@ -1,152 +0,0 @@ -[ - { - "id": "896312ce-65b0-4b50-ade1-e7f04fa22c66", - "title": "Thursday morning investigation", - "hasCustomName": false, - "isFavorite": false, - "collectables": [ - { - "origin": "Explore Logs", - "type": "timeseries", - "queries": [ - { - "refId": "LABEL_BREAKDOWN_VALUES", - "queryType": "range", - "editorMode": "code", - "supportingQueryType": "grafana-lokiexplore-app", - "legendFormat": "{{detected_level}}", - "expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)" - } - ], - "timeRange": { - "to": "2025-02-13T11:31:20.536Z", - "from": "2025-02-13T11:16:20.536Z", - "raw": { - "from": "now-15m", - "to": "now" - } - }, - "datasource": { - "uid": "fe9k7u07b1a0wc" - }, - "url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all", - "id": "LABEL_BREAKDOWN_VALUES_detected_level", - "title": "detected_level", - "logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg", - "createdAt": "2025-02-13T11:31:23.637Z" - } - ], - "createdAt": "2025-02-13T11:31:23.636Z", - "updatedAt": "2025-02-13T11:31:23.637Z", - "viewMode": { - "mode": "compact", - "showComments": true, - "showTooltips": false - } - }, - { - "id": "e9cf1958-d0ed-46b7-b597-9052c7648656", - "title": "Thursday morning investigation", - "hasCustomName": false, - "isFavorite": false, - "collectables": [ - { - "origin": "Explore Logs", - "type": "timeseries", - "queries": [ - { - "refId": "LABEL_BREAKDOWN_VALUES", - "queryType": "range", - "editorMode": "code", - "supportingQueryType": "grafana-lokiexplore-app", - "legendFormat": "{{detected_level}}", - "expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)" - } - ], - "timeRange": { - "to": "2025-02-13T11:31:20.536Z", - "from": "2025-02-13T11:16:20.536Z", - "raw": { - "from": "now-15m", - "to": "now" - } - }, - "datasource": { - "uid": "fe9k7u07b1a0wc" - }, - "url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all", - "id": "LABEL_BREAKDOWN_VALUES_detected_level", - "title": "detected_level", - "logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg", - "createdAt": "2025-02-13T11:31:23.638Z" - }, - { - "origin": "Explore Logs", - "type": "timeseries", - "queries": [ - { - "refId": "LABEL_BREAKDOWN_VALUES", - "queryType": "range", - "editorMode": "code", - "supportingQueryType": "grafana-lokiexplore-app", - "legendFormat": "{{service_name}}", - "expr": "sum(count_over_time({service_name=\"web_app_1\",service_name != \"\"} [$__auto])) by (service_name)" - } - ], - "timeRange": { - "to": "2025-02-13T11:31:20.536Z", - "from": "2025-02-13T11:16:20.536Z", - "raw": { - "from": "now-15m", - "to": "now" - } - }, - "datasource": { - "uid": "fe9k7u07b1a0wc" - }, - "url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all", - "id": "LABEL_BREAKDOWN_VALUES_service_name", - "title": "service_name", - "logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg", - "createdAt": "2025-02-13T11:31:41.507Z" - }, - { - "origin": "Explore Logs", - "type": "timeseries", - "queries": [ - { - "refId": "LABEL_BREAKDOWN_VALUES", - "queryType": "range", - "editorMode": "code", - "supportingQueryType": "grafana-lokiexplore-app", - "legendFormat": "{{service}}", - "expr": "sum(count_over_time({service_name=\"web_app_1\",service != \"\"} [$__auto])) by (service)" - } - ], - "timeRange": { - "to": "2025-02-13T11:31:20.536Z", - "from": "2025-02-13T11:16:20.536Z", - "raw": { - "from": "now-15m", - "to": "now" - } - }, - "datasource": { - "uid": "fe9k7u07b1a0wc" - }, - "url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all", - "id": "LABEL_BREAKDOWN_VALUES_service", - "title": "service", - "logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg", - "createdAt": "2025-02-13T11:31:43.698Z" - } - ], - "createdAt": "2025-02-13T11:31:23.637Z", - "updatedAt": "2025-02-13T11:31:43.698Z", - "viewMode": { - "mode": "compact", - "showComments": true, - "showTooltips": false - } - } -] diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod deleted file mode 100644 index 40677b56dab..00000000000 --- a/apps/investigations/go.mod +++ /dev/null @@ -1,102 +0,0 @@ -module github.com/grafana/grafana/apps/investigations - -go 1.25.5 - -require ( - github.com/grafana/grafana-app-sdk v0.48.7 - k8s.io/apimachinery v0.34.3 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e -) - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect - github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.13.0 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.4 // indirect - github.com/go-openapi/jsonreference v0.21.4 // indirect - github.com/go-openapi/swag v0.25.4 // indirect - github.com/go-openapi/swag/cmdutils v0.25.4 // indirect - github.com/go-openapi/swag/conv v0.25.4 // indirect - github.com/go-openapi/swag/fileutils v0.25.4 // indirect - github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-openapi/swag/jsonutils v0.25.4 // indirect - github.com/go-openapi/swag/loading v0.25.4 // indirect - github.com/go-openapi/swag/mangling v0.25.4 // indirect - github.com/go-openapi/swag/netutils v0.25.4 // indirect - github.com/go-openapi/swag/stringutils v0.25.4 // indirect - github.com/go-openapi/swag/typeutils v0.25.4 // indirect - github.com/go-openapi/swag/yamlutils v0.25.4 // indirect - github.com/go-test/deep v1.1.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.1 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect - github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/onsi/ginkgo/v2 v2.22.2 // indirect - github.com/onsi/gomega v1.36.2 // indirect - github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.4 // indirect - github.com/prometheus/procfs v0.19.2 // indirect - github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect - github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.4.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect - golang.org/x/time v0.14.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.3 // indirect - k8s.io/apiextensions-apiserver v0.34.3 // indirect - k8s.io/client-go v0.34.3 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect - sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect -) diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum deleted file mode 100644 index e5233ba82c9..00000000000 --- a/apps/investigations/go.sum +++ /dev/null @@ -1,264 +0,0 @@ -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/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= -github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= -github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= -github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= -github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= -github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= -github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= -github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= -github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= -github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= -github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= -github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= -github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= -github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= -github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= -github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= -github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= -github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= -github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= -github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= -github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= -github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= -github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= -github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= -github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= -github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= -github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= -github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= -github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= -github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= -github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= -github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= -github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= -github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= -github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= -github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= -github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= -github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= -github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= -gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= -gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= -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-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= -k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= -sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/apps/investigations/kinds/collectable.cue b/apps/investigations/kinds/collectable.cue deleted file mode 100644 index f9fc754a67c..00000000000 --- a/apps/investigations/kinds/collectable.cue +++ /dev/null @@ -1,43 +0,0 @@ -package investigations - -// Collectable represents an item collected during investigation -#Collectable: { - id: string - createdAt: string - - title: string - origin: string - type: string - queries: [...string] // +listType=atomic - timeRange: #TimeRange - datasource: #DatasourceRef - url: string - logoPath?: string - - note: string - noteUpdatedAt: string - - fieldConfig: string -} - -#CollectableSummary: { - id: string - title: string - logoPath: string - origin: string -} - -// TimeRange represents a time range with both absolute and relative values -#TimeRange: { - from: string - to: string - raw: { - from: string - to: string - } -} - -// DatasourceRef is a reference to a datasource -#DatasourceRef: { - uid: string -} diff --git a/apps/investigations/kinds/cue.mod/module.cue b/apps/investigations/kinds/cue.mod/module.cue deleted file mode 100644 index c592b1ce788..00000000000 --- a/apps/investigations/kinds/cue.mod/module.cue +++ /dev/null @@ -1,4 +0,0 @@ -module: "github.com/grafana/grafana/apps/investigations" -language: { - version: "v0.11.0" -} \ No newline at end of file diff --git a/apps/investigations/kinds/investigation.cue b/apps/investigations/kinds/investigation.cue deleted file mode 100644 index 1ade89416b9..00000000000 --- a/apps/investigations/kinds/investigation.cue +++ /dev/null @@ -1,43 +0,0 @@ -package investigations - -investigationV0alpha1: { - kind: "Investigation" - pluralName: "Investigations" - schema: { - spec: { - title: string - createdByProfile: #Person - hasCustomName: bool - isFavorite: bool - overviewNote: string - overviewNoteUpdatedAt: string - collectables: [...#Collectable] // +listType=atomic - viewMode: #ViewMode - } - } -} - -// Type definition for investigation summaries -#InvestigationSummary: { - title: string - createdByProfile: #Person - hasCustomName: bool - isFavorite: bool - overviewNote: string - overviewNoteUpdatedAt: string - viewMode: #ViewMode - collectableSummaries: [...#CollectableSummary] // +listType=atomic -} - -// Person represents a user profile with basic information -#Person: { - uid: string // Unique identifier for the user - name: string // Display name of the user - gravatarUrl: string // URL to user's Gravatar image -} - -#ViewMode: { - mode: "compact" | "full" - showComments: bool - showTooltips: bool -} diff --git a/apps/investigations/kinds/investigationindex.cue b/apps/investigations/kinds/investigationindex.cue deleted file mode 100644 index 65c5e3ae79f..00000000000 --- a/apps/investigations/kinds/investigationindex.cue +++ /dev/null @@ -1,18 +0,0 @@ -package investigations - -investigationIndexV0alpha1:{ - kind: "InvestigationIndex" - pluralName: "InvestigationIndexes" - schema: { - spec: { - // Title of the index, e.g. 'Favorites' or 'My Investigations' - title: string - - // The Person who owns this investigation index - owner: #Person - - // Array of investigation summaries - investigationSummaries: [...#InvestigationSummary] // +listType=atomic - } - } -} diff --git a/apps/investigations/kinds/manifest.cue b/apps/investigations/kinds/manifest.cue deleted file mode 100644 index 947d84be482..00000000000 --- a/apps/investigations/kinds/manifest.cue +++ /dev/null @@ -1,18 +0,0 @@ -package investigations - -manifest: { - appName: "investigations" - groupOverride: "investigations.grafana.app" - versions: { - "v0alpha1": { - codegen: { - ts: {enabled: false} - go: {enabled: true} - } - kinds: [ - investigationV0alpha1, - investigationIndexV0alpha1, - ] - } - } -} \ No newline at end of file diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go b/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go deleted file mode 100644 index bd538eb9c30..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go +++ /dev/null @@ -1,18 +0,0 @@ -package v0alpha1 - -import "k8s.io/apimachinery/pkg/runtime/schema" - -const ( - // APIGroup is the API group used by all kinds in this package - APIGroup = "investigations.grafana.app" - // APIVersion is the API version used by all kinds in this package - APIVersion = "v0alpha1" -) - -var ( - // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package - GroupVersion = schema.GroupVersion{ - Group: APIGroup, - Version: APIVersion, - } -) diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go deleted file mode 100644 index dea4d71802d..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go +++ /dev/null @@ -1,80 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" -) - -type InvestigationClient struct { - client *resource.TypedClient[*Investigation, *InvestigationList] -} - -func NewInvestigationClient(client resource.Client) *InvestigationClient { - return &InvestigationClient{ - client: resource.NewTypedClient[*Investigation, *InvestigationList](client, InvestigationKind()), - } -} - -func NewInvestigationClientFromGenerator(generator resource.ClientGenerator) (*InvestigationClient, error) { - c, err := generator.ClientFor(InvestigationKind()) - if err != nil { - return nil, err - } - return NewInvestigationClient(c), nil -} - -func (c *InvestigationClient) Get(ctx context.Context, identifier resource.Identifier) (*Investigation, error) { - return c.client.Get(ctx, identifier) -} - -func (c *InvestigationClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *InvestigationClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, 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 *InvestigationClient) Create(ctx context.Context, obj *Investigation, opts resource.CreateOptions) (*Investigation, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = InvestigationKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *InvestigationClient) Update(ctx context.Context, obj *Investigation, opts resource.UpdateOptions) (*Investigation, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *InvestigationClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Investigation, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *InvestigationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_codec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_codec_gen.go deleted file mode 100644 index 6bde81b50d3..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_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" -) - -// InvestigationJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type InvestigationJSONCodec struct{} - -// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*InvestigationJSONCodec) 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 (*InvestigationJSONCodec) Write(writer io.Writer, from resource.Object) error { - return json.NewEncoder(writer).Encode(from) -} - -// Interface compliance checks -var _ resource.Codec = &InvestigationJSONCodec{} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go deleted file mode 100644 index ffd588fec5e..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_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 InvestigationMetadata 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"` -} - -// NewInvestigationMetadata creates a new InvestigationMetadata object. -func NewInvestigationMetadata() *InvestigationMetadata { - return &InvestigationMetadata{ - Finalizers: []string{}, - Labels: map[string]string{}, - } -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_object_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_object_gen.go deleted file mode 100644 index f64d7b6e047..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_object_gen.go +++ /dev/null @@ -1,293 +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 Investigation struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - - // Spec is the spec of the Investigation - Spec InvestigationSpec `json:"spec" yaml:"spec"` -} - -func (o *Investigation) GetSpec() any { - return o.Spec -} - -func (o *Investigation) SetSpec(spec any) error { - cast, ok := spec.(InvestigationSpec) - if !ok { - return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) - } - o.Spec = cast - return nil -} - -func (o *Investigation) GetSubresources() map[string]any { - return map[string]any{} -} - -func (o *Investigation) GetSubresource(name string) (any, bool) { - switch name { - default: - return nil, false - } -} - -func (o *Investigation) SetSubresource(name string, value any) error { - switch name { - default: - return fmt.Errorf("subresource '%s' does not exist", name) - } -} - -func (o *Investigation) 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 *Investigation) 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 *Investigation) 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 *Investigation) 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 *Investigation) GetCreatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/createdBy"] -} - -func (o *Investigation) SetCreatedBy(createdBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy -} - -func (o *Investigation) 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 *Investigation) 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 *Investigation) GetUpdatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/updatedBy"] -} - -func (o *Investigation) SetUpdatedBy(updatedBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy -} - -func (o *Investigation) Copy() resource.Object { - return resource.CopyObject(o) -} - -func (o *Investigation) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *Investigation) DeepCopy() *Investigation { - cpy := &Investigation{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *Investigation) DeepCopyInto(dst *Investigation) { - dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion - dst.TypeMeta.Kind = o.TypeMeta.Kind - o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) - o.Spec.DeepCopyInto(&dst.Spec) -} - -// Interface compliance compile-time check -var _ resource.Object = &Investigation{} - -// +k8s:openapi-gen=true -type InvestigationList struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []Investigation `json:"items" yaml:"items"` -} - -func (o *InvestigationList) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *InvestigationList) Copy() resource.ListObject { - cpy := &InvestigationList{ - TypeMeta: o.TypeMeta, - Items: make([]Investigation, len(o.Items)), - } - o.ListMeta.DeepCopyInto(&cpy.ListMeta) - for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*Investigation); ok { - cpy.Items[i] = *item - } - } - return cpy -} - -func (o *InvestigationList) 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 *InvestigationList) SetItems(items []resource.Object) { - o.Items = make([]Investigation, len(items)) - for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*Investigation) - } -} - -func (o *InvestigationList) DeepCopy() *InvestigationList { - cpy := &InvestigationList{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *InvestigationList) DeepCopyInto(dst *InvestigationList) { - resource.CopyObjectInto(dst, o) -} - -// Interface compliance compile-time check -var _ resource.ListObject = &InvestigationList{} - -// Copy methods for all subresource types - -// DeepCopy creates a full deep copy of Spec -func (s *InvestigationSpec) DeepCopy() *InvestigationSpec { - cpy := &InvestigationSpec{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies Spec into another Spec object -func (s *InvestigationSpec) DeepCopyInto(dst *InvestigationSpec) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_schema_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_schema_gen.go deleted file mode 100644 index 2a1f03c884a..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_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 ( - schemaInvestigation = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &Investigation{}, &InvestigationList{}, resource.WithKind("Investigation"), - resource.WithPlural("investigations"), resource.WithScope(resource.NamespacedScope)) - kindInvestigation = resource.Kind{ - Schema: schemaInvestigation, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &InvestigationJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func InvestigationKind() resource.Kind { - return kindInvestigation -} - -// Schema returns a resource.SimpleSchema representation of Investigation -func InvestigationSchema() *resource.SimpleSchema { - return schemaInvestigation -} - -// Interface compliance checks -var _ resource.Schema = kindInvestigation diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go deleted file mode 100644 index 88d74be54eb..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go +++ /dev/null @@ -1,126 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// Person represents a user profile with basic information -// +k8s:openapi-gen=true -type InvestigationPerson struct { - // Unique identifier for the user - Uid string `json:"uid"` - // Display name of the user - Name string `json:"name"` - // URL to user's Gravatar image - GravatarUrl string `json:"gravatarUrl"` -} - -// NewInvestigationPerson creates a new InvestigationPerson object. -func NewInvestigationPerson() *InvestigationPerson { - return &InvestigationPerson{} -} - -// Collectable represents an item collected during investigation -// +k8s:openapi-gen=true -type InvestigationCollectable struct { - Id string `json:"id"` - CreatedAt string `json:"createdAt"` - Title string `json:"title"` - Origin string `json:"origin"` - Type string `json:"type"` - // +listType=atomic - Queries []string `json:"queries"` - TimeRange InvestigationTimeRange `json:"timeRange"` - Datasource InvestigationDatasourceRef `json:"datasource"` - Url string `json:"url"` - LogoPath *string `json:"logoPath,omitempty"` - Note string `json:"note"` - NoteUpdatedAt string `json:"noteUpdatedAt"` - FieldConfig string `json:"fieldConfig"` -} - -// NewInvestigationCollectable creates a new InvestigationCollectable object. -func NewInvestigationCollectable() *InvestigationCollectable { - return &InvestigationCollectable{ - Queries: []string{}, - TimeRange: *NewInvestigationTimeRange(), - Datasource: *NewInvestigationDatasourceRef(), - } -} - -// TimeRange represents a time range with both absolute and relative values -// +k8s:openapi-gen=true -type InvestigationTimeRange struct { - From string `json:"from"` - To string `json:"to"` - Raw InvestigationV0alpha1TimeRangeRaw `json:"raw"` -} - -// NewInvestigationTimeRange creates a new InvestigationTimeRange object. -func NewInvestigationTimeRange() *InvestigationTimeRange { - return &InvestigationTimeRange{ - Raw: *NewInvestigationV0alpha1TimeRangeRaw(), - } -} - -// DatasourceRef is a reference to a datasource -// +k8s:openapi-gen=true -type InvestigationDatasourceRef struct { - Uid string `json:"uid"` -} - -// NewInvestigationDatasourceRef creates a new InvestigationDatasourceRef object. -func NewInvestigationDatasourceRef() *InvestigationDatasourceRef { - return &InvestigationDatasourceRef{} -} - -// +k8s:openapi-gen=true -type InvestigationViewMode struct { - Mode InvestigationViewModeMode `json:"mode"` - ShowComments bool `json:"showComments"` - ShowTooltips bool `json:"showTooltips"` -} - -// NewInvestigationViewMode creates a new InvestigationViewMode object. -func NewInvestigationViewMode() *InvestigationViewMode { - return &InvestigationViewMode{} -} - -// +k8s:openapi-gen=true -type InvestigationSpec struct { - Title string `json:"title"` - CreatedByProfile InvestigationPerson `json:"createdByProfile"` - HasCustomName bool `json:"hasCustomName"` - IsFavorite bool `json:"isFavorite"` - OverviewNote string `json:"overviewNote"` - OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"` - // +listType=atomic - Collectables []InvestigationCollectable `json:"collectables"` - ViewMode InvestigationViewMode `json:"viewMode"` -} - -// NewInvestigationSpec creates a new InvestigationSpec object. -func NewInvestigationSpec() *InvestigationSpec { - return &InvestigationSpec{ - CreatedByProfile: *NewInvestigationPerson(), - Collectables: []InvestigationCollectable{}, - ViewMode: *NewInvestigationViewMode(), - } -} - -// +k8s:openapi-gen=true -type InvestigationV0alpha1TimeRangeRaw struct { - From string `json:"from"` - To string `json:"to"` -} - -// NewInvestigationV0alpha1TimeRangeRaw creates a new InvestigationV0alpha1TimeRangeRaw object. -func NewInvestigationV0alpha1TimeRangeRaw() *InvestigationV0alpha1TimeRangeRaw { - return &InvestigationV0alpha1TimeRangeRaw{} -} - -// +k8s:openapi-gen=true -type InvestigationViewModeMode string - -const ( - InvestigationViewModeModeCompact InvestigationViewModeMode = "compact" - InvestigationViewModeModeFull InvestigationViewModeMode = "full" -) diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go deleted file mode 100644 index dafe3e1012e..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go +++ /dev/null @@ -1,80 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" -) - -type InvestigationIndexClient struct { - client *resource.TypedClient[*InvestigationIndex, *InvestigationIndexList] -} - -func NewInvestigationIndexClient(client resource.Client) *InvestigationIndexClient { - return &InvestigationIndexClient{ - client: resource.NewTypedClient[*InvestigationIndex, *InvestigationIndexList](client, InvestigationIndexKind()), - } -} - -func NewInvestigationIndexClientFromGenerator(generator resource.ClientGenerator) (*InvestigationIndexClient, error) { - c, err := generator.ClientFor(InvestigationIndexKind()) - if err != nil { - return nil, err - } - return NewInvestigationIndexClient(c), nil -} - -func (c *InvestigationIndexClient) Get(ctx context.Context, identifier resource.Identifier) (*InvestigationIndex, error) { - return c.client.Get(ctx, identifier) -} - -func (c *InvestigationIndexClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *InvestigationIndexClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, 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 *InvestigationIndexClient) Create(ctx context.Context, obj *InvestigationIndex, opts resource.CreateOptions) (*InvestigationIndex, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = InvestigationIndexKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *InvestigationIndexClient) Update(ctx context.Context, obj *InvestigationIndex, opts resource.UpdateOptions) (*InvestigationIndex, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *InvestigationIndexClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*InvestigationIndex, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *InvestigationIndexClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_codec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_codec_gen.go deleted file mode 100644 index 0496ce4af6f..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_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" -) - -// InvestigationIndexJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type InvestigationIndexJSONCodec struct{} - -// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*InvestigationIndexJSONCodec) 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 (*InvestigationIndexJSONCodec) Write(writer io.Writer, from resource.Object) error { - return json.NewEncoder(writer).Encode(from) -} - -// Interface compliance checks -var _ resource.Codec = &InvestigationIndexJSONCodec{} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go deleted file mode 100644 index e7bef02a137..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_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 InvestigationIndexMetadata 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"` -} - -// NewInvestigationIndexMetadata creates a new InvestigationIndexMetadata object. -func NewInvestigationIndexMetadata() *InvestigationIndexMetadata { - return &InvestigationIndexMetadata{ - Finalizers: []string{}, - Labels: map[string]string{}, - } -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_object_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_object_gen.go deleted file mode 100644 index b8272b4fae7..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_object_gen.go +++ /dev/null @@ -1,293 +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 InvestigationIndex struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - - // Spec is the spec of the InvestigationIndex - Spec InvestigationIndexSpec `json:"spec" yaml:"spec"` -} - -func (o *InvestigationIndex) GetSpec() any { - return o.Spec -} - -func (o *InvestigationIndex) SetSpec(spec any) error { - cast, ok := spec.(InvestigationIndexSpec) - if !ok { - return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) - } - o.Spec = cast - return nil -} - -func (o *InvestigationIndex) GetSubresources() map[string]any { - return map[string]any{} -} - -func (o *InvestigationIndex) GetSubresource(name string) (any, bool) { - switch name { - default: - return nil, false - } -} - -func (o *InvestigationIndex) SetSubresource(name string, value any) error { - switch name { - default: - return fmt.Errorf("subresource '%s' does not exist", name) - } -} - -func (o *InvestigationIndex) 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 *InvestigationIndex) 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 *InvestigationIndex) 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 *InvestigationIndex) 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 *InvestigationIndex) GetCreatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/createdBy"] -} - -func (o *InvestigationIndex) SetCreatedBy(createdBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy -} - -func (o *InvestigationIndex) 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 *InvestigationIndex) 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 *InvestigationIndex) GetUpdatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/updatedBy"] -} - -func (o *InvestigationIndex) SetUpdatedBy(updatedBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy -} - -func (o *InvestigationIndex) Copy() resource.Object { - return resource.CopyObject(o) -} - -func (o *InvestigationIndex) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *InvestigationIndex) DeepCopy() *InvestigationIndex { - cpy := &InvestigationIndex{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *InvestigationIndex) DeepCopyInto(dst *InvestigationIndex) { - dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion - dst.TypeMeta.Kind = o.TypeMeta.Kind - o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) - o.Spec.DeepCopyInto(&dst.Spec) -} - -// Interface compliance compile-time check -var _ resource.Object = &InvestigationIndex{} - -// +k8s:openapi-gen=true -type InvestigationIndexList struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []InvestigationIndex `json:"items" yaml:"items"` -} - -func (o *InvestigationIndexList) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *InvestigationIndexList) Copy() resource.ListObject { - cpy := &InvestigationIndexList{ - TypeMeta: o.TypeMeta, - Items: make([]InvestigationIndex, len(o.Items)), - } - o.ListMeta.DeepCopyInto(&cpy.ListMeta) - for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*InvestigationIndex); ok { - cpy.Items[i] = *item - } - } - return cpy -} - -func (o *InvestigationIndexList) 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 *InvestigationIndexList) SetItems(items []resource.Object) { - o.Items = make([]InvestigationIndex, len(items)) - for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*InvestigationIndex) - } -} - -func (o *InvestigationIndexList) DeepCopy() *InvestigationIndexList { - cpy := &InvestigationIndexList{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *InvestigationIndexList) DeepCopyInto(dst *InvestigationIndexList) { - resource.CopyObjectInto(dst, o) -} - -// Interface compliance compile-time check -var _ resource.ListObject = &InvestigationIndexList{} - -// Copy methods for all subresource types - -// DeepCopy creates a full deep copy of Spec -func (s *InvestigationIndexSpec) DeepCopy() *InvestigationIndexSpec { - cpy := &InvestigationIndexSpec{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies Spec into another Spec object -func (s *InvestigationIndexSpec) DeepCopyInto(dst *InvestigationIndexSpec) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_schema_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_schema_gen.go deleted file mode 100644 index 3a0041dc6b8..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_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 ( - schemaInvestigationIndex = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &InvestigationIndex{}, &InvestigationIndexList{}, resource.WithKind("InvestigationIndex"), - resource.WithPlural("investigationindexes"), resource.WithScope(resource.NamespacedScope)) - kindInvestigationIndex = resource.Kind{ - Schema: schemaInvestigationIndex, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &InvestigationIndexJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func InvestigationIndexKind() resource.Kind { - return kindInvestigationIndex -} - -// Schema returns a resource.SimpleSchema representation of InvestigationIndex -func InvestigationIndexSchema() *resource.SimpleSchema { - return schemaInvestigationIndex -} - -// Interface compliance checks -var _ resource.Schema = kindInvestigationIndex diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go deleted file mode 100644 index 44be927c850..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go +++ /dev/null @@ -1,94 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// Person represents a user profile with basic information -// +k8s:openapi-gen=true -type InvestigationIndexPerson struct { - // Unique identifier for the user - Uid string `json:"uid"` - // Display name of the user - Name string `json:"name"` - // URL to user's Gravatar image - GravatarUrl string `json:"gravatarUrl"` -} - -// NewInvestigationIndexPerson creates a new InvestigationIndexPerson object. -func NewInvestigationIndexPerson() *InvestigationIndexPerson { - return &InvestigationIndexPerson{} -} - -// Type definition for investigation summaries -// +k8s:openapi-gen=true -type InvestigationIndexInvestigationSummary struct { - Title string `json:"title"` - CreatedByProfile InvestigationIndexPerson `json:"createdByProfile"` - HasCustomName bool `json:"hasCustomName"` - IsFavorite bool `json:"isFavorite"` - OverviewNote string `json:"overviewNote"` - OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"` - ViewMode InvestigationIndexViewMode `json:"viewMode"` - // +listType=atomic - CollectableSummaries []InvestigationIndexCollectableSummary `json:"collectableSummaries"` -} - -// NewInvestigationIndexInvestigationSummary creates a new InvestigationIndexInvestigationSummary object. -func NewInvestigationIndexInvestigationSummary() *InvestigationIndexInvestigationSummary { - return &InvestigationIndexInvestigationSummary{ - CreatedByProfile: *NewInvestigationIndexPerson(), - ViewMode: *NewInvestigationIndexViewMode(), - CollectableSummaries: []InvestigationIndexCollectableSummary{}, - } -} - -// +k8s:openapi-gen=true -type InvestigationIndexViewMode struct { - Mode InvestigationIndexViewModeMode `json:"mode"` - ShowComments bool `json:"showComments"` - ShowTooltips bool `json:"showTooltips"` -} - -// NewInvestigationIndexViewMode creates a new InvestigationIndexViewMode object. -func NewInvestigationIndexViewMode() *InvestigationIndexViewMode { - return &InvestigationIndexViewMode{} -} - -// +k8s:openapi-gen=true -type InvestigationIndexCollectableSummary struct { - Id string `json:"id"` - Title string `json:"title"` - LogoPath string `json:"logoPath"` - Origin string `json:"origin"` -} - -// NewInvestigationIndexCollectableSummary creates a new InvestigationIndexCollectableSummary object. -func NewInvestigationIndexCollectableSummary() *InvestigationIndexCollectableSummary { - return &InvestigationIndexCollectableSummary{} -} - -// +k8s:openapi-gen=true -type InvestigationIndexSpec struct { - // Title of the index, e.g. 'Favorites' or 'My Investigations' - Title string `json:"title"` - // The Person who owns this investigation index - Owner InvestigationIndexPerson `json:"owner"` - // Array of investigation summaries - // +listType=atomic - InvestigationSummaries []InvestigationIndexInvestigationSummary `json:"investigationSummaries"` -} - -// NewInvestigationIndexSpec creates a new InvestigationIndexSpec object. -func NewInvestigationIndexSpec() *InvestigationIndexSpec { - return &InvestigationIndexSpec{ - Owner: *NewInvestigationIndexPerson(), - InvestigationSummaries: []InvestigationIndexInvestigationSummary{}, - } -} - -// +k8s:openapi-gen=true -type InvestigationIndexViewModeMode string - -const ( - InvestigationIndexViewModeModeCompact InvestigationIndexViewModeMode = "compact" - InvestigationIndexViewModeModeFull InvestigationIndexViewModeMode = "full" -) diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_status_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_status_gen.go deleted file mode 100644 index 455d39bdb48..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type InvestigationIndexstatusOperatorState 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 InvestigationIndexStatusOperatorStateState `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"` -} - -// NewInvestigationIndexstatusOperatorState creates a new InvestigationIndexstatusOperatorState object. -func NewInvestigationIndexstatusOperatorState() *InvestigationIndexstatusOperatorState { - return &InvestigationIndexstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type InvestigationIndexStatus 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]InvestigationIndexstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewInvestigationIndexStatus creates a new InvestigationIndexStatus object. -func NewInvestigationIndexStatus() *InvestigationIndexStatus { - return &InvestigationIndexStatus{} -} - -// +k8s:openapi-gen=true -type InvestigationIndexStatusOperatorStateState string - -const ( - InvestigationIndexStatusOperatorStateStateSuccess InvestigationIndexStatusOperatorStateState = "success" - InvestigationIndexStatusOperatorStateStateInProgress InvestigationIndexStatusOperatorStateState = "in_progress" - InvestigationIndexStatusOperatorStateStateFailed InvestigationIndexStatusOperatorStateState = "failed" -) diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/zz_openapi_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/zz_openapi_gen.go deleted file mode 100644 index 4a095da63fc..00000000000 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/zz_openapi_gen.go +++ /dev/null @@ -1,1014 +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/investigations/pkg/apis/investigations/v0alpha1.Investigation": schema_pkg_apis_investigations_v0alpha1_Investigation(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationCollectable": schema_pkg_apis_investigations_v0alpha1_InvestigationCollectable(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationDatasourceRef": schema_pkg_apis_investigations_v0alpha1_InvestigationDatasourceRef(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndex": schema_pkg_apis_investigations_v0alpha1_InvestigationIndex(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexCollectableSummary": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexCollectableSummary(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexInvestigationSummary": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexInvestigationSummary(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexList": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexList(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexPerson": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexPerson(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexSpec": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexSpec(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexStatus": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexStatus(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexViewMode": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexViewMode(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexstatusOperatorState": schema_pkg_apis_investigations_v0alpha1_InvestigationIndexstatusOperatorState(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationList": schema_pkg_apis_investigations_v0alpha1_InvestigationList(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationPerson": schema_pkg_apis_investigations_v0alpha1_InvestigationPerson(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationSpec": schema_pkg_apis_investigations_v0alpha1_InvestigationSpec(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationStatus": schema_pkg_apis_investigations_v0alpha1_InvestigationStatus(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationTimeRange": schema_pkg_apis_investigations_v0alpha1_InvestigationTimeRange(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationV0alpha1TimeRangeRaw": schema_pkg_apis_investigations_v0alpha1_InvestigationV0alpha1TimeRangeRaw(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationViewMode": schema_pkg_apis_investigations_v0alpha1_InvestigationViewMode(ref), - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationstatusOperatorState": schema_pkg_apis_investigations_v0alpha1_InvestigationstatusOperatorState(ref), - } -} - -func schema_pkg_apis_investigations_v0alpha1_Investigation(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 Investigation", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationSpec", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationCollectable(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Collectable represents an item collected during investigation", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "id": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "createdAt": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "origin": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "type": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "queries": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "timeRange": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationTimeRange"), - }, - }, - "datasource": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationDatasourceRef"), - }, - }, - "url": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "logoPath": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "note": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "noteUpdatedAt": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "fieldConfig": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"id", "createdAt", "title", "origin", "type", "queries", "timeRange", "datasource", "url", "note", "noteUpdatedAt", "fieldConfig"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationDatasourceRef", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationTimeRange"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationDatasourceRef(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "DatasourceRef is a reference to a datasource", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "uid": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"uid"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndex(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 InvestigationIndex", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexSpec", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexCollectableSummary(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "id": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "logoPath": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "origin": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"id", "title", "logoPath", "origin"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexInvestigationSummary(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Type definition for investigation summaries", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "createdByProfile": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexPerson"), - }, - }, - "hasCustomName": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "isFavorite": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "overviewNote": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "overviewNoteUpdatedAt": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "viewMode": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexViewMode"), - }, - }, - "collectableSummaries": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - 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/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexCollectableSummary"), - }, - }, - }, - }, - }, - }, - Required: []string{"title", "createdByProfile", "hasCustomName", "isFavorite", "overviewNote", "overviewNoteUpdatedAt", "viewMode", "collectableSummaries"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexCollectableSummary", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexPerson", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexViewMode"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexList(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/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndex"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndex", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexPerson(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Person represents a user profile with basic information", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "uid": { - SchemaProps: spec.SchemaProps{ - Description: "Unique identifier for the user", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "name": { - SchemaProps: spec.SchemaProps{ - Description: "Display name of the user", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "gravatarUrl": { - SchemaProps: spec.SchemaProps{ - Description: "URL to user's Gravatar image", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"uid", "name", "gravatarUrl"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexSpec(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{ - Description: "Title of the index, e.g. 'Favorites' or 'My Investigations'", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "owner": { - SchemaProps: spec.SchemaProps{ - Description: "The Person who owns this investigation index", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexPerson"), - }, - }, - "investigationSummaries": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "Array of investigation summaries", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexInvestigationSummary"), - }, - }, - }, - }, - }, - }, - Required: []string{"title", "owner", "investigationSummaries"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexInvestigationSummary", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexPerson"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexStatus(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/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexstatusOperatorState"), - }, - }, - }, - }, - }, - "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/investigations/pkg/apis/investigations/v0alpha1.InvestigationIndexstatusOperatorState"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexViewMode(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "mode": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "showComments": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "showTooltips": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - }, - Required: []string{"mode", "showComments", "showTooltips"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationIndexstatusOperatorState(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_investigations_v0alpha1_InvestigationList(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/investigations/pkg/apis/investigations/v0alpha1.Investigation"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.Investigation", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationPerson(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Person represents a user profile with basic information", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "uid": { - SchemaProps: spec.SchemaProps{ - Description: "Unique identifier for the user", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "name": { - SchemaProps: spec.SchemaProps{ - Description: "Display name of the user", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "gravatarUrl": { - SchemaProps: spec.SchemaProps{ - Description: "URL to user's Gravatar image", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"uid", "name", "gravatarUrl"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "spec is the schema of our resource", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "createdByProfile": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationPerson"), - }, - }, - "hasCustomName": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "isFavorite": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "overviewNote": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "overviewNoteUpdatedAt": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "collectables": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - 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/investigations/pkg/apis/investigations/v0alpha1.InvestigationCollectable"), - }, - }, - }, - }, - }, - "viewMode": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationViewMode"), - }, - }, - }, - Required: []string{"title", "createdByProfile", "hasCustomName", "isFavorite", "overviewNote", "overviewNoteUpdatedAt", "collectables", "viewMode"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationCollectable", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationPerson", "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationViewMode"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationStatus(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/investigations/pkg/apis/investigations/v0alpha1.InvestigationstatusOperatorState"), - }, - }, - }, - }, - }, - "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/investigations/pkg/apis/investigations/v0alpha1.InvestigationstatusOperatorState"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationTimeRange(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TimeRange represents a time range with both absolute and relative values", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "from": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "to": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "raw": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationV0alpha1TimeRangeRaw"), - }, - }, - }, - Required: []string{"from", "to", "raw"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1.InvestigationV0alpha1TimeRangeRaw"}, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationV0alpha1TimeRangeRaw(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "from": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "to": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"from", "to"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationViewMode(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "mode": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "showComments": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "showTooltips": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - }, - Required: []string{"mode", "showComments", "showTooltips"}, - }, - }, - } -} - -func schema_pkg_apis_investigations_v0alpha1_InvestigationstatusOperatorState(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/investigations/pkg/apis/investigations_manifest.go b/apps/investigations/pkg/apis/investigations_manifest.go deleted file mode 100644 index 249e9b49758..00000000000 --- a/apps/investigations/pkg/apis/investigations_manifest.go +++ /dev/null @@ -1,136 +0,0 @@ -// -// This file is generated by grafana-app-sdk -// DO NOT EDIT -// - -package apis - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/resource" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kube-openapi/pkg/spec3" - "k8s.io/kube-openapi/pkg/validation/spec" - - v0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1" -) - -var ( - rawSchemaInvestigationv0alpha1 = []byte(`{"Collectable":{"additionalProperties":false,"description":"Collectable represents an item collected during investigation","properties":{"createdAt":{"type":"string"},"datasource":{"$ref":"#/components/schemas/DatasourceRef"},"fieldConfig":{"type":"string"},"id":{"type":"string"},"logoPath":{"type":"string"},"note":{"type":"string"},"noteUpdatedAt":{"type":"string"},"origin":{"type":"string"},"queries":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"timeRange":{"$ref":"#/components/schemas/TimeRange"},"title":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}},"required":["id","createdAt","title","origin","type","queries","timeRange","datasource","url","note","noteUpdatedAt","fieldConfig"],"type":"object"},"DatasourceRef":{"additionalProperties":false,"description":"DatasourceRef is a reference to a datasource","properties":{"uid":{"type":"string"}},"required":["uid"],"type":"object"},"Investigation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"TimeRange":{"additionalProperties":false,"description":"TimeRange represents a time range with both absolute and relative values","properties":{"from":{"type":"string"},"raw":{"additionalProperties":false,"properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"type":"object"},"to":{"type":"string"}},"required":["from","to","raw"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"collectables":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Collectable"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","collectables","viewMode"],"type":"object"}}`) - versionSchemaInvestigationv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaInvestigationv0alpha1, &versionSchemaInvestigationv0alpha1) - rawSchemaInvestigationIndexv0alpha1 = []byte(`{"CollectableSummary":{"additionalProperties":false,"properties":{"id":{"type":"string"},"logoPath":{"type":"string"},"origin":{"type":"string"},"title":{"type":"string"}},"required":["id","title","logoPath","origin"],"type":"object"},"InvestigationIndex":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"InvestigationSummary":{"additionalProperties":false,"description":"Type definition for investigation summaries","properties":{"collectableSummaries":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/CollectableSummary"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","viewMode","collectableSummaries"],"type":"object"},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"investigationSummaries":{"description":"Array of investigation summaries\n+listType=atomic","items":{"$ref":"#/components/schemas/InvestigationSummary"},"type":"array"},"owner":{"$ref":"#/components/schemas/Person","description":"The Person who owns this investigation index"},"title":{"description":"Title of the index, e.g. 'Favorites' or 'My Investigations'","type":"string"}},"required":["title","owner","investigationSummaries"],"type":"object"}}`) - versionSchemaInvestigationIndexv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaInvestigationIndexv0alpha1, &versionSchemaInvestigationIndexv0alpha1) -) - -var appManifestData = app.ManifestData{ - AppName: "investigations", - Group: "investigations.grafana.app", - PreferredVersion: "v0alpha1", - Versions: []app.ManifestVersion{ - { - Name: "v0alpha1", - Served: true, - Kinds: []app.ManifestVersionKind{ - { - Kind: "Investigation", - Plural: "Investigations", - Scope: "Namespaced", - Conversion: false, - Schema: &versionSchemaInvestigationv0alpha1, - }, - - { - Kind: "InvestigationIndex", - Plural: "InvestigationIndexes", - Scope: "Namespaced", - Conversion: false, - Schema: &versionSchemaInvestigationIndexv0alpha1, - }, - }, - Routes: app.ManifestVersionRoutes{ - Namespaced: map[string]spec3.PathProps{}, - Cluster: map[string]spec3.PathProps{}, - Schemas: map[string]spec.Schema{}, - }, - }, - }, -} - -func LocalManifest() app.Manifest { - return app.NewEmbeddedManifest(appManifestData) -} - -func RemoteManifest() app.Manifest { - return app.NewAPIServerManifest("investigations") -} - -var kindVersionToGoType = map[string]resource.Kind{ - "Investigation/v0alpha1": v0alpha1.InvestigationKind(), - "InvestigationIndex/v0alpha1": v0alpha1.InvestigationIndexKind(), -} - -// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. -// If there is no association for the provided Kind and Version, exists will return false. -func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) { - goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] - return goType, exists -} - -var customRouteToGoResponseType = map[string]any{} - -// 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. -// If there is no association for the provided kind, version, custom route path, and method, exists will return false. -// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) -func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { - if len(path) > 0 && path[0] == '/' { - path = path[1:] - } - goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] - return goType, exists -} - -var customRouteToGoParamsType = map[string]runtime.Object{} - -func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) { - if len(path) > 0 && path[0] == '/' { - path = path[1:] - } - goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] - return goType, exists -} - -var customRouteToGoRequestBodyType = map[string]any{} - -func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { - if len(path) > 0 && path[0] == '/' { - path = path[1:] - } - goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] - return goType, exists -} - -type GoTypeAssociator struct{} - -func NewGoTypeAssociator() *GoTypeAssociator { - return &GoTypeAssociator{} -} - -func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { - return ManifestGoTypeAssociator(kind, version) -} -func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) { - return ManifestCustomRouteResponsesAssociator(kind, version, path, verb) -} -func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { - return ManifestCustomRouteQueryAssociator(kind, version, path, verb) -} -func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { - return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) -} diff --git a/apps/investigations/pkg/app/investigations_app.go b/apps/investigations/pkg/app/investigations_app.go deleted file mode 100644 index 516d278788d..00000000000 --- a/apps/investigations/pkg/app/investigations_app.go +++ /dev/null @@ -1,62 +0,0 @@ -package app - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/operator" - "github.com/grafana/grafana-app-sdk/resource" - "github.com/grafana/grafana-app-sdk/simple" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/klog/v2" - - investigationsv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1" -) - -func New(cfg app.Config) (app.App, error) { - var err error - simpleConfig := simple.AppConfig{ - Name: "investigation", - KubeConfig: cfg.KubeConfig, - InformerConfig: simple.AppInformerConfig{ - InformerOptions: operator.InformerOptions{ - ErrorHandler: func(_ context.Context, err error) { - klog.ErrorS(err, "Informer processing error") - }, - }, - }, - ManagedKinds: []simple.AppManagedKind{ - { - Kind: investigationsv0alpha1.InvestigationKind(), - }, - { - Kind: investigationsv0alpha1.InvestigationIndexKind(), - }, - }, - } - - a, err := simple.NewApp(simpleConfig) - if err != nil { - return nil, err - } - - err = a.ValidateManifest(cfg.ManifestData) - if err != nil { - return nil, err - } - - return a, nil -} - -func GetKinds() map[schema.GroupVersion][]resource.Kind { - gv := schema.GroupVersion{ - Group: investigationsv0alpha1.InvestigationKind().Group(), - Version: investigationsv0alpha1.InvestigationKind().Version(), - } - return map[schema.GroupVersion][]resource.Kind{ - gv: { - investigationsv0alpha1.InvestigationKind(), - investigationsv0alpha1.InvestigationIndexKind(), - }, - } -} diff --git a/apps/investigations/plugin/src/generated/investigation/v0alpha1/investigation_object_gen.ts b/apps/investigations/plugin/src/generated/investigation/v0alpha1/investigation_object_gen.ts deleted file mode 100644 index 3c7cf0874f8..00000000000 --- a/apps/investigations/plugin/src/generated/investigation/v0alpha1/investigation_object_gen.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file was generated by grafana-app-sdk. DO NOT EDIT. - */ -import { Spec } from './types.spec.gen'; -import { Status } from './types.status.gen'; - -export interface Metadata { - name: string; - namespace: string; - generateName?: string; - selfLink?: string; - uid?: string; - resourceVersion?: string; - generation?: number; - creationTimestamp?: string; - deletionTimestamp?: string; - deletionGracePeriodSeconds?: number; - labels?: Record; - annotations?: Record; - ownerReferences?: OwnerReference[]; - finalizers?: string[]; - managedFields?: ManagedFieldsEntry[]; -} - -export interface OwnerReference { - apiVersion: string; - kind: string; - name: string; - uid: string; - controller?: boolean; - blockOwnerDeletion?: boolean; -} - -export interface ManagedFieldsEntry { - manager?: string; - operation?: string; - apiVersion?: string; - time?: string; - fieldsType?: string; - subresource?: string; -} - -export interface Investigation { - kind: string; - apiVersion: string; - metadata: Metadata; - spec: Spec; - status: Status; -} diff --git a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.metadata.gen.ts b/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.metadata.gen.ts deleted file mode 100644 index 4377f3c1d08..00000000000 --- a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.metadata.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -// 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. -export interface Metadata { - updateTimestamp: string; - createdBy: string; - uid: string; - creationTimestamp: string; - deletionTimestamp?: string; - finalizers: string[]; - resourceVersion: string; - generation: number; - updatedBy: string; - labels: Record; -} - -export const defaultMetadata = (): Metadata => ({ - updateTimestamp: "", - createdBy: "", - uid: "", - creationTimestamp: "", - finalizers: [], - resourceVersion: "", - generation: 0, - updatedBy: "", - labels: {}, -}); - diff --git a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.spec.gen.ts b/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.spec.gen.ts deleted file mode 100644 index c46622cd502..00000000000 --- a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.spec.gen.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -// Person represents a user profile with basic information -export interface Person { - // Unique identifier for the user - uid: string; - // Display name of the user - name: string; - // URL to user's Gravatar image - gravatarUrl: string; -} - -export const defaultPerson = (): Person => ({ - uid: "", - name: "", - gravatarUrl: "", -}); - -// Collectable represents an item collected during investigation -export interface Collectable { - id: string; - createdAt: string; - title: string; - origin: string; - type: string; - // +listType=atomic - queries: string[]; - timeRange: TimeRange; - datasource: DatasourceRef; - url: string; - logoPath?: string; - note: string; - noteUpdatedAt: string; - fieldConfig: string; -} - -export const defaultCollectable = (): Collectable => ({ - id: "", - createdAt: "", - title: "", - origin: "", - type: "", - queries: [], - timeRange: defaultTimeRange(), - datasource: defaultDatasourceRef(), - url: "", - note: "", - noteUpdatedAt: "", - fieldConfig: "", -}); - -// TimeRange represents a time range with both absolute and relative values -export interface TimeRange { - from: string; - to: string; - raw: { - from: string; - to: string; - }; -} - -export const defaultTimeRange = (): TimeRange => ({ - from: "", - to: "", - raw: { - from: "", - to: "", -}, -}); - -// DatasourceRef is a reference to a datasource -export interface DatasourceRef { - uid: string; -} - -export const defaultDatasourceRef = (): DatasourceRef => ({ - uid: "", -}); - -export interface ViewMode { - mode: "compact" | "full"; - showComments: boolean; - showTooltips: boolean; -} - -export const defaultViewMode = (): ViewMode => ({ - mode: "compact", - showComments: false, - showTooltips: false, -}); - -// spec is the schema of our resource -export interface Spec { - title: string; - createdByProfile: Person; - hasCustomName: boolean; - isFavorite: boolean; - overviewNote: string; - overviewNoteUpdatedAt: string; - // +listType=atomic - collectables: Collectable[]; - viewMode: ViewMode; -} - -export const defaultSpec = (): Spec => ({ - title: "", - createdByProfile: defaultPerson(), - hasCustomName: false, - isFavorite: false, - overviewNote: "", - overviewNoteUpdatedAt: "", - collectables: [], - viewMode: defaultViewMode(), -}); - diff --git a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.status.gen.ts b/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.status.gen.ts deleted file mode 100644 index 01be8df7961..00000000000 --- a/apps/investigations/plugin/src/generated/investigation/v0alpha1/types.status.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -export interface OperatorState { - // lastEvaluation is the ResourceVersion last evaluated - lastEvaluation: string; - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - state: "success" | "in_progress" | "failed"; - // descriptiveState is an optional more descriptive state field which has no requirements on format - descriptiveState?: string; - // details contains any extra information that is operator-specific - details?: Record; -} - -export const defaultOperatorState = (): OperatorState => ({ - lastEvaluation: "", - state: "success", -}); - -export interface Status { - // 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?: Record; - // additionalFields is reserved for future use - additionalFields?: Record; -} - -export const defaultStatus = (): Status => ({ -}); - diff --git a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/investigationindex_object_gen.ts b/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/investigationindex_object_gen.ts deleted file mode 100644 index 7d88430d451..00000000000 --- a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/investigationindex_object_gen.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file was generated by grafana-app-sdk. DO NOT EDIT. - */ -import { Spec } from './types.spec.gen'; -import { Status } from './types.status.gen'; - -export interface Metadata { - name: string; - namespace: string; - generateName?: string; - selfLink?: string; - uid?: string; - resourceVersion?: string; - generation?: number; - creationTimestamp?: string; - deletionTimestamp?: string; - deletionGracePeriodSeconds?: number; - labels?: Record; - annotations?: Record; - ownerReferences?: OwnerReference[]; - finalizers?: string[]; - managedFields?: ManagedFieldsEntry[]; -} - -export interface OwnerReference { - apiVersion: string; - kind: string; - name: string; - uid: string; - controller?: boolean; - blockOwnerDeletion?: boolean; -} - -export interface ManagedFieldsEntry { - manager?: string; - operation?: string; - apiVersion?: string; - time?: string; - fieldsType?: string; - subresource?: string; -} - -export interface InvestigationIndex { - kind: string; - apiVersion: string; - metadata: Metadata; - spec: Spec; - status: Status; -} diff --git a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.metadata.gen.ts b/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.metadata.gen.ts deleted file mode 100644 index 4377f3c1d08..00000000000 --- a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.metadata.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -// 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. -export interface Metadata { - updateTimestamp: string; - createdBy: string; - uid: string; - creationTimestamp: string; - deletionTimestamp?: string; - finalizers: string[]; - resourceVersion: string; - generation: number; - updatedBy: string; - labels: Record; -} - -export const defaultMetadata = (): Metadata => ({ - updateTimestamp: "", - createdBy: "", - uid: "", - creationTimestamp: "", - finalizers: [], - resourceVersion: "", - generation: 0, - updatedBy: "", - labels: {}, -}); - diff --git a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.spec.gen.ts b/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.spec.gen.ts deleted file mode 100644 index f0d2f25d96b..00000000000 --- a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.spec.gen.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -// Person represents a user profile with basic information -export interface Person { - // Unique identifier for the user - uid: string; - // Display name of the user - name: string; - // URL to user's Gravatar image - gravatarUrl: string; -} - -export const defaultPerson = (): Person => ({ - uid: "", - name: "", - gravatarUrl: "", -}); - -// Type definition for investigation summaries -export interface InvestigationSummary { - title: string; - createdByProfile: Person; - hasCustomName: boolean; - isFavorite: boolean; - overviewNote: string; - overviewNoteUpdatedAt: string; - viewMode: ViewMode; - // +listType=atomic - collectableSummaries: CollectableSummary[]; -} - -export const defaultInvestigationSummary = (): InvestigationSummary => ({ - title: "", - createdByProfile: defaultPerson(), - hasCustomName: false, - isFavorite: false, - overviewNote: "", - overviewNoteUpdatedAt: "", - viewMode: defaultViewMode(), - collectableSummaries: [], -}); - -export interface ViewMode { - mode: "compact" | "full"; - showComments: boolean; - showTooltips: boolean; -} - -export const defaultViewMode = (): ViewMode => ({ - mode: "compact", - showComments: false, - showTooltips: false, -}); - -export interface CollectableSummary { - id: string; - title: string; - logoPath: string; - origin: string; -} - -export const defaultCollectableSummary = (): CollectableSummary => ({ - id: "", - title: "", - logoPath: "", - origin: "", -}); - -export interface Spec { - // Title of the index, e.g. 'Favorites' or 'My Investigations' - title: string; - // The Person who owns this investigation index - owner: Person; - // Array of investigation summaries - // +listType=atomic - investigationSummaries: InvestigationSummary[]; -} - -export const defaultSpec = (): Spec => ({ - title: "", - owner: defaultPerson(), - investigationSummaries: [], -}); - diff --git a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.status.gen.ts b/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.status.gen.ts deleted file mode 100644 index 01be8df7961..00000000000 --- a/apps/investigations/plugin/src/generated/investigationindex/v0alpha1/types.status.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -export interface OperatorState { - // lastEvaluation is the ResourceVersion last evaluated - lastEvaluation: string; - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - state: "success" | "in_progress" | "failed"; - // descriptiveState is an optional more descriptive state field which has no requirements on format - descriptiveState?: string; - // details contains any extra information that is operator-specific - details?: Record; -} - -export const defaultOperatorState = (): OperatorState => ({ - lastEvaluation: "", - state: "success", -}); - -export interface Status { - // 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?: Record; - // additionalFields is reserved for future use - additionalFields?: Record; -} - -export const defaultStatus = (): Status => ({ -}); - diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go index 228523f598e..f9f8dcc8382 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go @@ -116,3 +116,26 @@ type ConnectionList struct { // +listType=atomic Items []Connection `json:"items"` } + +// ExternalRepositoryList lists repositories from an external git provider +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalRepositoryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + // +listType=atomic + Items []ExternalRepository `json:"items"` +} + +type ExternalRepository struct { + // Name of the repository + Name string `json:"name"` + // Owner is the user, organization, or workspace that owns the repository + // For GitHub: organization or user + // For GitLab: namespace (user or group) + // For Bitbucket: workspace + // For pure Git: empty + Owner string `json:"owner,omitempty"` + // URL of the repository + URL string `json:"url"` +} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go index f06798c0ddd..c1785a048d1 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go @@ -197,6 +197,7 @@ func AddKnownTypes(gv schema.GroupVersion, scheme *runtime.Scheme) error { &HistoricJobList{}, &Connection{}, &ConnectionList{}, + &ExternalRepositoryList{}, ) return nil } diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 48d03d23f99..97b1e752f68 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -262,6 +262,53 @@ func (in *ExportJobOptions) DeepCopy() *ExportJobOptions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalRepository) DeepCopyInto(out *ExternalRepository) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalRepository. +func (in *ExternalRepository) DeepCopy() *ExternalRepository { + if in == nil { + return nil + } + out := new(ExternalRepository) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalRepositoryList) DeepCopyInto(out *ExternalRepositoryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ExternalRepository, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalRepositoryList. +func (in *ExternalRepositoryList) DeepCopy() *ExternalRepositoryList { + if in == nil { + return nil + } + out := new(ExternalRepositoryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalRepositoryList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileItem) DeepCopyInto(out *FileItem) { *out = *in diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index a96fa2d8a6b..11788142e94 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -26,6 +26,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExternalRepository": schema_pkg_apis_provisioning_v0alpha1_ExternalRepository(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExternalRepositoryList": schema_pkg_apis_provisioning_v0alpha1_ExternalRepositoryList(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubConnectionConfig(ref), @@ -544,6 +546,96 @@ func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.Reference } } +func schema_pkg_apis_provisioning_v0alpha1_ExternalRepository(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{ + Description: "Name of the repository", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "owner": { + SchemaProps: spec.SchemaProps{ + Description: "Owner is the user, organization, or workspace that owns the repository For GitHub: organization or user For GitLab: namespace (user or group) For Bitbucket: workspace For pure Git: empty", + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL of the repository", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "url"}, + }, + }, + } +} + +func schema_pkg_apis_provisioning_v0alpha1_ExternalRepositoryList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ExternalRepositoryList lists repositories from an external git provider", + 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": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + 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/provisioning/pkg/apis/provisioning/v0alpha1.ExternalRepository"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExternalRepository", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + func schema_pkg_apis_provisioning_v0alpha1_FileItem(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index b9504855b80..3a54dcf2a5e 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,6 +1,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources +API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ExternalRepositoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,HistoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors diff --git a/docs/sources/datasources/graphite/_index.md b/docs/sources/datasources/graphite/_index.md index fba9d616f38..78972af5781 100644 --- a/docs/sources/datasources/graphite/_index.md +++ b/docs/sources/datasources/graphite/_index.md @@ -111,3 +111,4 @@ After installing and configuring the Graphite data source you can: - Add [transformations](ref:transformations) - Add [annotations](ref:annotate-visualizations) - Set up [alerting](ref:alerting) +- [Troubleshoot](troubleshooting/) common issues with the Graphite data source diff --git a/docs/sources/datasources/graphite/troubleshooting/index.md b/docs/sources/datasources/graphite/troubleshooting/index.md new file mode 100644 index 00000000000..41702c6b975 --- /dev/null +++ b/docs/sources/datasources/graphite/troubleshooting/index.md @@ -0,0 +1,174 @@ +--- +description: Troubleshoot common issues with the Graphite data source. +keywords: + - grafana + - graphite + - troubleshooting + - guide +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Troubleshooting +title: Troubleshoot Graphite data source issues +weight: 400 +refs: + configure-graphite: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/graphite/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/graphite/configure/ + query-editor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/graphite/query-editor/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/graphite/query-editor/ +--- + +# Troubleshoot Graphite data source issues + +This document provides solutions for common issues you might encounter when using the Graphite data source. + +## Connection issues + +Use the following troubleshooting steps to resolve connection problems between Grafana and your Graphite server. + +**Data source test fails with "Unable to connect":** + +If the data source test fails, verify the following: + +- The URL in your data source configuration is correct and accessible from the Grafana server. +- The Graphite server is running and accepting connections. +- Any firewall rules or network policies allow traffic between Grafana and the Graphite server. +- If using TLS, ensure your certificates are valid and properly configured. + +To test connectivity, run the following command from the Grafana server: + +```sh +curl -v /render +``` + +Replace _``_ with your Graphite server URL. A successful connection returns a response from the Graphite server. + +**Authentication errors:** + +If you receive 401 or 403 errors: + +- Verify your Basic Auth username and password are correct. +- Ensure the **With Credentials** toggle is enabled if your Graphite server requires cookies for authentication. +- Check that your TLS client certificates are valid and match what the server expects. + +For detailed authentication configuration, refer to [Configure the Graphite data source](ref:configure-graphite). + +## Query issues + +Use the following troubleshooting steps to resolve problems with Graphite queries. + +**No data returned:** + +If your query returns no data: + +- Verify the metric path exists in your Graphite server by testing directly in the Graphite web interface. +- Check that the time range in Grafana matches when data was collected. +- Ensure wildcards in your query match existing metrics. +- Confirm your query syntax is correct for your Graphite version. + +**HTTP 500 errors with HTML content:** + +Graphite-web versions before 1.6 return HTTP 500 errors with full HTML stack traces when a query fails. If you see error messages containing HTML tags: + +- Check the Graphite server logs for the full error details. +- Verify your query syntax is valid. +- Ensure the requested time range doesn't exceed your Graphite server's capabilities. +- Check that all functions used in your query are supported by your Graphite version. + +**Parser errors in the query editor:** + +If the query editor displays parser errors: + +- Check for unbalanced parentheses in function calls. +- Verify that function arguments are in the correct format. +- Ensure metric paths don't contain unsupported characters. + +For query syntax help, refer to [Graphite query editor](ref:query-editor). + +## Version and feature issues + +Use the following troubleshooting steps to resolve problems related to Graphite versions and features. + +**Functions missing from the query editor:** + +If expected functions don't appear in the query editor: + +- Verify the correct Graphite version is selected in the data source configuration. +- The available functions depend on the configured version. For example, tag-based functions require Graphite 1.1 or later. +- If using a custom Graphite installation with additional functions, ensure the version setting matches your server. + +**Tag-based queries not working:** + +If `seriesByTag()` or other tag functions fail: + +- Confirm your Graphite server is version 1.1 or later. +- Verify the Graphite version setting in your data source configuration matches your actual server version. +- Check that tags are properly configured in your Graphite server. + +## Performance issues + +Use the following troubleshooting steps to address slow queries or timeouts. + +**Queries timing out:** + +If queries consistently time out: + +- Increase the **Timeout** setting in the data source configuration. +- Reduce the time range of your query. +- Use more specific metric paths instead of broad wildcards. +- Consider using `summarize()` or `consolidateBy()` functions to reduce the amount of data returned. +- Check your Graphite server's performance and resource utilization. + +**Slow autocomplete in the query editor:** + +If metric path autocomplete is slow: + +- This often indicates a large number of metrics in your Graphite server. +- Use more specific path prefixes to narrow the search scope. +- Check your Graphite server's index performance. + +## MetricTank-specific issues + +If you're using MetricTank as your Graphite backend, use the following troubleshooting steps. + +**Rollup indicator not appearing:** + +If the rollup indicator doesn't display when expected: + +- Verify **Metrictank** is selected as the Graphite backend type in the data source configuration. +- Ensure the **Rollup indicator** toggle is enabled. +- The indicator only appears when data aggregation actually occurs. + +**Unexpected data aggregation:** + +If you see unexpected aggregation in your data: + +- Check the rollup configuration in your MetricTank instance. +- Adjust the time range or use `consolidateBy()` to control aggregation behavior. +- Review the query processing metadata in the panel inspector for details on how data was processed. + +## Get additional help + +If you continue to experience issues: + +- Check the [Grafana community forums](https://community.grafana.com/) for similar issues and solutions. +- Review the [Graphite documentation](https://graphite.readthedocs.io/) for additional configuration options. +- Contact [Grafana Support](https://grafana.com/support/) if you're an Enterprise, Cloud Pro, or Cloud Advanced customer. + +When reporting issues, include the following information: + +- Grafana version +- Graphite version (for example, 1.1.x) and backend type (Default or MetricTank) +- Authentication method (Basic Auth, TLS, or none) +- Error messages (redact sensitive information) +- Steps to reproduce the issue +- Relevant configuration such as data source settings, timeout values, and Graphite version setting (redact passwords and other credentials) +- Sample query (if applicable, with sensitive data redacted) diff --git a/docs/sources/setup-grafana/configure-access/configure-authentication/anonymous-auth/index.md b/docs/sources/setup-grafana/configure-access/configure-authentication/anonymous-auth/index.md index 77df611f904..27c65e7bd02 100644 --- a/docs/sources/setup-grafana/configure-access/configure-authentication/anonymous-auth/index.md +++ b/docs/sources/setup-grafana/configure-access/configure-authentication/anonymous-auth/index.md @@ -38,13 +38,6 @@ Users can now view anonymous usage statistics, including the count of devices an The number of anonymous devices is not limited by default. The configuration option `device_limit` allows you to enforce a limit on the number of anonymous devices. This enables you to have greater control over the usage within your Grafana instance and keep the usage within the limits of your environment. Once the limit is reached, any new devices that try to access Grafana will be denied access. -To display anonymous users and devices for versions 10.2, 10.3, 10.4, you need to enable the feature toggle `displayAnonymousStats` - -```bash -[feature_toggles] -enable = displayAnonymousStats -``` - ## Configuration Example: @@ -67,3 +60,15 @@ device_limit = ``` If you change your organization name in the Grafana UI this setting needs to be updated to match the new name. + +## Licensing for anonymous access + +Grafana Enterprise (self-managed) licenses anonymous access as active users. + +Anonymous access lets people use Grafana without login credentials. It was an early way to share dashboards, but Public dashboards gives you a more secure way to share dashboards. + +### How anonymous usage is counted + +Grafana estimates anonymous active users from anonymous devices: + +- **Counting rule**: Grafana counts 1 anonymous user for every 3 anonymous devices detected. diff --git a/go.mod b/go.mod index 4d36534519e..285226a0cc6 100644 --- a/go.mod +++ b/go.mod @@ -250,7 +250,6 @@ require ( github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b // @grafana/grafana-app-platform-squad github.com/grafana/grafana/apps/folder v0.0.0 // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/iam v0.0.0 // @grafana/identity-access-team - github.com/grafana/grafana/apps/investigations v0.0.0 // @fcjack @matryer github.com/grafana/grafana/apps/logsdrilldown v0.0.0 // @grafana/observability-logs github.com/grafana/grafana/apps/playlist v0.0.0 // @grafana/grafana-app-platform-squad github.com/grafana/grafana/apps/plugins v0.0.0 // @grafana/plugins-platform-backend @@ -284,7 +283,6 @@ replace ( github.com/grafana/grafana/apps/dashboard => ./apps/dashboard github.com/grafana/grafana/apps/folder => ./apps/folder github.com/grafana/grafana/apps/iam => ./apps/iam - github.com/grafana/grafana/apps/investigations => ./apps/investigations github.com/grafana/grafana/apps/logsdrilldown => ./apps/logsdrilldown github.com/grafana/grafana/apps/playlist => ./apps/playlist github.com/grafana/grafana/apps/plugins => ./apps/plugins diff --git a/go.work b/go.work index 884f393673e..0734a34509d 100644 --- a/go.work +++ b/go.work @@ -17,7 +17,6 @@ use ( ./apps/example ./apps/folder ./apps/iam - ./apps/investigations ./apps/logsdrilldown ./apps/playlist ./apps/plugins 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 index 4f17bb46ecf..a0af41ef893 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -5554,6 +5554,7 @@ export type ReportDashboard = { }; export type Type = string; export type ReportOptions = { + csvEncoding?: string; layout?: string; orientation?: string; pdfCombineOneFile?: boolean; 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 be22503f3bf..b6519295c66 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 @@ -122,6 +122,10 @@ const injectedRtkApi = api }), invalidatesTags: ['Connection'], }), + getConnectionRepositories: build.query({ + query: (queryArg) => ({ url: `/connections/${queryArg.name}/repositories` }), + providesTags: ['Connection'], + }), getConnectionStatus: build.query({ query: (queryArg) => ({ url: `/connections/${queryArg.name}/status`, @@ -726,6 +730,18 @@ export type UpdateConnectionApiArg = { force?: boolean; patch: Patch; }; +export type GetConnectionRepositoriesApiResponse = /** 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; + items: 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; + metadata?: any; +}; +export type GetConnectionRepositoriesApiArg = { + /** name of the ExternalRepositoryList */ + name: string; +}; export type GetConnectionStatusApiResponse = /** status 200 OK */ Connection; export type GetConnectionStatusApiArg = { /** name of the Connection */ @@ -2079,6 +2095,8 @@ export const { useReplaceConnectionMutation, useDeleteConnectionMutation, useUpdateConnectionMutation, + useGetConnectionRepositoriesQuery, + useLazyGetConnectionRepositoriesQuery, useGetConnectionStatusQuery, useLazyGetConnectionStatusQuery, useReplaceConnectionStatusMutation, diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 5d9ad02dbc7..6e35e460055 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -207,6 +207,10 @@ export interface FeatureToggles { */ reportingRetries?: boolean; /** + * Enables CSV encoding options in the reporting feature + */ + reportingCsvEncodingOptions?: boolean; + /** * Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch. */ sseGroupByDatasource?: boolean; @@ -778,20 +782,11 @@ export interface FeatureToggles { */ elasticsearchCrossClusterSearch?: boolean; /** - * Displays the navigation history so the user can navigate back to previous pages - */ - unifiedHistory?: boolean; - /** * Defaults to using the Loki `/labels` API instead of `/series` * @default true */ lokiLabelNamesQueryApi?: boolean; /** - * Enable the investigations backend API - * @default false - */ - investigationsBackend?: boolean; - /** * Enable folder's api server counts * @default false */ diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0b7ffa04ea4..0fa8b7d7115 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -224,7 +224,7 @@ func (a *dashboardSqlAccess) CountResources(ctx context.Context, opts MigrateOpt case "folder.grafana.app/folders": summary := &resourcepb.BulkResponse_Summary{} summary.Group = folders.GROUP - summary.Group = folders.RESOURCE + summary.Resource = folders.RESOURCE _, err = sess.SQL("SELECT COUNT(*) FROM "+sql.Table("dashboard")+ " WHERE is_folder=TRUE AND org_id=?", orgId).Get(&summary.Count) rsp.Summary = append(rsp.Summary, summary) diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 8abc67bf885..efcf6fa5b39 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -53,7 +53,7 @@ func newIAMAuthorizer( resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer - resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = allowAuthorizer resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer resourceAuthorizer["searchUsers"] = serviceAuthorizer resourceAuthorizer["searchTeams"] = serviceAuthorizer diff --git a/pkg/registry/apis/iam/authorizer/external_group_mapping.go b/pkg/registry/apis/iam/authorizer/external_group_mapping.go new file mode 100644 index 00000000000..0537acbe81d --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/external_group_mapping.go @@ -0,0 +1,150 @@ +package authorizer + +import ( + "context" + "fmt" + + "github.com/grafana/authlib/types" + "k8s.io/apimachinery/pkg/runtime" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +type ExternalGroupMappingAuthorizer struct { + accessClient types.AccessClient +} + +var _ storewrapper.ResourceStorageAuthorizer = (*ExternalGroupMappingAuthorizer)(nil) + +func NewExternalGroupMappingAuthorizer( + accessClient types.AccessClient, +) *ExternalGroupMappingAuthorizer { + return &ExternalGroupMappingAuthorizer{ + accessClient: accessClient, + } +} + +// AfterGet implements ResourceStorageAuthorizer. +func (r *ExternalGroupMappingAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return storewrapper.ErrUnauthenticated + } + + concreteObj, ok := obj.(*iamv0.ExternalGroupMapping) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMapping, got %T: %w", obj, storewrapper.ErrUnexpectedType)) + } + + teamName := concreteObj.Spec.TeamRef.Name + checkReq := types.CheckRequest{ + Namespace: authInfo.GetNamespace(), + Group: iamv0.GROUP, + Resource: iamv0.TeamResourceInfo.GetName(), + Verb: utils.VerbGetPermissions, + Name: teamName, + } + res, err := r.accessClient.Check(ctx, authInfo, checkReq, "") + if err != nil { + return apierrors.NewInternalError(err) + } + + if !res.Allowed { + return apierrors.NewForbidden( + iamv0.ExternalGroupMappingResourceInfo.GroupResource(), + concreteObj.Name, + fmt.Errorf("user cannot access team %s", teamName), + ) + } + return nil +} + +// BeforeCreate implements ResourceStorageAuthorizer. +func (r *ExternalGroupMappingAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error { + return r.beforeWrite(ctx, obj) +} + +// BeforeDelete implements ResourceStorageAuthorizer. +func (r *ExternalGroupMappingAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error { + return r.beforeWrite(ctx, obj) +} + +// BeforeUpdate implements ResourceStorageAuthorizer. +func (r *ExternalGroupMappingAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error { + // Update is not supported for ExternalGroupMapping resources and update attempts are blocked at a lower level, + // so this is just a safeguard. + return apierrors.NewMethodNotSupported(iamv0.ExternalGroupMappingResourceInfo.GroupResource(), "PUT/PATCH") +} + +func (r *ExternalGroupMappingAuthorizer) beforeWrite(ctx context.Context, obj runtime.Object) error { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return storewrapper.ErrUnauthenticated + } + + concreteObj, ok := obj.(*iamv0.ExternalGroupMapping) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMapping, got %T: %w", obj, storewrapper.ErrUnexpectedType)) + } + + teamName := concreteObj.Spec.TeamRef.Name + checkReq := types.CheckRequest{ + Namespace: authInfo.GetNamespace(), + Group: iamv0.GROUP, + Resource: iamv0.TeamResourceInfo.GetName(), + Verb: utils.VerbSetPermissions, + Name: teamName, + } + + res, err := r.accessClient.Check(ctx, authInfo, checkReq, "") + if err != nil { + return apierrors.NewInternalError(err) + } + + if !res.Allowed { + return apierrors.NewForbidden( + iamv0.ExternalGroupMappingResourceInfo.GroupResource(), + concreteObj.Name, + fmt.Errorf("user cannot write team %s", teamName), + ) + } + return nil +} + +// FilterList implements ResourceStorageAuthorizer. +func (r *ExternalGroupMappingAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return nil, storewrapper.ErrUnauthenticated + } + + l, ok := list.(*iamv0.ExternalGroupMappingList) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMappingList, got %T: %w", list, storewrapper.ErrUnexpectedType)) + } + + var filteredItems []iamv0.ExternalGroupMapping + + listReq := types.ListRequest{ + Namespace: authInfo.GetNamespace(), + Group: iamv0.GROUP, + Resource: iamv0.TeamResourceInfo.GetName(), + Verb: utils.VerbGetPermissions, + } + canView, _, err := r.accessClient.Compile(ctx, authInfo, listReq) + if err != nil { + return nil, apierrors.NewInternalError(err) + } + + for _, item := range l.Items { + if canView(item.Spec.TeamRef.Name, "") { + filteredItems = append(filteredItems, item) + } + } + + l.Items = filteredItems + return l, nil +} diff --git a/pkg/registry/apis/iam/authorizer/external_group_mapping_test.go b/pkg/registry/apis/iam/authorizer/external_group_mapping_test.go new file mode 100644 index 00000000000..69ee7e5fed9 --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/external_group_mapping_test.go @@ -0,0 +1,229 @@ +package authorizer + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/authlib/types" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +func newExternalGroupMapping(teamName, name string) *iamv0.ExternalGroupMapping { + return &iamv0.ExternalGroupMapping{ + ObjectMeta: metav1.ObjectMeta{Namespace: "org-2", Name: name}, + Spec: iamv0.ExternalGroupMappingSpec{ + TeamRef: iamv0.ExternalGroupMappingTeamRef{ + Name: teamName, + }, + }, + } +} + +func TestExternalGroupMapping_AfterGet(t *testing.T) { + mapping := newExternalGroupMapping("team-1", "mapping-1") + + tests := []struct { + name string + shouldAllow bool + }{ + { + name: "allow access", + shouldAllow: true, + }, + { + name: "deny access", + shouldAllow: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.NotNil(t, id) + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, iamv0.GROUP, req.Group) + require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource) + require.Equal(t, "team-1", req.Name) + require.Equal(t, utils.VerbGetPermissions, req.Verb) + require.Equal(t, "", folder) + + return types.CheckResponse{Allowed: tt.shouldAllow}, nil + } + + accessClient := &fakeAccessClient{checkFunc: checkFunc} + authz := NewExternalGroupMappingAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := authz.AfterGet(ctx, mapping) + if tt.shouldAllow { + require.NoError(t, err) + } else { + require.Error(t, err) + } + require.True(t, accessClient.checkCalled) + }) + } +} + +func TestExternalGroupMapping_FilterList(t *testing.T) { + list := &iamv0.ExternalGroupMappingList{ + Items: []iamv0.ExternalGroupMapping{ + *newExternalGroupMapping("team-1", "mapping-1"), + *newExternalGroupMapping("team-2", "mapping-2"), + }, + ListMeta: metav1.ListMeta{ + SelfLink: "/apis/iam.grafana.app/v0alpha1/namespaces/org-2/externalgroupmappings", + }, + } + + compileFunc := func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) { + require.NotNil(t, id) + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, iamv0.GROUP, req.Group) + require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource) + require.Equal(t, utils.VerbGetPermissions, req.Verb) + + return func(name, folder string) bool { + return name == "team-1" + }, &types.NoopZookie{}, nil + } + + accessClient := &fakeAccessClient{compileFunc: compileFunc} + authz := NewExternalGroupMappingAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + obj, err := authz.FilterList(ctx, list) + require.NoError(t, err) + require.NotNil(t, list) + require.True(t, accessClient.compileCalled) + + filtered, ok := obj.(*iamv0.ExternalGroupMappingList) + require.True(t, ok) + require.Len(t, filtered.Items, 1) + require.Equal(t, "mapping-1", filtered.Items[0].Name) +} + +func TestExternalGroupMapping_BeforeCreate(t *testing.T) { + mapping := newExternalGroupMapping("team-1", "mapping-1") + + tests := []struct { + name string + shouldAllow bool + }{ + { + name: "allow create", + shouldAllow: true, + }, + { + name: "deny create", + shouldAllow: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.NotNil(t, id) + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, iamv0.GROUP, req.Group) + require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource) + require.Equal(t, "team-1", req.Name) + require.Equal(t, utils.VerbSetPermissions, req.Verb) + require.Equal(t, "", folder) + + return types.CheckResponse{Allowed: tt.shouldAllow}, nil + } + + accessClient := &fakeAccessClient{checkFunc: checkFunc} + authz := NewExternalGroupMappingAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := authz.BeforeCreate(ctx, mapping) + if tt.shouldAllow { + require.NoError(t, err) + } else { + require.Error(t, err) + } + require.True(t, accessClient.checkCalled) + }) + } +} + +func TestExternalGroupMapping_BeforeUpdate(t *testing.T) { + mapping := newExternalGroupMapping("team-1", "mapping-1") + + accessClient := &fakeAccessClient{ + checkFunc: func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.Fail(t, "check should not be called") + return types.CheckResponse{}, nil + }, + } + authz := NewExternalGroupMappingAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := authz.BeforeUpdate(ctx, mapping) + require.Error(t, err) + require.True(t, apierrors.IsMethodNotSupported(err)) + require.Contains(t, err.Error(), "PUT/PATCH") + require.False(t, accessClient.checkCalled) +} + +func TestExternalGroupMapping_BeforeDelete(t *testing.T) { + mapping := newExternalGroupMapping("team-1", "mapping-1") + + tests := []struct { + name string + shouldAllow bool + }{ + { + name: "allow delete", + shouldAllow: true, + }, + { + name: "deny delete", + shouldAllow: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.NotNil(t, id) + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, iamv0.GROUP, req.Group) + require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource) + require.Equal(t, "team-1", req.Name) + require.Equal(t, utils.VerbSetPermissions, req.Verb) + require.Equal(t, "", folder) + + return types.CheckResponse{Allowed: tt.shouldAllow}, nil + } + + accessClient := &fakeAccessClient{checkFunc: checkFunc} + authz := NewExternalGroupMappingAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := authz.BeforeDelete(ctx, mapping) + if tt.shouldAllow { + require.NoError(t, err) + } else { + require.Error(t, err) + } + require.True(t, accessClient.checkCalled) + }) + } +} diff --git a/pkg/registry/apis/iam/authorizer/resource_permissions_test.go b/pkg/registry/apis/iam/authorizer/resource_permissions_test.go index df777ee31e9..9e12bd87ebf 100644 --- a/pkg/registry/apis/iam/authorizer/resource_permissions_test.go +++ b/pkg/registry/apis/iam/authorizer/resource_permissions_test.go @@ -4,35 +4,15 @@ import ( "context" "testing" - "github.com/go-jose/go-jose/v4/jwt" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/grafana/authlib/authn" "github.com/grafana/authlib/types" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) -var ( - user = authn.NewIDTokenAuthInfo( - authn.Claims[authn.AccessTokenClaims]{ - Claims: jwt.Claims{Issuer: "grafana", - Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"), Audience: []string{"iam.grafana.app"}}, - Rest: authn.AccessTokenClaims{ - Namespace: "*", - Permissions: identity.ServiceIdentityClaims.Rest.Permissions, - DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions, - }, - }, &authn.Claims[authn.IDTokenClaims]{ - Claims: jwt.Claims{Subject: types.NewTypeID(types.TypeUser, "u001")}, - Rest: authn.IDTokenClaims{Namespace: "org-2", Identifier: "u001", Type: types.TypeUser}, - }, - ) -) - func newResourcePermission(apiGroup, resource, name string) *iamv0.ResourcePermission { return &iamv0.ResourcePermission{ ObjectMeta: metav1.ObjectMeta{Namespace: "org-2"}, @@ -222,26 +202,6 @@ func TestResourcePermissions_beforeWrite(t *testing.T) { } } -// fakeAccessClient is a mock implementation of claims.AccessClient -type fakeAccessClient struct { - checkCalled bool - checkFunc func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) - compileCalled bool - compileFunc func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) -} - -func (m *fakeAccessClient) Check(ctx context.Context, id types.AuthInfo, req types.CheckRequest, folder string) (types.CheckResponse, error) { - m.checkCalled = true - return m.checkFunc(id, &req, folder) -} - -func (m *fakeAccessClient) Compile(ctx context.Context, id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) { - m.compileCalled = true - return m.compileFunc(id, req) -} - -var _ types.AccessClient = (*fakeAccessClient)(nil) - type fakeParentProvider struct { hasParent bool getParentCalled bool diff --git a/pkg/registry/apis/iam/authorizer/testutil.go b/pkg/registry/apis/iam/authorizer/testutil.go new file mode 100644 index 00000000000..37e40596583 --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/testutil.go @@ -0,0 +1,48 @@ +package authorizer + +import ( + "context" + + "github.com/go-jose/go-jose/v4/jwt" + "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +var ( + // Shared test user identity + user = authn.NewIDTokenAuthInfo( + authn.Claims[authn.AccessTokenClaims]{ + Claims: jwt.Claims{Issuer: "grafana", + Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"), Audience: []string{"iam.grafana.app"}}, + Rest: authn.AccessTokenClaims{ + Namespace: "*", + Permissions: identity.ServiceIdentityClaims.Rest.Permissions, + DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions, + }, + }, &authn.Claims[authn.IDTokenClaims]{ + Claims: jwt.Claims{Subject: types.NewTypeID(types.TypeUser, "u001")}, + Rest: authn.IDTokenClaims{Namespace: "org-2", Identifier: "u001", Type: types.TypeUser}, + }, + ) +) + +var _ types.AccessClient = (*fakeAccessClient)(nil) + +// fakeAccessClient is a mock implementation of claims.AccessClient +type fakeAccessClient struct { + checkCalled bool + checkFunc func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) + compileCalled bool + compileFunc func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) +} + +func (m *fakeAccessClient) Check(ctx context.Context, id types.AuthInfo, req types.CheckRequest, folder string) (types.CheckResponse, error) { + m.checkCalled = true + return m.checkFunc(id, &req, folder) +} + +func (m *fakeAccessClient) Compile(ctx context.Context, id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) { + m.compileCalled = true + return m.compileFunc(id, req) +} diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 8a3dcb9586b..ea1b1225f41 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -353,7 +353,8 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge if err != nil { return err } - storage[extGroupMappingResource.StoragePath()] = extGroupMappingUniStore + + var extGroupMappingStore storewrapper.K8sStorage = extGroupMappingUniStore if b.externalGroupMappingStorage != nil { extGroupMappingLegacyStore, err := NewLocalStore(extGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage) @@ -365,9 +366,17 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge if err != nil { return err } - storage[extGroupMappingResource.StoragePath()] = dw + + var ok bool + extGroupMappingStore, ok = dw.(storewrapper.K8sStorage) + if !ok { + return fmt.Errorf("expected storewrapper.K8sStorage, got %T", dw) + } } + authzWrapper := storewrapper.New(extGroupMappingStore, iamauthorizer.NewExternalGroupMappingAuthorizer(b.accessClient)) + storage[extGroupMappingResource.StoragePath()] = authzWrapper + //nolint:staticcheck // not yet migrated to OpenFeature if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis) { // v0alpha1 diff --git a/pkg/registry/apis/provisioning/connection_repositories.go b/pkg/registry/apis/provisioning/connection_repositories.go new file mode 100644 index 00000000000..c2e908fc685 --- /dev/null +++ b/pkg/registry/apis/provisioning/connection_repositories.go @@ -0,0 +1,69 @@ +package provisioning + +import ( + "context" + "net/http" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/grafana/grafana-app-sdk/logging" + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +type connectionRepositoriesConnector struct{} + +func NewConnectionRepositoriesConnector() *connectionRepositoriesConnector { + return &connectionRepositoriesConnector{} +} + +func (*connectionRepositoriesConnector) New() runtime.Object { + return &provisioning.ExternalRepositoryList{} +} + +func (*connectionRepositoriesConnector) Destroy() {} + +func (*connectionRepositoriesConnector) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} +} + +func (*connectionRepositoriesConnector) ProducesObject(verb string) any { + return &provisioning.ExternalRepositoryList{} +} + +func (*connectionRepositoriesConnector) ConnectMethods() []string { + return []string{http.MethodGet} +} + +func (*connectionRepositoriesConnector) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" +} + +func (c *connectionRepositoriesConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + logger := logging.FromContext(ctx).With("logger", "connection-repositories-connector", "connection_name", name) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + responder.Error(apierrors.NewMethodNotSupported(provisioning.ConnectionResourceInfo.GroupResource(), r.Method)) + return + } + + logger.Debug("repositories endpoint called but not yet implemented") + + // TODO: Implement repository listing from external git provider + // This will require: + // 1. Get the Connection object using logging.Context(r.Context(), logger) + // 2. Use the connection credentials to authenticate with the git provider + // 3. List repositories from the provider (GitHub, GitLab, Bitbucket) + // 4. Return ExternalRepositoryList with Name, Owner, and URL for each repository + + responder.Error(apierrors.NewMethodNotSupported(provisioning.ConnectionResourceInfo.GroupResource(), "repositories endpoint not yet implemented")) + }), nil +} + +var ( + _ rest.Storage = (*connectionRepositoriesConnector)(nil) + _ rest.Connecter = (*connectionRepositoriesConnector)(nil) + _ rest.StorageMetadata = (*connectionRepositoriesConnector)(nil) +) diff --git a/pkg/registry/apis/provisioning/connection_repositories_test.go b/pkg/registry/apis/provisioning/connection_repositories_test.go new file mode 100644 index 00000000000..792eb52bb64 --- /dev/null +++ b/pkg/registry/apis/provisioning/connection_repositories_test.go @@ -0,0 +1,101 @@ +package provisioning + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +func TestConnectionRepositoriesConnector(t *testing.T) { + connector := NewConnectionRepositoriesConnector() + + t.Run("New returns ExternalRepositoryList", func(t *testing.T) { + obj := connector.New() + require.IsType(t, &provisioning.ExternalRepositoryList{}, obj) + }) + + t.Run("ProducesMIMETypes returns application/json", func(t *testing.T) { + types := connector.ProducesMIMETypes("GET") + require.Equal(t, []string{"application/json"}, types) + }) + + t.Run("ProducesObject returns ExternalRepositoryList", func(t *testing.T) { + obj := connector.ProducesObject("GET") + require.IsType(t, &provisioning.ExternalRepositoryList{}, obj) + }) + + t.Run("ConnectMethods returns GET", func(t *testing.T) { + methods := connector.ConnectMethods() + require.Equal(t, []string{http.MethodGet}, methods) + }) + + t.Run("NewConnectOptions returns no path component", func(t *testing.T) { + obj, hasPath, path := connector.NewConnectOptions() + require.Nil(t, obj) + require.False(t, hasPath) + require.Empty(t, path) + }) + + t.Run("Connect returns handler that rejects non-GET methods", func(t *testing.T) { + ctx := context.Background() + responder := &mockResponder{} + + handler, err := connector.Connect(ctx, "test-connection", nil, responder) + require.NoError(t, err) + require.NotNil(t, handler) + + // Test POST method (should be rejected) + req := httptest.NewRequest(http.MethodPost, "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + require.True(t, responder.called) + require.NotNil(t, responder.err) + require.True(t, apierrors.IsMethodNotSupported(responder.err)) + }) + + t.Run("Connect returns handler that returns not implemented for GET", func(t *testing.T) { + ctx := context.Background() + responder := &mockResponder{} + + handler, err := connector.Connect(ctx, "test-connection", nil, responder) + require.NoError(t, err) + require.NotNil(t, handler) + + // Test GET method (should return not implemented) + req := httptest.NewRequest(http.MethodGet, "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + require.True(t, responder.called) + require.NotNil(t, responder.err) + require.True(t, apierrors.IsMethodNotSupported(responder.err)) + require.Contains(t, responder.err.Error(), "not yet implemented") + }) +} + +// mockResponder implements rest.Responder for testing +type mockResponder struct { + called bool + err error + obj runtime.Object + code int +} + +func (m *mockResponder) Object(statusCode int, obj runtime.Object) { + m.called = true + m.code = statusCode + m.obj = obj +} + +func (m *mockResponder) Error(err error) { + m.called = true + m.err = err +} diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 26e97f56b3c..217a1933d15 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -480,6 +480,16 @@ func (b *APIBuilder) authorizeConnectionSubresource(ctx context.Context, a autho Namespace: a.GetNamespace(), }, "")) + // Repositories is read-only + case "repositories": + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.ConnectionResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + default: id, err := identity.GetRequester(ctx) if err != nil { @@ -603,6 +613,7 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI storage[provisioning.ConnectionResourceInfo.StoragePath()] = connectionsStore storage[provisioning.ConnectionResourceInfo.StoragePath("status")] = connectionStatusStorage + storage[provisioning.ConnectionResourceInfo.StoragePath("repositories")] = NewConnectionRepositoriesConnector() // TODO: Add some logic so that the connectors can registered themselves and we don't have logic all over the place storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = NewTestConnector(b, repository.NewRepositoryTesterWithExistingChecker(repository.NewSimpleRepositoryTester(b.validator), b.VerifyAgainstExistingRepositories)) @@ -1247,6 +1258,23 @@ spec: oas.Paths.Paths[repoprefix+"/jobs/{uid}"] = sub } + // Document connection repositories endpoint + connectionprefix := root + "namespaces/{namespace}/connections/{name}" + sub = oas.Paths.Paths[connectionprefix+"/repositories"] + if sub != nil { + sub.Get.Description = "List repositories available from the external git provider through this connection" + sub.Get.Summary = "List external repositories" + sub.Get.Parameters = []*spec3.Parameter{} + sub.Post = nil + sub.Put = nil + sub.Delete = nil + + // Replace the content type for this response + mt := sub.Get.Responses.StatusCodeResponses[200].Content + s := defs[defsBase+"ExternalRepositoryList"].Schema + mt["*/*"].Schema = &s + } + // Run all extra post-processors. for _, extra := range b.extras { if err := extra.PostProcessOpenAPI(oas); err != nil { diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index a1ec8aafd65..dc52f4658e0 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -19,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/annotation" "github.com/grafana/grafana/pkg/registry/apps/correlations" "github.com/grafana/grafana/pkg/registry/apps/example" - "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/logsdrilldown" "github.com/grafana/grafana/pkg/registry/apps/playlist" "github.com/grafana/grafana/pkg/registry/apps/plugins" @@ -107,7 +106,6 @@ func ProvideBuilderRunners( registrar builder.APIRegistrar, restConfigProvider apiserver.RestConfigProvider, features featuremgmt.FeatureToggles, - investigationAppProvider *investigations.InvestigationsAppProvider, grafanaCfg *setting.Cfg, ) (*Service, error) { cfgWrapper := func(ctx context.Context) (*rest.Config, error) { @@ -127,11 +125,6 @@ func ProvideBuilderRunners( var apiGroupRunner *runner.APIGroupRunner var err error providers := []app.Provider{} - //nolint:staticcheck // not yet migrated to OpenFeature - if features.IsEnabledGlobally(featuremgmt.FlagInvestigationsBackend) { - logger.Debug("Investigations backend is enabled") - providers = append(providers, investigationAppProvider) - } apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...) if err != nil { diff --git a/pkg/registry/apps/investigations/authorizer.go b/pkg/registry/apps/investigations/authorizer.go deleted file mode 100644 index 3178f3d2f9c..00000000000 --- a/pkg/registry/apps/investigations/authorizer.go +++ /dev/null @@ -1,38 +0,0 @@ -package investigations - -import ( - "context" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/accesscontrol" - - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -func GetAuthorizer() authorizer.Authorizer { - return authorizer.AuthorizerFunc(func( - ctx context.Context, attr authorizer.Attributes, - ) (authorized authorizer.Decision, reason string, err error) { - if !attr.IsResourceRequest() { - return authorizer.DecisionNoOpinion, "", nil - } - - u, err := identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "valid user is required", err - } - - p := u.GetPermissions() - if len(p) == 0 { - return authorizer.DecisionDeny, "no permissions", nil - } - - _, ok := p[accesscontrol.ActionDatasourcesExplore] - if !ok { - // defer to the default authorizer if datasources:explore is not present - return authorizer.DecisionNoOpinion, "", nil - } - - return authorizer.DecisionAllow, "", nil - }) -} diff --git a/pkg/registry/apps/investigations/authorizer_test.go b/pkg/registry/apps/investigations/authorizer_test.go deleted file mode 100644 index c6d870bc7e7..00000000000 --- a/pkg/registry/apps/investigations/authorizer_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package investigations - -import ( - "context" - "testing" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/stretchr/testify/assert" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -func TestGetAuthorizer(t *testing.T) { - tests := []struct { - name string - ctx context.Context - attr authorizer.Attributes - expectedDecision authorizer.Decision - expectedReason string - expectedErr error - }{ - { - name: "non-resource request", - ctx: context.TODO(), - attr: &mockAttributes{resourceRequest: false}, - expectedDecision: authorizer.DecisionNoOpinion, - expectedReason: "", - expectedErr: nil, - }, - { - name: "user has datasources:explore permission", - ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{accesscontrol.ActionDatasourcesExplore: {}}}), - attr: &mockAttributes{resourceRequest: true}, - expectedDecision: authorizer.DecisionAllow, - expectedReason: "", - expectedErr: nil, - }, - { - name: "user does not have datasources:explore permission", - ctx: identity.WithRequester(context.TODO(), &mockUser{}), - attr: &mockAttributes{resourceRequest: true}, - expectedDecision: authorizer.DecisionDeny, - expectedReason: "no permissions", - expectedErr: nil, - }, - { - name: "user does not have datasources:explore permission", - ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{"foo": {}}}), - attr: &mockAttributes{resourceRequest: true}, - expectedDecision: authorizer.DecisionNoOpinion, - expectedReason: "", - expectedErr: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - auth := GetAuthorizer() - decision, reason, err := auth.Authorize(tt.ctx, tt.attr) - assert.Equal(t, tt.expectedDecision, decision) - assert.Equal(t, tt.expectedReason, reason) - assert.Equal(t, tt.expectedErr, err) - }) - } -} - -type mockAttributes struct { - authorizer.Attributes - resourceRequest bool -} - -func (m *mockAttributes) IsResourceRequest() bool { - return m.resourceRequest -} - -// Implement other methods of authorizer.Attributes as needed - -type mockUser struct { - identity.Requester - permissions map[string][]string -} - -func (m *mockUser) GetPermissions() map[string][]string { - return m.permissions -} - -// Implement other methods of identity.Requester as needed diff --git a/pkg/registry/apps/investigations/register.go b/pkg/registry/apps/investigations/register.go deleted file mode 100644 index 6fbf2603491..00000000000 --- a/pkg/registry/apps/investigations/register.go +++ /dev/null @@ -1,33 +0,0 @@ -package investigations - -import ( - "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/simple" - "github.com/grafana/grafana/apps/investigations/pkg/apis" - investigationv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1" - investigationapp "github.com/grafana/grafana/apps/investigations/pkg/app" - "github.com/grafana/grafana/pkg/services/apiserver/builder" - "github.com/grafana/grafana/pkg/services/apiserver/builder/runner" - "github.com/grafana/grafana/pkg/setting" -) - -type InvestigationsAppProvider struct { - app.Provider - cfg *setting.Cfg -} - -func RegisterApp( - cfg *setting.Cfg, -) *InvestigationsAppProvider { - provider := &InvestigationsAppProvider{ - cfg: cfg, - } - appCfg := &runner.AppBuilderConfig{ - OpenAPIDefGetter: investigationv0alpha1.GetOpenAPIDefinitions, - ManagedKinds: investigationapp.GetKinds(), - Authorizer: GetAuthorizer(), - AllowedV0Alpha1Resources: []string{builder.AllResourcesAllowed}, - } - provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, investigationapp.New) - return provider -} diff --git a/pkg/registry/apps/wireset.go b/pkg/registry/apps/wireset.go index a2cef7b2d55..d2d4feb2b5c 100644 --- a/pkg/registry/apps/wireset.go +++ b/pkg/registry/apps/wireset.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/annotation" "github.com/grafana/grafana/pkg/registry/apps/correlations" "github.com/grafana/grafana/pkg/registry/apps/example" - "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/logsdrilldown" "github.com/grafana/grafana/pkg/registry/apps/playlist" "github.com/grafana/grafana/pkg/registry/apps/plugins" @@ -21,7 +20,6 @@ var WireSet = wire.NewSet( ProvideAppInstallers, ProvideBuilderRunners, playlist.RegisterAppInstaller, - investigations.RegisterApp, plugins.ProvideAppInstaller, shorturl.RegisterAppInstaller, correlations.RegisterAppInstaller, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index ee7653f79d4..65e6ebeb36e 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -84,7 +84,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/annotation" correlations2 "github.com/grafana/grafana/pkg/registry/apps/correlations" "github.com/grafana/grafana/pkg/registry/apps/example" - "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/logsdrilldown" "github.com/grafana/grafana/pkg/registry/apps/playlist" "github.com/grafana/grafana/pkg/registry/apps/plugins" @@ -848,8 +847,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api return nil, err } zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, zanzanaClient, sqlStore, serverLockService, folderimplService, registerer) - investigationsAppProvider := investigations.RegisterApp(cfg) - appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, investigationsAppProvider, cfg) + appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, cfg) if err != nil { return nil, err } @@ -1511,8 +1509,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac return nil, err } zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, zanzanaClient, sqlStore, serverLockService, folderimplService, registerer) - investigationsAppProvider := investigations.RegisterApp(cfg) - appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, investigationsAppProvider, cfg) + appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, cfg) if err != nil { return nil, err } diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index dcf2432fb2c..fd0b4f78b42 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -182,25 +182,6 @@ func newFolderTranslation() translation { return folderTranslation } -func newExternalGroupMappingTranslation() translation { - return translation{ - resource: "teams.permissions", - attribute: "uid", - verbMapping: map[string]string{ - utils.VerbGet: "teams.permissions:read", - utils.VerbList: "teams.permissions:read", - utils.VerbWatch: "teams.permissions:read", - utils.VerbCreate: "teams.permissions:write", - utils.VerbUpdate: "teams.permissions:write", - utils.VerbPatch: "teams.permissions:write", - utils.VerbDelete: "teams.permissions:write", - utils.VerbGetPermissions: "teams.permissions:write", - utils.VerbSetPermissions: "teams.permissions:write", - }, - folderSupport: false, - } -} - func NewMapperRegistry() MapperRegistry { skipScopeOnAllVerbs := map[string]bool{ utils.VerbCreate: true, @@ -229,8 +210,6 @@ func NewMapperRegistry() MapperRegistry { "serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, map[string]bool{utils.VerbCreate: true}), // Teams is a special case. We translate user permissions from id to uid based. "teams": newResourceTranslation("teams", "uid", false, map[string]bool{utils.VerbCreate: true}), - // ExternalGroupMappings is a special case. We translate team permissions from id to uid based. - "externalgroupmappings": newExternalGroupMappingTranslation(), "coreroles": translation{ resource: "roles", attribute: "uid", diff --git a/pkg/services/authz/zanzana.go b/pkg/services/authz/zanzana.go index 79eb19a8b5b..b1d49d9ee17 100644 --- a/pkg/services/authz/zanzana.go +++ b/pkg/services/authz/zanzana.go @@ -90,7 +90,7 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea authzv1.RegisterAuthzServiceServer(channel, srv) authzextv1.RegisterAuthzExtentionServiceServer(channel, srv) - client, err := zClient.New(channel) + client, err := zClient.New(channel, reg) if err != nil { return nil, fmt.Errorf("failed to initialize zanzana client: %w", err) } @@ -169,7 +169,7 @@ func NewRemoteZanzanaClient(cfg ZanzanaClientConfig, reg prometheus.Registerer) return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err) } - client, err := zClient.New(conn) + client, err := zClient.New(conn, reg) if err != nil { return nil, fmt.Errorf("failed to initialize zanzana client: %w", err) } diff --git a/pkg/services/authz/zanzana/client/client.go b/pkg/services/authz/zanzana/client/client.go index 3c51d707561..ac19d9b3fdc 100644 --- a/pkg/services/authz/zanzana/client/client.go +++ b/pkg/services/authz/zanzana/client/client.go @@ -9,6 +9,7 @@ import ( authzlib "github.com/grafana/authlib/authz" authzv1 "github.com/grafana/authlib/authz/proto/v1" authlib "github.com/grafana/authlib/types" + "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/infra/log" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" @@ -25,15 +26,17 @@ type Client struct { authz authzv1.AuthzServiceClient authzext authzextv1.AuthzExtentionServiceClient authzlibclient *authzlib.ClientImpl + metrics *clientMetrics } -func New(cc grpc.ClientConnInterface) (*Client, error) { +func New(cc grpc.ClientConnInterface, reg prometheus.Registerer) (*Client, error) { authzlibclient := authzlib.NewClient(cc, authzlib.WithTracerClientOption(tracer)) c := &Client{ authzlibclient: authzlibclient, authz: authzv1.NewAuthzServiceClient(cc), authzext: authzextv1.NewAuthzExtentionServiceClient(cc), logger: log.New("zanzana.client"), + metrics: newClientMetrics(reg), } return c, nil @@ -43,6 +46,9 @@ func (c *Client) Check(ctx context.Context, id authlib.AuthInfo, req authlib.Che ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Check", req.Namespace)) + defer timer.ObserveDuration() + return c.authzlibclient.Check(ctx, id, req, folder) } @@ -50,6 +56,9 @@ func (c *Client) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.L ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Compile") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Compile", req.Namespace)) + defer timer.ObserveDuration() + return c.authzlibclient.Compile(ctx, id, req) } @@ -64,6 +73,9 @@ func (c *Client) Write(ctx context.Context, req *authzextv1.WriteRequest) error ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Write") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Write", req.Namespace)) + defer timer.ObserveDuration() + _, err := c.authzext.Write(ctx, req) return err } @@ -72,6 +84,9 @@ func (c *Client) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckReque ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("BatchCheck", req.Namespace)) + defer timer.ObserveDuration() + return c.authzext.BatchCheck(ctx, req) } @@ -87,6 +102,9 @@ func (c *Client) Mutate(ctx context.Context, req *authzextv1.MutateRequest) erro ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Mutate") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Mutate", req.Namespace)) + defer timer.ObserveDuration() + _, err := c.authzext.Mutate(ctx, req) return err } @@ -95,5 +113,8 @@ func (c *Client) Query(ctx context.Context, req *authzextv1.QueryRequest) (*auth ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Query") defer span.End() + timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Query", req.Namespace)) + defer timer.ObserveDuration() + return c.authzext.Query(ctx, req) } diff --git a/pkg/services/authz/zanzana/client/metrics.go b/pkg/services/authz/zanzana/client/metrics.go index 3fe7b1dd590..070f1cecf2c 100644 --- a/pkg/services/authz/zanzana/client/metrics.go +++ b/pkg/services/authz/zanzana/client/metrics.go @@ -7,10 +7,10 @@ import ( const ( metricsNamespace = "iam" - metricsSubSystem = "authz_zanzana" + metricsSubSystem = "authz_zanzana_client" ) -type metrics struct { +type shadowClientMetrics struct { // evaluationsSeconds is a summary for evaluating access for a specific engine (RBAC and zanzana) evaluationsSeconds *prometheus.HistogramVec // compileSeconds is a summary for compiling item checker for a specific engine (RBAC and zanzana) @@ -19,8 +19,13 @@ type metrics struct { evaluationStatusTotal *prometheus.CounterVec } -func newShadowClientMetrics(reg prometheus.Registerer) *metrics { - return &metrics{ +type clientMetrics struct { + // requestDurationSeconds is a summary for zanzana client request duration + requestDurationSeconds *prometheus.HistogramVec +} + +func newShadowClientMetrics(reg prometheus.Registerer) *shadowClientMetrics { + return &shadowClientMetrics{ evaluationsSeconds: promauto.With(reg).NewHistogramVec( prometheus.HistogramOpts{ Name: "engine_evaluations_seconds", @@ -52,3 +57,18 @@ func newShadowClientMetrics(reg prometheus.Registerer) *metrics { ), } } + +func newClientMetrics(reg prometheus.Registerer) *clientMetrics { + return &clientMetrics{ + requestDurationSeconds: promauto.With(reg).NewHistogramVec( + prometheus.HistogramOpts{ + Name: "request_duration_seconds", + Help: "Histogram for zanzana client request duration", + Namespace: metricsNamespace, + Subsystem: metricsSubSystem, + Buckets: prometheus.ExponentialBuckets(0.00001, 4, 10), + }, + []string{"method", "request_namespace"}, + ), + } +} diff --git a/pkg/services/authz/zanzana/client/shadow_client.go b/pkg/services/authz/zanzana/client/shadow_client.go index 6f6e6dc6836..1e23c9b20f2 100644 --- a/pkg/services/authz/zanzana/client/shadow_client.go +++ b/pkg/services/authz/zanzana/client/shadow_client.go @@ -20,7 +20,7 @@ type ShadowClient struct { logger log.Logger accessClient authlib.AccessClient zanzanaClient authlib.AccessClient - metrics *metrics + metrics *shadowClientMetrics } // WithShadowClient returns a new access client that runs zanzana checks in the background. diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5ec4bfb880b..885079a5f5a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -322,6 +322,13 @@ var ( Owner: grafanaOperatorExperienceSquad, RequiresRestart: true, }, + { + Name: "reportingCsvEncodingOptions", + Description: "Enables CSV encoding options in the reporting feature", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaOperatorExperienceSquad, + }, { Name: "sseGroupByDatasource", Description: "Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.", @@ -1283,13 +1290,6 @@ var ( Owner: grafanaPartnerPluginsSquad, Expression: "false", }, - { - Name: "unifiedHistory", - Description: "Displays the navigation history so the user can navigate back to previous pages", - Stage: FeatureStageExperimental, - Owner: grafanaFrontendSearchNavOrganise, - FrontendOnly: true, - }, { // Remove this flag once Loki v4 is released and the min supported version is v3.0+, // since users on v2.9 need it to disable the feature, as it doesn't work for them. @@ -1299,13 +1299,6 @@ var ( Owner: grafanaObservabilityLogsSquad, Expression: "true", }, - { - Name: "investigationsBackend", - Description: "Enable the investigations backend API", - Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, - Expression: "false", - }, { Name: "k8SFolderCounts", Description: "Enable folder's api server counts", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 20009d3f30b..8ddc448ef52 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -43,6 +43,7 @@ configurableSchedulerTick,experimental,@grafana/alerting-squad,false,true,false dashgpt,GA,@grafana/dashboards-squad,false,false,true aiGeneratedDashboardChanges,experimental,@grafana/dashboards-squad,false,false,true reportingRetries,preview,@grafana/grafana-operator-experience-squad,false,true,false +reportingCsvEncodingOptions,experimental,@grafana/grafana-operator-experience-squad,false,false,false sseGroupByDatasource,experimental,@grafana/grafana-datasources-core-services,false,false,false lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false @@ -177,9 +178,7 @@ alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false, alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true unifiedStorageSearchUI,experimental,@grafana/search-and-storage,false,false,false elasticsearchCrossClusterSearch,GA,@grafana/partner-datasources,false,false,false -unifiedHistory,experimental,@grafana/grafana-search-navigate-organise,false,false,true lokiLabelNamesQueryApi,GA,@grafana/observability-logs,false,false,false -investigationsBackend,experimental,@grafana/grafana-app-platform-squad,false,false,false k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false k8SFolderMove,experimental,@grafana/search-and-storage,false,false,false improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 062748b95df..c42229d8870 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -135,6 +135,10 @@ const ( // Enables rendering retries for the reporting feature FlagReportingRetries = "reportingRetries" + // FlagReportingCsvEncodingOptions + // Enables CSV encoding options in the reporting feature + FlagReportingCsvEncodingOptions = "reportingCsvEncodingOptions" + // FlagSseGroupByDatasource // Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch. FlagSseGroupByDatasource = "sseGroupByDatasource" @@ -539,10 +543,6 @@ const ( // Defaults to using the Loki `/labels` API instead of `/series` FlagLokiLabelNamesQueryApi = "lokiLabelNamesQueryApi" - // FlagInvestigationsBackend - // Enable the investigations backend API - FlagInvestigationsBackend = "investigationsBackend" - // FlagK8SFolderCounts // Enable folder's api server counts FlagK8SFolderCounts = "k8SFolderCounts" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ddd6d3bbc0d..09c4d0c9760 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1793,7 +1793,8 @@ "metadata": { "name": "investigationsBackend", "resourceVersion": "1764664939750", - "creationTimestamp": "2024-12-18T08:31:03Z" + "creationTimestamp": "2024-12-18T08:31:03Z", + "deletionTimestamp": "2025-12-16T16:06:24Z" }, "spec": { "description": "Enable the investigations backend API", @@ -3136,6 +3137,18 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "reportingCsvEncodingOptions", + "resourceVersion": "1766080709938", + "creationTimestamp": "2025-12-18T17:58:29Z" + }, + "spec": { + "description": "Enables CSV encoding options in the reporting feature", + "stage": "experimental", + "codeowner": "@grafana/grafana-operator-experience-squad" + } + }, { "metadata": { "name": "reportingRetries", @@ -3571,8 +3584,12 @@ { "metadata": { "name": "unifiedHistory", - "resourceVersion": "1764664939750", - "creationTimestamp": "2024-12-13T10:41:18Z" + "resourceVersion": "1762958248290", + "creationTimestamp": "2024-12-13T10:41:18Z", + "deletionTimestamp": "2025-11-13T16:25:53Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-11-12 14:37:28.29086 +0000 UTC" + } }, "spec": { "description": "Displays the navigation history so the user can navigate back to previous pages", diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 5b9eb985bd8..a1ef1aab1ef 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -440,7 +440,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingTriage) { if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ - Text: "Alerts", SubTitle: "Visualize active and pending alerts", Id: "alert-alerts", Url: s.cfg.AppSubURL + "/alerting/alerts", Icon: "bell", IsNew: true, + Text: "Alert activity", SubTitle: "Visualize active and pending alerts", Id: "alert-alerts", Url: s.cfg.AppSubURL + "/alerting/alerts", Icon: "bell", IsNew: true, }) } } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index aafc9167ce4..1155a4ead7f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -637,6 +637,8 @@ type UnifiedStorageConfig struct { // EnableMigration indicates whether migration is enabled for the resource. // If not set, will use the default from MigratedUnifiedResources. EnableMigration bool + // AutoMigrationThreshold is the threshold below which a resource is automatically migrated. + AutoMigrationThreshold int } type InstallPlugin struct { diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 8086151c40a..21a3f455993 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -8,6 +8,10 @@ import ( "github.com/grafana/grafana/pkg/util/osutil" ) +// DefaultAutoMigrationThreshold is the default threshold for auto migration switching. +// If a resource has entries at or below this count, it will be migrated. +const DefaultAutoMigrationThreshold = 10 + const ( PlaylistResource = "playlists.playlist.grafana.app" FolderResource = "folders.folder.grafana.app" @@ -21,6 +25,13 @@ var MigratedUnifiedResources = map[string]bool{ DashboardResource: false, } +// AutoMigratedUnifiedResources maps resources that support auto-migration +// TODO: remove this before Grafana 13 GA: https://github.com/grafana/search-and-storage-team/issues/613 +var AutoMigratedUnifiedResources = map[string]bool{ + FolderResource: true, + DashboardResource: true, +} + // read storage configs from ini file. They look like: // [unified_storage..] // = @@ -59,6 +70,13 @@ func (cfg *Cfg) setUnifiedStorageConfig() { enableMigration = section.Key("enableMigration").MustBool(MigratedUnifiedResources[resourceName]) } + // parse autoMigrationThreshold from resource section + autoMigrationThreshold := 0 + autoMigrate := AutoMigratedUnifiedResources[resourceName] + if autoMigrate { + autoMigrationThreshold = section.Key("autoMigrationThreshold").MustInt(DefaultAutoMigrationThreshold) + } + storageConfig[resourceName] = UnifiedStorageConfig{ DualWriterMode: rest.DualWriterMode(dualWriterMode), DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled, @@ -66,6 +84,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { DataSyncerRecordsLimit: dataSyncerRecordsLimit, DataSyncerInterval: dataSyncerInterval, EnableMigration: enableMigration, + AutoMigrationThreshold: autoMigrationThreshold, } } cfg.UnifiedStorage = storageConfig @@ -73,13 +92,13 @@ func (cfg *Cfg) setUnifiedStorageConfig() { // Set indexer config for unified storage section := cfg.Raw.Section("unified_storage") cfg.DisableDataMigrations = section.Key("disable_data_migrations").MustBool(false) - if !cfg.DisableDataMigrations && cfg.getUnifiedStorageType() == "unified" { + if !cfg.DisableDataMigrations && cfg.UnifiedStorageType() == "unified" { // Helper log to find instances running migrations in the future cfg.Logger.Info("Unified migration configs enforced") cfg.enforceMigrationToUnifiedConfigs() } else { // Helper log to find instances disabling migration - cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.getUnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations) + cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.UnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations) } cfg.EnableSearch = section.Key("enable_search").MustBool(false) cfg.MaxPageSizeBytes = section.Key("max_page_size_bytes").MustInt(0) @@ -147,14 +166,15 @@ func (cfg *Cfg) enforceMigrationToUnifiedConfigs() { DualWriterMode: 5, DualWriterMigrationDataSyncDisabled: true, EnableMigration: true, + AutoMigrationThreshold: resourceCfg.AutoMigrationThreshold, } } } -// getUnifiedStorageType returns the configured storage type without creating or mutating keys. +// UnifiedStorageType returns the configured storage type without creating or mutating keys. // Precedence: env > ini > default ("unified"). // Used to decide unified storage behavior early without side effects. -func (cfg *Cfg) getUnifiedStorageType() string { +func (cfg *Cfg) UnifiedStorageType() string { const ( grafanaAPIServerSectionName = "grafana-apiserver" storageTypeKeyName = "storage_type" @@ -168,3 +188,23 @@ func (cfg *Cfg) getUnifiedStorageType() string { } return defaultStorageType } + +// UnifiedStorageConfig returns the UnifiedStorageConfig for a resource. +func (cfg *Cfg) UnifiedStorageConfig(resource string) UnifiedStorageConfig { + if cfg.UnifiedStorage == nil { + return UnifiedStorageConfig{} + } + return cfg.UnifiedStorage[resource] +} + +// EnableMode5 enables migration and sets mode 5 for a resource. +func (cfg *Cfg) EnableMode5(resource string) { + if cfg.UnifiedStorage == nil { + cfg.UnifiedStorage = make(map[string]UnifiedStorageConfig) + } + config := cfg.UnifiedStorage[resource] + config.DualWriterMode = rest.Mode5 + config.DualWriterMigrationDataSyncDisabled = true + config.EnableMigration = true + cfg.UnifiedStorage[resource] = config +} diff --git a/pkg/setting/setting_unified_storage_test.go b/pkg/setting/setting_unified_storage_test.go index c112dc85962..58f0fe56a60 100644 --- a/pkg/setting/setting_unified_storage_test.go +++ b/pkg/setting/setting_unified_storage_test.go @@ -43,10 +43,16 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) { } assert.Equal(t, exists, true, migratedResource) + expectedThreshold := 0 + if AutoMigratedUnifiedResources[migratedResource] { + expectedThreshold = DefaultAutoMigrationThreshold + } + assert.Equal(t, UnifiedStorageConfig{ DualWriterMode: 5, DualWriterMigrationDataSyncDisabled: true, EnableMigration: isEnabled, + AutoMigrationThreshold: expectedThreshold, }, resourceCfg, migratedResource) } } @@ -71,6 +77,7 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) { DualWriterPeriodicDataSyncJobEnabled: true, DataSyncerRecordsLimit: 1001, DataSyncerInterval: time.Minute * 10, + AutoMigrationThreshold: 0, }) validateMigratedResources(false) diff --git a/pkg/storage/unified/migrations/migrator_test.go b/pkg/storage/unified/migrations/migrator_test.go index 8552092d066..cfd909a9255 100644 --- a/pkg/storage/unified/migrations/migrator_test.go +++ b/pkg/storage/unified/migrations/migrator_test.go @@ -214,8 +214,18 @@ func runMigrationTestSuite(t *testing.T, testCases []resourceMigratorTestCase) { for _, state := range testStates { t.Run(state.tc.name(), func(t *testing.T) { - // Verify resources now exist in unified storage after migration - state.tc.verify(t, helper, true) + shouldExist := true + for _, gvr := range state.tc.resources() { + resourceKey := fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group) + // Resources exist if they're either: + // 1. In MigratedUnifiedResources (enabled by default), OR + // 2. In AutoMigratedUnifiedResources (auto-migrated because count is below threshold) + if !setting.MigratedUnifiedResources[resourceKey] && !setting.AutoMigratedUnifiedResources[resourceKey] { + shouldExist = false + break + } + } + state.tc.verify(t, helper, shouldExist) }) } @@ -270,7 +280,7 @@ const ( var migrationIDsToDefault = map[string]bool{ playlistsID: true, - foldersAndDashboardsID: false, + foldersAndDashboardsID: true, // Auto-migrated when resource count is below threshold } func verifyRegisteredMigrations(t *testing.T, helper *apis.K8sTestHelper, onlyDefault bool, optOut bool) { diff --git a/pkg/storage/unified/migrations/resource_migration.go b/pkg/storage/unified/migrations/resource_migration.go index 5bbed9c5722..d551ae32bde 100644 --- a/pkg/storage/unified/migrations/resource_migration.go +++ b/pkg/storage/unified/migrations/resource_migration.go @@ -10,9 +10,11 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/util/xorm" + "github.com/grafana/grafana/pkg/util/xorm/core" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -31,6 +33,20 @@ type ResourceMigration struct { migrationID string validators []Validator // Optional: custom validation logic for this migration log log.Logger + cfg *setting.Cfg + autoMigrate bool // If true, auto-migrate resource if count is below threshold + hadErrors bool // Tracks if errors occurred during migration (used with ignoreErrors) +} + +// ResourceMigrationOption is a functional option for configuring ResourceMigration. +type ResourceMigrationOption func(*ResourceMigration) + +// WithAutoMigrate configures the migration to auto-migrate resource if count is below threshold. +func WithAutoMigrate(cfg *setting.Cfg) ResourceMigrationOption { + return func(m *ResourceMigration) { + m.cfg = cfg + m.autoMigrate = true + } } // NewResourceMigration creates a new migration for the specified resources. @@ -39,14 +55,24 @@ func NewResourceMigration( resources []schema.GroupResource, migrationID string, validators []Validator, + opts ...ResourceMigrationOption, ) *ResourceMigration { - return &ResourceMigration{ + m := &ResourceMigration{ migrator: migrator, resources: resources, migrationID: migrationID, validators: validators, log: log.New("storage.unified.resource_migration." + migrationID), } + for _, opt := range opts { + opt(m) + } + return m +} + +func (m *ResourceMigration) SkipMigrationLog() bool { + // Skip populating the log table if auto-migrate is enabled and errors occurred + return m.autoMigrate && m.hadErrors } var _ migrator.CodeMigration = (*ResourceMigration)(nil) @@ -57,7 +83,23 @@ func (m *ResourceMigration) SQL(_ migrator.Dialect) string { } // Exec implements migrator.CodeMigration interface. Executes the migration across all organizations. -func (m *ResourceMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { +func (m *ResourceMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) (err error) { + // Track any errors that occur during migration + defer func() { + if err != nil { + if m.autoMigrate { + m.log.Warn( + `[WARN] Resource migration failed and is currently skipped. +This migration will be enforced in the next major Grafana release, where failures will block startup or resource loading. + +This warning is intended to help you detect and report issues early. +Please investigate the failure and report it to the Grafana team so it can be addressed before the next major release.`, + "error", err) + } + m.hadErrors = true + } + }() + ctx := context.Background() orgs, err := m.getAllOrgs(sess) @@ -75,7 +117,8 @@ func (m *ResourceMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) erro if mg.Dialect.DriverName() == migrator.SQLite { // reuse transaction in SQLite to avoid "database is locked" errors - tx, err := sess.Tx() + var tx *core.Tx + tx, err = sess.Tx() if err != nil { m.log.Error("Failed to get transaction from session", "error", err) return fmt.Errorf("failed to get transaction: %w", err) @@ -85,12 +128,22 @@ func (m *ResourceMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) erro } for _, org := range orgs { - if err := m.migrateOrg(ctx, sess, org); err != nil { + if err = m.migrateOrg(ctx, sess, org); err != nil { return err } } + // Auto-enable mode 5 for resources after successful migration + // TODO: remove this before Grafana 13 GA: https://github.com/grafana/search-and-storage-team/issues/613 + if m.autoMigrate { + for _, gr := range m.resources { + m.log.Info("Auto-enabling mode 5 for resource", "resource", gr.Resource+"."+gr.Group) + m.cfg.EnableMode5(gr.Resource + "." + gr.Group) + } + } + m.log.Info("Migration completed successfully for all organizations", "org_count", len(orgs)) + return nil } diff --git a/pkg/storage/unified/migrations/resources.go b/pkg/storage/unified/migrations/resources.go index ea32bb54322..debbb270642 100644 --- a/pkg/storage/unified/migrations/resources.go +++ b/pkg/storage/unified/migrations/resources.go @@ -1,11 +1,13 @@ package migrations import ( + "context" "fmt" v1beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" playlists "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" sqlstoremigrator "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" @@ -14,69 +16,70 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -type ResourceDefinition struct { - GroupResource schema.GroupResource - MigratorFunc string // Name of the method: "MigrateFolders", "MigrateDashboards", etc. +type resourceDefinition struct { + groupResource schema.GroupResource + migratorFunc string // Name of the method: "MigrateFolders", "MigrateDashboards", etc. } type migrationDefinition struct { name string + migrationID string // The ID stored in the migration log table (e.g., "playlists migration") resources []string - registerFunc func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) + registerFunc func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient, opts ...ResourceMigrationOption) } -var resourceRegistry = []ResourceDefinition{ +var resourceRegistry = []resourceDefinition{ { - GroupResource: schema.GroupResource{Group: folders.GROUP, Resource: folders.RESOURCE}, - MigratorFunc: "MigrateFolders", + groupResource: schema.GroupResource{Group: folders.GROUP, Resource: folders.RESOURCE}, + migratorFunc: "MigrateFolders", }, { - GroupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.LIBRARY_PANEL_RESOURCE}, - MigratorFunc: "MigrateLibraryPanels", + groupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.LIBRARY_PANEL_RESOURCE}, + migratorFunc: "MigrateLibraryPanels", }, { - GroupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.DASHBOARD_RESOURCE}, - MigratorFunc: "MigrateDashboards", + groupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.DASHBOARD_RESOURCE}, + migratorFunc: "MigrateDashboards", }, { - GroupResource: schema.GroupResource{Group: playlists.APIGroup, Resource: "playlists"}, - MigratorFunc: "MigratePlaylists", + groupResource: schema.GroupResource{Group: playlists.APIGroup, Resource: "playlists"}, + migratorFunc: "MigratePlaylists", }, } var migrationRegistry = []migrationDefinition{ { name: "playlists", + migrationID: "playlists migration", resources: []string{setting.PlaylistResource}, registerFunc: registerPlaylistMigration, }, { name: "folders and dashboards", + migrationID: "folders and dashboards migration", resources: []string{setting.FolderResource, setting.DashboardResource}, registerFunc: registerDashboardAndFolderMigration, }, } -func registerMigrations(cfg *setting.Cfg, mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) error { +func registerMigrations(ctx context.Context, + cfg *setting.Cfg, + mg *sqlstoremigrator.Migrator, + migrator UnifiedMigrator, + client resource.ResourceClient, + sqlStore db.DB, +) error { for _, migration := range migrationRegistry { - var ( - hasValue bool - allEnabled bool - ) - - for _, res := range migration.resources { - enabled := cfg.UnifiedStorage[res].EnableMigration - if !hasValue { - allEnabled = enabled - hasValue = true - continue - } - if enabled != allEnabled { - return fmt.Errorf("cannot migrate resources separately: %v migration must be either all enabled or all disabled", migration.resources) - } + if shouldAutoMigrate(ctx, migration, cfg, sqlStore) { + migration.registerFunc(mg, migrator, client, WithAutoMigrate(cfg)) + continue } - if !allEnabled { + enabled, err := isMigrationEnabled(migration, cfg) + if err != nil { + return err + } + if !enabled { logger.Info("Migration is disabled in config, skipping", "migration", migration.name) continue } @@ -85,10 +88,193 @@ func registerMigrations(cfg *setting.Cfg, mg *sqlstoremigrator.Migrator, migrato return nil } -func getResourceDefinition(group, resource string) *ResourceDefinition { +func registerDashboardAndFolderMigration(mg *sqlstoremigrator.Migrator, + migrator UnifiedMigrator, + client resource.ResourceClient, + opts ...ResourceMigrationOption, +) { + foldersDef := getResourceDefinition("folder.grafana.app", "folders") + dashboardsDef := getResourceDefinition("dashboard.grafana.app", "dashboards") + driverName := mg.Dialect.DriverName() + + folderCountValidator := NewCountValidator( + client, + foldersDef.groupResource, + "dashboard", + "org_id = ? and is_folder = true", + driverName, + ) + + dashboardCountValidator := NewCountValidator( + client, + dashboardsDef.groupResource, + "dashboard", + "org_id = ? and is_folder = false", + driverName, + ) + + folderTreeValidator := NewFolderTreeValidator(client, foldersDef.groupResource, driverName) + + dashboardsAndFolders := NewResourceMigration( + migrator, + []schema.GroupResource{foldersDef.groupResource, dashboardsDef.groupResource}, + "folders-dashboards", + []Validator{folderCountValidator, dashboardCountValidator, folderTreeValidator}, + opts..., + ) + mg.AddMigration("folders and dashboards migration", dashboardsAndFolders) +} + +func registerPlaylistMigration(mg *sqlstoremigrator.Migrator, + migrator UnifiedMigrator, + client resource.ResourceClient, + opts ...ResourceMigrationOption, +) { + playlistsDef := getResourceDefinition("playlist.grafana.app", "playlists") + driverName := mg.Dialect.DriverName() + + playlistCountValidator := NewCountValidator( + client, + playlistsDef.groupResource, + "playlist", + "org_id = ?", + driverName, + ) + + playlistsMigration := NewResourceMigration( + migrator, + []schema.GroupResource{playlistsDef.groupResource}, + "playlists", + []Validator{playlistCountValidator}, + opts..., + ) + mg.AddMigration("playlists migration", playlistsMigration) +} + +// TODO: remove this before Grafana 13 GA: https://github.com/grafana/search-and-storage-team/issues/613 +func shouldAutoMigrate(ctx context.Context, migration migrationDefinition, cfg *setting.Cfg, sqlStore db.DB) bool { + autoMigrate := false + + for _, res := range migration.resources { + config := cfg.UnifiedStorageConfig(res) + + if config.DualWriterMode == 5 { + return false + } + + if !setting.AutoMigratedUnifiedResources[res] { + continue + } + + if checkIfAlreadyMigrated(ctx, migration, sqlStore) { + for _, res := range migration.resources { + cfg.EnableMode5(res) + } + logger.Info("Auto-migration already completed, enabling mode 5 for resources", "migration", migration.name) + return true + } + + autoMigrate = true + threshold := int64(setting.DefaultAutoMigrationThreshold) + if config.AutoMigrationThreshold > 0 { + threshold = int64(config.AutoMigrationThreshold) + } + + count, err := countResource(ctx, sqlStore, res) + if err != nil { + logger.Warn("Failed to count resource for auto migration check", "resource", res, "error", err) + return false + } + + logger.Info("Resource count for auto migration check", "resource", res, "count", count, "threshold", threshold) + + if count > threshold { + return false + } + } + + if !autoMigrate { + return false + } + + logger.Info("Auto-migration enabled for migration", "migration", migration.name) + return true +} + +func checkIfAlreadyMigrated(ctx context.Context, migration migrationDefinition, sqlStore db.DB) bool { + if migration.migrationID == "" { + return false + } + + exists, err := migrationExists(ctx, sqlStore, migration.migrationID) + if err != nil { + logger.Warn("Failed to check if migration exists", "migration", migration.name, "error", err) + return false + } + + return exists +} + +func isMigrationEnabled(migration migrationDefinition, cfg *setting.Cfg) (bool, error) { + var ( + hasValue bool + allEnabled bool + ) + + for _, res := range migration.resources { + enabled := cfg.UnifiedStorage[res].EnableMigration + if !hasValue { + allEnabled = enabled + hasValue = true + continue + } + if enabled != allEnabled { + return false, fmt.Errorf("cannot migrate resources separately: %v migration must be either all enabled or all disabled", migration.resources) + } + } + + return allEnabled, nil +} + +// TODO: remove this before Grafana 13 GA: https://github.com/grafana/search-and-storage-team/issues/613 +func countResource(ctx context.Context, sqlStore db.DB, resourceName string) (int64, error) { + var count int64 + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + switch resourceName { + case setting.DashboardResource: + var err error + count, err = sess.Table("dashboard").Where("is_folder = ?", false).Count() + return err + case setting.FolderResource: + var err error + count, err = sess.Table("dashboard").Where("is_folder = ?", true).Count() + return err + default: + return fmt.Errorf("unknown resource: %s", resourceName) + } + }) + return count, err +} + +const migrationLogTableName = "unifiedstorage_migration_log" + +func migrationExists(ctx context.Context, sqlStore db.DB, migrationID string) (bool, error) { + var count int64 + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + var err error + count, err = sess.Table(migrationLogTableName).Where("migration_id = ?", migrationID).Count() + return err + }) + if err != nil { + return false, fmt.Errorf("failed to check migration existence: %w", err) + } + return count > 0, nil +} + +func getResourceDefinition(group, resource string) *resourceDefinition { for i := range resourceRegistry { r := &resourceRegistry[i] - if r.GroupResource.Group == group && r.GroupResource.Resource == resource { + if r.groupResource.Group == group && r.groupResource.Resource == resource { return r } } @@ -102,8 +288,8 @@ func buildResourceKey(group, resource, namespace string) *resourcepb.ResourceKey } return &resourcepb.ResourceKey{ Namespace: namespace, - Group: def.GroupResource.Group, - Resource: def.GroupResource.Resource, + Group: def.groupResource.Group, + Resource: def.groupResource.Resource, } } @@ -113,7 +299,7 @@ func getMigratorFunc(accessor legacy.MigrationDashboardAccessor, group, resource return nil } - switch def.MigratorFunc { + switch def.migratorFunc { case "MigrateFolders": return accessor.MigrateFolders case "MigrateLibraryPanels": @@ -130,7 +316,7 @@ func getMigratorFunc(accessor legacy.MigrationDashboardAccessor, group, resource func validateRegisteredResources() error { registeredMap := make(map[string]bool) for _, gr := range resourceRegistry { - key := fmt.Sprintf("%s.%s", gr.GroupResource.Resource, gr.GroupResource.Group) + key := fmt.Sprintf("%s.%s", gr.groupResource.Resource, gr.groupResource.Group) registeredMap[key] = true } diff --git a/pkg/storage/unified/migrations/resources_test.go b/pkg/storage/unified/migrations/resources_test.go index 5a519e9ad62..261cd49e244 100644 --- a/pkg/storage/unified/migrations/resources_test.go +++ b/pkg/storage/unified/migrations/resources_test.go @@ -1,12 +1,15 @@ package migrations import ( + "context" + "strings" "testing" sqlstoremigrator "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime/schema" ) // TestRegisterMigrations exercises registerMigrations with various EnableMigration configs using a table-driven test. @@ -14,20 +17,28 @@ func TestRegisterMigrations(t *testing.T) { origRegistry := migrationRegistry t.Cleanup(func() { migrationRegistry = origRegistry }) + // Use fake resource names that are NOT in setting.AutoMigratedUnifiedResources + // to avoid triggering the auto-migrate code path which requires a non-nil sqlStore. + const ( + fakePlaylistResource = "fake.playlists.resource" + fakeFolderResource = "fake.folders.resource" + fakeDashboardResource = "fake.dashboards.resource" + ) + // helper to build a fake registry with custom register funcs that bump counters makeFakeRegistry := func(migrationCalls map[string]int) []migrationDefinition { return []migrationDefinition{ { name: "playlists", - resources: []string{setting.PlaylistResource}, - registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { + resources: []string{fakePlaylistResource}, + registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient, opts ...ResourceMigrationOption) { migrationCalls["playlists"]++ }, }, { name: "folders and dashboards", - resources: []string{setting.FolderResource, setting.DashboardResource}, - registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { + resources: []string{fakeFolderResource, fakeDashboardResource}, + registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient, opts ...ResourceMigrationOption) { migrationCalls["folders and dashboards"]++ }, }, @@ -38,7 +49,9 @@ func TestRegisterMigrations(t *testing.T) { makeCfg := func(vals map[string]bool) *setting.Cfg { cfg := &setting.Cfg{UnifiedStorage: make(map[string]setting.UnifiedStorageConfig)} for k, v := range vals { - cfg.UnifiedStorage[k] = setting.UnifiedStorageConfig{EnableMigration: v} + cfg.UnifiedStorage[k] = setting.UnifiedStorageConfig{ + EnableMigration: v, + } } return cfg } @@ -71,13 +84,13 @@ func TestRegisterMigrations(t *testing.T) { migrationRegistry = makeFakeRegistry(migrationCalls) cfg := makeCfg(map[string]bool{ - setting.PlaylistResource: tt.enablePlaylist, - setting.FolderResource: tt.enableFolder, - setting.DashboardResource: tt.enableDashboard, + fakePlaylistResource: tt.enablePlaylist, + fakeFolderResource: tt.enableFolder, + fakeDashboardResource: tt.enableDashboard, }) // We pass nils for migrator dependencies because our fake registerFuncs don't use them - err := registerMigrations(cfg, nil, nil, nil) + err := registerMigrations(context.Background(), cfg, nil, nil, nil, nil) if tt.wantErr { require.Error(t, err, "expected error for mismatched enablement") @@ -90,3 +103,176 @@ func TestRegisterMigrations(t *testing.T) { }) } } + +// TestResourceMigration_AutoMigrateEnablesMode5 verifies the autoMigrate behavior: +// - When autoMigrate=true AND cfg is set AND storage type is "unified", mode 5 should be enabled +// - In all other cases, mode 5 should NOT be enabled +func TestResourceMigration_AutoMigrateEnablesMode5(t *testing.T) { + // Helper to create a cfg with unified storage type + makeUnifiedCfg := func() *setting.Cfg { + cfg := setting.NewCfg() + cfg.Raw.Section("grafana-apiserver").Key("storage_type").SetValue("unified") + cfg.UnifiedStorage = make(map[string]setting.UnifiedStorageConfig) + return cfg + } + + // Helper to create a cfg with legacy storage type + makeLegacyCfg := func() *setting.Cfg { + cfg := setting.NewCfg() + cfg.Raw.Section("grafana-apiserver").Key("storage_type").SetValue("legacy") + cfg.UnifiedStorage = make(map[string]setting.UnifiedStorageConfig) + return cfg + } + + tests := []struct { + name string + autoMigrate bool + cfg *setting.Cfg + resources []string + wantMode5Enabled bool + description string + }{ + { + name: "autoMigrate enabled with unified storage", + autoMigrate: true, + cfg: makeUnifiedCfg(), + resources: []string{setting.DashboardResource}, + wantMode5Enabled: true, + description: "Should enable mode 5 when autoMigrate=true and storage type is unified", + }, + { + name: "autoMigrate disabled with unified storage", + autoMigrate: false, + cfg: makeUnifiedCfg(), + resources: []string{setting.DashboardResource}, + wantMode5Enabled: false, + description: "Should NOT enable mode 5 when autoMigrate=false", + }, + { + name: "autoMigrate enabled with legacy storage", + autoMigrate: true, + cfg: makeLegacyCfg(), + resources: []string{setting.DashboardResource}, + wantMode5Enabled: false, + description: "Should NOT enable mode 5 when storage type is legacy", + }, + { + name: "autoMigrate enabled with nil cfg", + autoMigrate: true, + cfg: nil, + resources: []string{setting.DashboardResource}, + wantMode5Enabled: false, + description: "Should NOT enable mode 5 when cfg is nil", + }, + { + name: "autoMigrate enabled with multiple resources", + autoMigrate: true, + cfg: makeUnifiedCfg(), + resources: []string{setting.FolderResource, setting.DashboardResource}, + wantMode5Enabled: true, + description: "Should enable mode 5 for all resources when autoMigrate=true", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Build schema.GroupResource from resource strings + resources := make([]schema.GroupResource, 0, len(tt.resources)) + for _, r := range tt.resources { + parts := strings.SplitN(r, ".", 2) + resources = append(resources, schema.GroupResource{ + Resource: parts[0], + Group: parts[1], + }) + } + + // Create the migration with options + var opts []ResourceMigrationOption + if tt.autoMigrate { + opts = append(opts, WithAutoMigrate(tt.cfg)) + } + + m := NewResourceMigration(nil, resources, "test-auto-migrate", nil, opts...) + + // Simulate what happens at the end of a successful migration + // This is the logic from Exec() that we're testing + if m.autoMigrate && m.cfg != nil && m.cfg.UnifiedStorageType() == "unified" { + for _, gr := range m.resources { + m.cfg.EnableMode5(gr.Resource + "." + gr.Group) + } + } + + // Verify mode 5 was enabled (or not) for each resource + for _, resourceName := range tt.resources { + if tt.cfg == nil { + // If cfg is nil, we can't check - just verify we didn't panic + continue + } + config := tt.cfg.UnifiedStorageConfig(resourceName) + if tt.wantMode5Enabled { + require.Equal(t, 5, int(config.DualWriterMode), "%s: %s", tt.description, resourceName) + require.True(t, config.EnableMigration, "%s: EnableMigration should be true for %s", tt.description, resourceName) + require.True(t, config.DualWriterMigrationDataSyncDisabled, "%s: DualWriterMigrationDataSyncDisabled should be true for %s", tt.description, resourceName) + } else { + require.Equal(t, 0, int(config.DualWriterMode), "%s: mode should be 0 for %s", tt.description, resourceName) + } + } + }) + } +} + +// TestResourceMigration_SkipMigrationLog verifies the SkipMigrationLog behavior: +// - When ignoreErrors=true AND errors occurred (hadErrors=true), skip writing to migration log +// This allows the migration to be re-run on the next startup +// - In all other cases, write to migration log normally +// +// This is important for the folders/dashboards migration which uses WithIgnoreErrors() to handle +// partial failures gracefully while still allowing retry on next startup. +func TestResourceMigration_SkipMigrationLog(t *testing.T) { + tests := []struct { + name string + autoMigrate bool + hadErrors bool + want bool + description string + }{ + { + name: "normal migration success", + autoMigrate: false, + hadErrors: false, + want: false, + description: "Normal successful migration should write to log", + }, + { + name: "ignoreErrors migration success", + autoMigrate: true, + hadErrors: false, + want: false, + description: "Migration with ignoreErrors that succeeds should still write to log", + }, + { + name: "normal migration with errors", + autoMigrate: false, + hadErrors: true, + want: false, + description: "Migration that fails without ignoreErrors should write error to log", + }, + { + name: "ignoreErrors migration with errors - skip log", + autoMigrate: true, + hadErrors: true, + want: true, + description: "Migration with ignoreErrors that has errors should SKIP log to allow retry", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &ResourceMigration{ + autoMigrate: tt.autoMigrate, + hadErrors: tt.hadErrors, + } + require.Equal(t, tt.want, m.SkipMigrationLog(), tt.description) + }) + } +} diff --git a/pkg/storage/unified/migrations/service.go b/pkg/storage/unified/migrations/service.go index 4ac9cdad218..8dec7ed019e 100644 --- a/pkg/storage/unified/migrations/service.go +++ b/pkg/storage/unified/migrations/service.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" - "k8s.io/apimachinery/pkg/runtime/schema" ) var tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/unified/migrations") @@ -54,6 +53,7 @@ func (p *UnifiedStorageMigrationServiceImpl) Run(ctx context.Context) error { logger.Info("Data migrations are disabled, skipping") return nil } + logger.Info("Running migrations for unified storage") metrics.MUnifiedStorageMigrationStatus.Set(3) return RegisterMigrations(ctx, p.migrator, p.cfg, p.sqlStore, p.client) @@ -79,7 +79,7 @@ func RegisterMigrations( return err } - if err := registerMigrations(cfg, mg, migrator, client); err != nil { + if err := registerMigrations(ctx, cfg, mg, migrator, client, sqlStore); err != nil { return err } @@ -92,65 +92,13 @@ func RegisterMigrations( db.SetMaxOpenConns(3) defer db.SetMaxOpenConns(maxOpenConns) } - if err := mg.RunMigrations(ctx, + err := mg.RunMigrations(ctx, sec.Key("migration_locking").MustBool(true), - sec.Key("locking_attempt_timeout_sec").MustInt()); err != nil { + sec.Key("locking_attempt_timeout_sec").MustInt()) + if err != nil { return fmt.Errorf("unified storage data migration failed: %w", err) } logger.Info("Unified storage migrations completed successfully") return nil } - -func registerDashboardAndFolderMigration(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { - foldersDef := getResourceDefinition("folder.grafana.app", "folders") - dashboardsDef := getResourceDefinition("dashboard.grafana.app", "dashboards") - driverName := mg.Dialect.DriverName() - - folderCountValidator := NewCountValidator( - client, - foldersDef.GroupResource, - "dashboard", - "org_id = ? and is_folder = true", - driverName, - ) - - dashboardCountValidator := NewCountValidator( - client, - dashboardsDef.GroupResource, - "dashboard", - "org_id = ? and is_folder = false", - driverName, - ) - - folderTreeValidator := NewFolderTreeValidator(client, foldersDef.GroupResource, driverName) - - dashboardsAndFolders := NewResourceMigration( - migrator, - []schema.GroupResource{foldersDef.GroupResource, dashboardsDef.GroupResource}, - "folders-dashboards", - []Validator{folderCountValidator, dashboardCountValidator, folderTreeValidator}, - ) - mg.AddMigration("folders and dashboards migration", dashboardsAndFolders) -} - -func registerPlaylistMigration(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { - playlistsDef := getResourceDefinition("playlist.grafana.app", "playlists") - driverName := mg.Dialect.DriverName() - - playlistCountValidator := NewCountValidator( - client, - playlistsDef.GroupResource, - "playlist", - "org_id = ?", - driverName, - ) - - playlistsMigration := NewResourceMigration( - migrator, - []schema.GroupResource{playlistsDef.GroupResource}, - "playlists", - []Validator{playlistCountValidator}, - ) - mg.AddMigration("playlists migration", playlistsMigration) -} diff --git a/pkg/storage/unified/migrations/threshold/migrator_threshold_test.go b/pkg/storage/unified/migrations/threshold/migrator_threshold_test.go new file mode 100644 index 00000000000..4a542b3ae0b --- /dev/null +++ b/pkg/storage/unified/migrations/threshold/migrator_threshold_test.go @@ -0,0 +1,211 @@ +package threshold + +import ( + "context" + "fmt" + "net/http" + "os" + "testing" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/tests/testsuite" + "github.com/grafana/grafana/pkg/util/testutil" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// TODO: remove this test before Grafana 13 GA +func TestMain(m *testing.M) { + testsuite.Run(m) +} + +// TestIntegrationAutoMigrateThresholdExceeded verifies that auto-migration is skipped when +// resource count exceeds the configured threshold. +// TODO: remove this test before Grafana 13 GA +func TestIntegrationAutoMigrateThresholdExceeded(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + if db.IsTestDbSQLite() { + // Share the same SQLite DB file between steps + tmpDir := t.TempDir() + dbPath := tmpDir + "/shared-threshold-test.db" + + oldVal := os.Getenv("SQLITE_TEST_DB") + require.NoError(t, os.Setenv("SQLITE_TEST_DB", dbPath)) + t.Cleanup(func() { + if oldVal == "" { + _ = os.Unsetenv("SQLITE_TEST_DB") + } else { + _ = os.Setenv("SQLITE_TEST_DB", oldVal) + } + }) + t.Logf("Using shared database path: %s", dbPath) + } + + var org1 *apis.OrgUsers + var orgB *apis.OrgUsers + + dashboardGVR := schema.GroupVersionResource{ + Group: "dashboard.grafana.app", + Version: "v1beta1", + Resource: "dashboards", + } + folderGVR := schema.GroupVersionResource{ + Group: "folder.grafana.app", + Version: "v1beta1", + Resource: "folders", + } + + dashboardKey := fmt.Sprintf("%s.%s", dashboardGVR.Resource, dashboardGVR.Group) + folderKey := fmt.Sprintf("%s.%s", folderGVR.Resource, folderGVR.Group) + playlistKey := "playlists.playlist.grafana.app" + + // Step 1: Create resources exceeding the threshold (3 resources, threshold=1) + t.Run("Step 1: Create resources exceeding threshold", func(t *testing.T) { + unifiedConfig := map[string]setting.UnifiedStorageConfig{} + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + DisableDataMigrations: true, + DisableDBCleanup: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: unifiedConfig, + }) + org1 = &helper.Org1 + orgB = &helper.OrgB + + // Create 3 dashboards + for i := 1; i <= 3; i++ { + createTestDashboard(t, helper, fmt.Sprintf("Threshold Dashboard %d", i)) + } + + // Create 3 folders + for i := 1; i <= 3; i++ { + createTestFolder(t, helper, fmt.Sprintf("folder-%d", i), fmt.Sprintf("Threshold Folder %d", i), "") + } + + // Explicitly shutdown helper before Step 1 ends to ensure database is properly closed + helper.Shutdown() + }) + + // Set SKIP_DB_TRUNCATE to prevent truncation in subsequent steps + oldSkipTruncate := os.Getenv("SKIP_DB_TRUNCATE") + require.NoError(t, os.Setenv("SKIP_DB_TRUNCATE", "true")) + t.Cleanup(func() { + if oldSkipTruncate == "" { + _ = os.Unsetenv("SKIP_DB_TRUNCATE") + } else { + _ = os.Setenv("SKIP_DB_TRUNCATE", oldSkipTruncate) + } + }) + + // Step 2: Verify auto-migration is skipped due to threshold + t.Run("Step 2: Verify auto-migration skipped (threshold exceeded)", func(t *testing.T) { + // Set threshold=1, but we have 3 resources of each type, so migration should be skipped + // Disable playlists migration since we're only testing dashboard/folder threshold behavior + unifiedConfig := map[string]setting.UnifiedStorageConfig{ + dashboardKey: {AutoMigrationThreshold: 1, EnableMigration: false}, + folderKey: {AutoMigrationThreshold: 1, EnableMigration: false}, + playlistKey: {EnableMigration: false}, + } + helper := apis.NewK8sTestHelperWithOpts(t, apis.K8sTestHelperOpts{ + GrafanaOpts: testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + DisableDataMigrations: false, // Allow migration system to run + APIServerStorageType: "unified", + UnifiedStorageConfig: unifiedConfig, + }, + Org1Users: org1, + OrgBUsers: orgB, + }) + t.Cleanup(helper.Shutdown) + + namespace := authlib.OrgNamespaceFormatter(helper.Org1.OrgID) + + dashCli := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: namespace, + GVR: dashboardGVR, + }) + verifyResourceCount(t, dashCli, 3) + + folderCli := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: namespace, + GVR: folderGVR, + }) + verifyResourceCount(t, folderCli, 3) + + // Verify migration did NOT run by checking the migration log + count, err := helper.GetEnv().SQLStore.GetEngine().Table("unifiedstorage_migration_log"). + Where("migration_id = ?", "folders and dashboards migration"). + Count() + require.NoError(t, err) + require.Equal(t, int64(0), count, "Migration should not have run") + }) +} + +func createTestDashboard(t *testing.T, helper *apis.K8sTestHelper, title string) string { + t.Helper() + + payload := fmt.Sprintf(`{"dashboard": {"title": "%s", "panels": []}, "overwrite": false}`, title) + + result := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "POST", + Path: "/api/dashboards/db", + Body: []byte(payload), + }, &map[string]interface{}{}) + + require.NotNil(t, result.Response) + require.Equal(t, 200, result.Response.StatusCode) + + uid := (*result.Result)["uid"].(string) + require.NotEmpty(t, uid) + return uid +} + +func createTestFolder(t *testing.T, helper *apis.K8sTestHelper, uid, title, parentUID string) *folder.Folder { + t.Helper() + + payload := fmt.Sprintf(`{ + "title": "%s", + "uid": "%s"`, title, uid) + + if parentUID != "" { + payload += fmt.Sprintf(`, + "parentUid": "%s"`, parentUID) + } + + payload += "}" + + folderCreate := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(payload), + }, &folder.Folder{}) + + require.NotNil(t, folderCreate.Result) + return folderCreate.Result +} + +// verifyResourceCount verifies that the expected number of resources exist in K8s storage +func verifyResourceCount(t *testing.T, client *apis.K8sResourceClient, expectedCount int) { + t.Helper() + + l, err := client.Resource.List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + + resources, err := meta.ExtractList(l) + require.NoError(t, err) + require.Equal(t, expectedCount, len(resources)) +} diff --git a/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql b/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql index 8372eb73463..72026e26362 100644 --- a/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql +++ b/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql @@ -12,7 +12,7 @@ INSERT INTO {{ .Ident .TableName }} VALUES ( {{ .Arg .GUID }}, {{ .Arg .KeyPath }}, - COALESCE({{ .Arg .Value }}, ""), + {{ .Arg .Value }}, {{ .Arg .Group }}, {{ .Arg .Resource }}, {{ .Arg .Namespace }}, diff --git a/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql index 437d3ae9107..77680d190ff 100644 --- a/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql +++ b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql @@ -10,7 +10,7 @@ INSERT INTO {{ .Ident "resource_history" }} {{ .Ident "folder" }} ) VALUES ( - COALESCE({{ .Arg .Value }}, ""), + {{ .Arg .Value }}, {{ .Arg .GUID }}, {{ .Arg .Group }}, {{ .Arg .Resource }}, diff --git a/pkg/storage/unified/resource/data/sqlkv_save_event.sql b/pkg/storage/unified/resource/data/sqlkv_save_event.sql index 669497dbb19..0091c6e1347 100644 --- a/pkg/storage/unified/resource/data/sqlkv_save_event.sql +++ b/pkg/storage/unified/resource/data/sqlkv_save_event.sql @@ -5,7 +5,7 @@ INSERT INTO {{ .Ident .TableName }} ) VALUES ( {{ .Arg .KeyPath }}, - COALESCE({{ .Arg .Value }}, "") + {{ .Arg .Value }} ) {{- if eq .DialectName "mysql" }} ON DUPLICATE KEY UPDATE {{ .Ident "value" }} = {{ .Arg .Value }} diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index bae3c776d79..73f3f5aea74 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -349,6 +349,11 @@ func (w *sqlWriteCloser) Close() error { } w.closed = true + value := w.buf.Bytes() + if value == nil { + // to prevent NOT NULL constraint violations + value = []byte{} + } // do regular kv save: simple key_path + value insert with conflict check. // can only do this on resource_events for now, until we drop the columns in resource_history @@ -356,7 +361,7 @@ func (w *sqlWriteCloser) Close() error { _, err := dbutil.Exec(w.ctx, w.kv.db, sqlKVSaveEvent, sqlKVSaveRequest{ SQLTemplate: sqltemplate.New(w.kv.dialect), sqlKVSectionKey: w.sectionKey, - Value: w.buf.Bytes(), + Value: value, }) if err != nil { @@ -380,7 +385,7 @@ func (w *sqlWriteCloser) Close() error { SQLTemplate: sqltemplate.New(w.kv.dialect), sqlKVSectionKey: w.sectionKey, GUID: uuid.New().String(), - Value: w.buf.Bytes(), + Value: value, }) if err != nil { @@ -397,7 +402,7 @@ func (w *sqlWriteCloser) Close() error { _, err = dbutil.Exec(w.ctx, w.kv.db, sqlKVUpdateData, sqlKVSaveRequest{ SQLTemplate: sqltemplate.New(w.kv.dialect), sqlKVSectionKey: w.sectionKey, - Value: w.buf.Bytes(), + Value: value, }) if err != nil { @@ -433,7 +438,7 @@ func (w *sqlWriteCloser) Close() error { _, err = dbutil.Exec(w.ctx, tx, sqlKVInsertLegacyResourceHistory, sqlKVSaveRequest{ SQLTemplate: sqltemplate.New(w.kv.dialect), sqlKVSectionKey: w.sectionKey, - Value: w.buf.Bytes(), + Value: value, GUID: dataKey.GUID, Group: dataKey.Group, Resource: dataKey.Resource, diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index 0f19ae01d93..8f9be689718 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -217,5 +217,8 @@ func initResourceTables(mg *migrator.Migrator) string { migrator.ConvertUniqueKeyToPrimaryKey(mg, oldResourceVersionUniqueKey, updatedResourceVersionTable) + mg.AddMigration("Change key_path collation of resource_history in postgres", migrator.NewRawSQLMigration("").Postgres(`ALTER TABLE resource_history ALTER COLUMN key_path TYPE VARCHAR(2048) COLLATE "C";`)) + mg.AddMigration("Change key_path collation of resource_events in postgres", migrator.NewRawSQLMigration("").Postgres(`ALTER TABLE resource_events ALTER COLUMN key_path TYPE VARCHAR(2048) COLLATE "C";`)) + return marker } diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index dab9f3cd8b1..f03b3c3369b 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -87,7 +87,7 @@ "tags": [ "ExternalGroupMapping" ], - "description": "list or watch objects of kind ExternalGroupMapping", + "description": "list objects of kind ExternalGroupMapping", "operationId": "listExternalGroupMapping", "parameters": [ { @@ -8690,32 +8690,6 @@ "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", "type": "string", "format": "date-time" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "type": "object", - "required": [ - "type", - "object" - ], - "properties": { - "object": { - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ] - }, - "type": { - "type": "string", - "default": "" - } - } - }, - "io.k8s.apimachinery.pkg.runtime.RawExtension": { - "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", - "type": "object" } } } diff --git a/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json deleted file mode 100644 index 017323b2a1e..00000000000 --- a/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json +++ /dev/null @@ -1,2959 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "investigations.grafana.app/v0alpha1" - }, - "paths": { - "/apis/investigations.grafana.app/v0alpha1/": { - "get": { - "tags": [ - "API Discovery" - ], - "description": "Describe the available kubernetes resources", - "operationId": "getAPIResources", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - } - } - } - } - } - }, - "/apis/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigationindexes": { - "get": { - "tags": [ - "InvestigationIndex" - ], - "description": "list or watch objects of kind InvestigationIndex", - "operationId": "listInvestigationIndex", - "parameters": [ - { - "name": "allowWatchBookmarks", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "continue", - "in": "query", - "description": "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\".\n\nThis 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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "labelSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "limit", - "in": "query", - "description": "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.\n\nThe 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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "resourceVersion", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersionMatch", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "sendInitialEvents", - "in": "query", - "description": "`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.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "timeoutSeconds", - "in": "query", - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "watch", - "in": "query", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList" - } - } - } - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "post": { - "tags": [ - "InvestigationIndex" - ], - "description": "create an InvestigationIndex", - "operationId": "createInvestigationIndex", - "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.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - }, - "202": { - "description": "Accepted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "delete": { - "tags": [ - "InvestigationIndex" - ], - "description": "delete collection of InvestigationIndex", - "operationId": "deletecollectionInvestigationIndex", - "parameters": [ - { - "name": "continue", - "in": "query", - "description": "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\".\n\nThis 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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "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": "fieldSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "gracePeriodSeconds", - "in": "query", - "description": "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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "in": "query", - "description": "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", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "labelSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "limit", - "in": "query", - "description": "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.\n\nThe 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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "orphanDependents", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "propagationPolicy", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersion", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersionMatch", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "sendInitialEvents", - "in": "query", - "description": "`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.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "timeoutSeconds", - "in": "query", - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "schema": { - "type": "integer", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "parameters": [ - { - "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/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigationindexes/{name}": { - "get": { - "tags": [ - "InvestigationIndex" - ], - "description": "read the specified InvestigationIndex", - "operationId": "getInvestigationIndex", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "put": { - "tags": [ - "InvestigationIndex" - ], - "description": "replace the specified InvestigationIndex", - "operationId": "replaceInvestigationIndex", - "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.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "delete": { - "tags": [ - "InvestigationIndex" - ], - "description": "delete an InvestigationIndex", - "operationId": "deleteInvestigationIndex", - "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": "gracePeriodSeconds", - "in": "query", - "description": "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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "in": "query", - "description": "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", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "orphanDependents", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "propagationPolicy", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - }, - "202": { - "description": "Accepted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "patch": { - "tags": [ - "InvestigationIndex" - ], - "description": "partially update the specified InvestigationIndex", - "operationId": "updateInvestigationIndex", - "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.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "InvestigationIndex" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the InvestigationIndex", - "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/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigations": { - "get": { - "tags": [ - "Investigation" - ], - "description": "list or watch objects of kind Investigation", - "operationId": "listInvestigation", - "parameters": [ - { - "name": "allowWatchBookmarks", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "continue", - "in": "query", - "description": "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\".\n\nThis 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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "labelSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "limit", - "in": "query", - "description": "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.\n\nThe 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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "resourceVersion", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersionMatch", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "sendInitialEvents", - "in": "query", - "description": "`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.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "timeoutSeconds", - "in": "query", - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "watch", - "in": "query", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList" - } - } - } - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "post": { - "tags": [ - "Investigation" - ], - "description": "create an Investigation", - "operationId": "createInvestigation", - "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.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - }, - "202": { - "description": "Accepted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "delete": { - "tags": [ - "Investigation" - ], - "description": "delete collection of Investigation", - "operationId": "deletecollectionInvestigation", - "parameters": [ - { - "name": "continue", - "in": "query", - "description": "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\".\n\nThis 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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "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": "fieldSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "gracePeriodSeconds", - "in": "query", - "description": "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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "in": "query", - "description": "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", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "labelSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "limit", - "in": "query", - "description": "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.\n\nThe 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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "orphanDependents", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "propagationPolicy", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersion", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersionMatch", - "in": "query", - "description": "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.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "sendInitialEvents", - "in": "query", - "description": "`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.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "timeoutSeconds", - "in": "query", - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "schema": { - "type": "integer", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "parameters": [ - { - "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/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigations/{name}": { - "get": { - "tags": [ - "Investigation" - ], - "description": "read the specified Investigation", - "operationId": "getInvestigation", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "put": { - "tags": [ - "Investigation" - ], - "description": "replace the specified Investigation", - "operationId": "replaceInvestigation", - "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.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "delete": { - "tags": [ - "Investigation" - ], - "description": "delete an Investigation", - "operationId": "deleteInvestigation", - "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": "gracePeriodSeconds", - "in": "query", - "description": "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.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "in": "query", - "description": "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", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "orphanDependents", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "propagationPolicy", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - }, - "202": { - "description": "Accepted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - } - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "patch": { - "tags": [ - "Investigation" - ], - "description": "partially update the specified Investigation", - "operationId": "updateInvestigation", - "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.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "investigations.grafana.app", - "version": "v0alpha1", - "kind": "Investigation" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the Investigation", - "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": { - "schemas": { - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation": { - "type": "object", - "required": [ - "metadata", - "spec", - "status" - ], - "properties": { - "apiVersion": { - "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" - }, - "kind": { - "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" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "description": "Spec is the spec of the Investigation", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationSpec" - } - ] - }, - "status": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationStatus" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "investigations.grafana.app", - "kind": "Investigation", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationCollectable": { - "description": "Collectable represents an item collected during investigation", - "type": "object", - "required": [ - "id", - "createdAt", - "title", - "origin", - "type", - "queries", - "timeRange", - "datasource", - "url", - "note", - "noteUpdatedAt", - "fieldConfig" - ], - "properties": { - "createdAt": { - "type": "string", - "default": "" - }, - "datasource": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationDatasourceRef" - } - ] - }, - "fieldConfig": { - "type": "string", - "default": "" - }, - "id": { - "type": "string", - "default": "" - }, - "logoPath": { - "type": "string" - }, - "note": { - "type": "string", - "default": "" - }, - "noteUpdatedAt": { - "type": "string", - "default": "" - }, - "origin": { - "type": "string", - "default": "" - }, - "queries": { - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "atomic" - }, - "timeRange": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationTimeRange" - } - ] - }, - "title": { - "type": "string", - "default": "" - }, - "type": { - "type": "string", - "default": "" - }, - "url": { - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationDatasourceRef": { - "description": "DatasourceRef is a reference to a datasource", - "type": "object", - "required": [ - "uid" - ], - "properties": { - "uid": { - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex": { - "type": "object", - "required": [ - "metadata", - "spec", - "status" - ], - "properties": { - "apiVersion": { - "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" - }, - "kind": { - "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" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "description": "Spec is the spec of the InvestigationIndex", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexSpec" - } - ] - }, - "status": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexStatus" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "investigations.grafana.app", - "kind": "InvestigationIndex", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexCollectableSummary": { - "type": "object", - "required": [ - "id", - "title", - "logoPath", - "origin" - ], - "properties": { - "id": { - "type": "string", - "default": "" - }, - "logoPath": { - "type": "string", - "default": "" - }, - "origin": { - "type": "string", - "default": "" - }, - "title": { - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexInvestigationSummary": { - "description": "Type definition for investigation summaries", - "type": "object", - "required": [ - "title", - "createdByProfile", - "hasCustomName", - "isFavorite", - "overviewNote", - "overviewNoteUpdatedAt", - "viewMode", - "collectableSummaries" - ], - "properties": { - "collectableSummaries": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexCollectableSummary" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "createdByProfile": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexPerson" - } - ] - }, - "hasCustomName": { - "type": "boolean", - "default": false - }, - "isFavorite": { - "type": "boolean", - "default": false - }, - "overviewNote": { - "type": "string", - "default": "" - }, - "overviewNoteUpdatedAt": { - "type": "string", - "default": "" - }, - "title": { - "type": "string", - "default": "" - }, - "viewMode": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexViewMode" - } - ] - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexList": { - "type": "object", - "required": [ - "metadata", - "items" - ], - "properties": { - "apiVersion": { - "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" - }, - "items": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" - } - ] - } - }, - "kind": { - "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" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "investigations.grafana.app", - "kind": "InvestigationIndexList", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexPerson": { - "description": "Person represents a user profile with basic information", - "type": "object", - "required": [ - "uid", - "name", - "gravatarUrl" - ], - "properties": { - "gravatarUrl": { - "description": "URL to user's Gravatar image", - "type": "string", - "default": "" - }, - "name": { - "description": "Display name of the user", - "type": "string", - "default": "" - }, - "uid": { - "description": "Unique identifier for the user", - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexSpec": { - "type": "object", - "required": [ - "title", - "owner", - "investigationSummaries" - ], - "properties": { - "investigationSummaries": { - "description": "Array of investigation summaries", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexInvestigationSummary" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "owner": { - "description": "The Person who owns this investigation index", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexPerson" - } - ] - }, - "title": { - "description": "Title of the index, e.g. 'Favorites' or 'My Investigations'", - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "operatorStates": { - "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": "object", - "additionalProperties": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexstatusOperatorState" - } - ] - } - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexViewMode": { - "type": "object", - "required": [ - "mode", - "showComments", - "showTooltips" - ], - "properties": { - "mode": { - "type": "string", - "default": "" - }, - "showComments": { - "type": "boolean", - "default": false - }, - "showTooltips": { - "type": "boolean", - "default": false - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndexstatusOperatorState": { - "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" - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string", - "default": "" - }, - "state": { - "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationList": { - "type": "object", - "required": [ - "metadata", - "items" - ], - "properties": { - "apiVersion": { - "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" - }, - "items": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" - } - ] - } - }, - "kind": { - "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" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "investigations.grafana.app", - "kind": "InvestigationList", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationPerson": { - "description": "Person represents a user profile with basic information", - "type": "object", - "required": [ - "uid", - "name", - "gravatarUrl" - ], - "properties": { - "gravatarUrl": { - "description": "URL to user's Gravatar image", - "type": "string", - "default": "" - }, - "name": { - "description": "Display name of the user", - "type": "string", - "default": "" - }, - "uid": { - "description": "Unique identifier for the user", - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationSpec": { - "description": "spec is the schema of our resource", - "type": "object", - "required": [ - "title", - "createdByProfile", - "hasCustomName", - "isFavorite", - "overviewNote", - "overviewNoteUpdatedAt", - "collectables", - "viewMode" - ], - "properties": { - "collectables": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationCollectable" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "createdByProfile": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationPerson" - } - ] - }, - "hasCustomName": { - "type": "boolean", - "default": false - }, - "isFavorite": { - "type": "boolean", - "default": false - }, - "overviewNote": { - "type": "string", - "default": "" - }, - "overviewNoteUpdatedAt": { - "type": "string", - "default": "" - }, - "title": { - "type": "string", - "default": "" - }, - "viewMode": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationViewMode" - } - ] - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "operatorStates": { - "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": "object", - "additionalProperties": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationstatusOperatorState" - } - ] - } - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationTimeRange": { - "description": "TimeRange represents a time range with both absolute and relative values", - "type": "object", - "required": [ - "from", - "to", - "raw" - ], - "properties": { - "from": { - "type": "string", - "default": "" - }, - "raw": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationV0alpha1TimeRangeRaw" - } - ] - }, - "to": { - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationV0alpha1TimeRangeRaw": { - "type": "object", - "required": [ - "from", - "to" - ], - "properties": { - "from": { - "type": "string", - "default": "" - }, - "to": { - "type": "string", - "default": "" - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationViewMode": { - "type": "object", - "required": [ - "mode", - "showComments", - "showTooltips" - ], - "properties": { - "mode": { - "type": "string", - "default": "" - }, - "showComments": { - "type": "boolean", - "default": false - }, - "showTooltips": { - "type": "boolean", - "default": false - } - } - }, - "com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationstatusOperatorState": { - "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" - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string", - "default": "" - }, - "state": { - "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - "type": "string", - "default": "" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "type": "object", - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "properties": { - "categories": { - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "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\".", - "type": "string" - }, - "kind": { - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", - "type": "string", - "default": "" - }, - "name": { - "description": "name is the plural name of the resource.", - "type": "string", - "default": "" - }, - "namespaced": { - "description": "namespaced indicates if a resource is namespaced or not.", - "type": "boolean", - "default": false - }, - "shortNames": { - "description": "shortNames is a list of suggested short names of the resource.", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "atomic" - }, - "singularName": { - "description": "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.", - "type": "string", - "default": "" - }, - "storageVersionHash": { - "description": "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.", - "type": "string" - }, - "verbs": { - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", - "type": "array", - "items": { - "type": "string", - "default": "" - } - }, - "version": { - "description": "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)\".", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList": { - "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", - "type": "object", - "required": [ - "groupVersion", - "resources" - ], - "properties": { - "apiVersion": { - "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" - }, - "groupVersion": { - "description": "groupVersion is the group and version this APIResourceList is for.", - "type": "string", - "default": "" - }, - "kind": { - "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" - }, - "resources": { - "description": "resources contains the name of the resources and if they are namespaced.", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" - } - ] - }, - "x-kubernetes-list-type": "atomic" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions": { - "description": "DeleteOptions may be provided when deleting an API object.", - "type": "object", - "properties": { - "apiVersion": { - "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" - }, - "dryRun": { - "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", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "atomic" - }, - "gracePeriodSeconds": { - "description": "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.", - "type": "integer", - "format": "int64" - }, - "ignoreStoreReadErrorWithClusterBreakingPotential": { - "description": "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", - "type": "boolean" - }, - "kind": { - "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" - }, - "orphanDependents": { - "description": "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.", - "type": "boolean" - }, - "preconditions": { - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" - } - ] - }, - "propagationPolicy": { - "description": "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.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:\u003cname\u003e', where \u003cname\u003e is the name of a field in a struct, or key in a map 'v:\u003cvalue\u003e', where \u003cvalue\u003e is the exact json formatted value of a list item 'i:\u003cindex\u003e', where \u003cindex\u003e is position of a item in a list 'k:\u003ckeys\u003e', where \u003ckeys\u003e is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { - "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", - "type": "object", - "properties": { - "continue": { - "description": "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.", - "type": "string" - }, - "remainingItemCount": { - "description": "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.", - "type": "integer", - "format": "int64" - }, - "resourceVersion": { - "description": "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", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { - "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", - "type": "object", - "properties": { - "apiVersion": { - "description": "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.", - "type": "string" - }, - "fieldsType": { - "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", - "type": "string" - }, - "fieldsV1": { - "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" - } - ] - }, - "manager": { - "description": "Manager is an identifier of the workflow managing these fields.", - "type": "string" - }, - "operation": { - "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", - "type": "string" - }, - "subresource": { - "description": "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.", - "type": "string" - }, - "time": { - "description": "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.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ] - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "type": "object", - "properties": { - "annotations": { - "description": "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", - "type": "object", - "additionalProperties": { - "type": "string", - "default": "" - } - }, - "creationTimestamp": { - "description": "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.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ] - }, - "deletionGracePeriodSeconds": { - "description": "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.", - "type": "integer", - "format": "int64" - }, - "deletionTimestamp": { - "description": "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.\n\nPopulated 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", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ] - }, - "finalizers": { - "description": "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.", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "set", - "x-kubernetes-patch-strategy": "merge" - }, - "generateName": { - "description": "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.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", - "type": "string" - }, - "generation": { - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", - "type": "integer", - "format": "int64" - }, - "labels": { - "description": "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", - "type": "object", - "additionalProperties": { - "type": "string", - "default": "" - } - }, - "managedFields": { - "description": "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.", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "name": { - "description": "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", - "type": "string" - }, - "namespace": { - "description": "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.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", - "type": "string" - }, - "ownerReferences": { - "description": "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.", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" - } - ] - }, - "x-kubernetes-list-map-keys": [ - "uid" - ], - "x-kubernetes-list-type": "map", - "x-kubernetes-patch-merge-key": "uid", - "x-kubernetes-patch-strategy": "merge" - }, - "resourceVersion": { - "description": "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.\n\nPopulated 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", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - }, - "uid": { - "description": "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.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { - "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", - "type": "object", - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "properties": { - "apiVersion": { - "description": "API version of the referent.", - "type": "string", - "default": "" - }, - "blockOwnerDeletion": { - "description": "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.", - "type": "boolean" - }, - "controller": { - "description": "If true, this reference points to the managing controller.", - "type": "boolean" - }, - "kind": { - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string", - "default": "" - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", - "type": "string", - "default": "" - }, - "uid": { - "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string", - "default": "" - } - }, - "x-kubernetes-map-type": "atomic" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "type": "object", - "properties": { - "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "type": "object", - "properties": { - "apiVersion": { - "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" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "type": "integer", - "format": "int32" - }, - "details": { - "description": "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.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "x-kubernetes-list-type": "atomic" - }, - "kind": { - "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" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - }, - "reason": { - "description": "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.", - "type": "string" - }, - "status": { - "description": "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", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "type": "object", - "properties": { - "field": { - "description": "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.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "type": "object", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "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", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "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.", - "type": "integer", - "format": "int32" - }, - "uid": { - "description": "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", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "type": "string", - "format": "date-time" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "type": "object", - "required": [ - "type", - "object" - ], - "properties": { - "object": { - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ] - }, - "type": { - "type": "string", - "default": "" - } - } - }, - "io.k8s.apimachinery.pkg.runtime.RawExtension": { - "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", - "type": "object" - } - } - } -} \ No newline at end of file diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index d4764b742f1..0e2f06946ac 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -866,6 +866,80 @@ } ] }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections/{name}/repositories": { + "get": { + "tags": [ + "Connection" + ], + "summary": "List external repositories", + "description": "List repositories available from the external git provider through this connection", + "operationId": "getConnectionRepositories", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "description": "ExternalRepositoryList lists repositories from an external git provider", + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "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" + }, + "items": { + "type": "array", + "items": { + "default": {} + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "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" + }, + "metadata": { + "default": {} + } + } + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ExternalRepositoryList" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the ExternalRepositoryList", + "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 + } + } + ] + }, "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections/{name}/status": { "get": { "tags": [ @@ -4645,6 +4719,73 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ExternalRepository": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "description": "Name of the repository", + "type": "string", + "default": "" + }, + "owner": { + "description": "Owner is the user, organization, or workspace that owns the repository For GitHub: organization or user For GitLab: namespace (user or group) For Bitbucket: workspace For pure Git: empty", + "type": "string" + }, + "url": { + "description": "URL of the repository", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ExternalRepositoryList": { + "description": "ExternalRepositoryList lists repositories from an external git provider", + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "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" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ExternalRepository" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "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" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "ExternalRepositoryList", + "version": "v0alpha1" + } + ] + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.FileItem": { "type": "object", "required": [ diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index f9d5cdb70e5..131634e6805 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -30,7 +30,6 @@ func TestIntegrationOpenAPIs(t *testing.T) { EnableFeatureToggles: []string{ featuremgmt.FlagQueryService, // Query Library featuremgmt.FlagProvisioning, - featuremgmt.FlagInvestigationsBackend, featuremgmt.FlagGrafanaAdvisor, featuremgmt.FlagKubernetesAlertingRules, featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // all datasources @@ -97,9 +96,6 @@ func TestIntegrationOpenAPIs(t *testing.T) { }, { Group: "iam.grafana.app", Version: "v0alpha1", - }, { - Group: "investigations.grafana.app", - Version: "v0alpha1", }, { Group: "advisor.grafana.app", Version: "v0alpha1", diff --git a/pkg/tests/apis/provisioning/connection_repositories_test.go b/pkg/tests/apis/provisioning/connection_repositories_test.go new file mode 100644 index 00000000000..e6ef823b801 --- /dev/null +++ b/pkg/tests/apis/provisioning/connection_repositories_test.go @@ -0,0 +1,172 @@ +package provisioning + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationProvisioning_ConnectionRepositories(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + + // Create a connection for testing + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection-repositories-test", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.NoError(t, err, "failed to create connection") + + t.Run("endpoint returns not implemented", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-repositories-test"). + SubResource("repositories"). + Do(ctx). + StatusCode(&statusCode) + + require.Error(t, result.Error(), "should return error for not implemented endpoint") + require.Equal(t, http.StatusMethodNotAllowed, statusCode, "should return 405 Method Not Allowed") + require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported") + }) + + t.Run("admin can access endpoint (gets not implemented)", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-repositories-test"). + SubResource("repositories"). + Do(ctx).StatusCode(&statusCode) + + // Endpoint exists but returns not implemented + require.Error(t, result.Error(), "should return error") + require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported") + // Status code should be 405 (Method Not Allowed) for method not supported + require.Equal(t, http.StatusMethodNotAllowed, statusCode) + }) + + t.Run("editor cannot access endpoint", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-repositories-test"). + SubResource("repositories"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to access repositories endpoint") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot access endpoint", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-repositories-test"). + SubResource("repositories"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to access repositories endpoint") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("non-GET methods are rejected", func(t *testing.T) { + configBytes, _ := json.Marshal(map[string]any{}) + + var statusCode int + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("connections"). + Name("connection-repositories-test"). + SubResource("repositories"). + Body(configBytes). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "POST should not be allowed") + require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported") + }) +} + +func TestIntegrationProvisioning_ConnectionRepositoriesResponseType(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + + // Create a connection for testing + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection-repositories-type-test", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.NoError(t, err, "failed to create connection") + + t.Run("verify ExternalRepositoryList type exists in API", func(t *testing.T) { + // Verify the type is registered and can be instantiated + list := &provisioning.ExternalRepositoryList{} + require.NotNil(t, list) + // Verify it has the expected structure (Items is a slice, nil by default is fine) + require.IsType(t, []provisioning.ExternalRepository{}, list.Items) + // Can create items + list.Items = []provisioning.ExternalRepository{ + {Name: "test", Owner: "owner", URL: "https://example.com/repo"}, + } + require.Len(t, list.Items, 1) + require.Equal(t, "test", list.Items[0].Name) + }) +} diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index b5e04d65a40..17f1e9d84b2 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -557,6 +557,8 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) _, err = section.NewKey("enableMigration", fmt.Sprintf("%t", v.EnableMigration)) require.NoError(t, err) + _, err = section.NewKey("autoMigrationThreshold", fmt.Sprintf("%d", v.AutoMigrationThreshold)) + require.NoError(t, err) } } if opts.UnifiedStorageEnableSearch { diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 9f09c0135d7..ca681c5a8a9 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -6990,6 +6990,9 @@ "ReportOptions": { "type": "object", "properties": { + "csvEncoding": { + "type": "string" + }, "layout": { "type": "string" }, diff --git a/public/api-merged.json b/public/api-merged.json index 642583d2600..f8d7c8efef2 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -20504,6 +20504,9 @@ "ReportOptions": { "type": "object", "properties": { + "csvEncoding": { + "type": "string" + }, "layout": { "type": "string" }, diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx index e340e8276b8..131b2f48ea2 100644 --- a/public/app/core/components/AppChrome/AppChromeService.tsx +++ b/public/app/core/components/AppChrome/AppChromeService.tsx @@ -10,11 +10,8 @@ import { isShallowEqual } from 'app/core/utils/isShallowEqual'; import { KioskMode } from 'app/types/dashboard'; import { RouteDescriptor } from '../../navigation/types'; -import { buildBreadcrumbs } from '../Breadcrumbs/utils'; -import { logDuplicateUnifiedHistoryEntryEvent } from './History/eventsTracking'; import { ReturnToPreviousProps } from './ReturnToPrevious/ReturnToPrevious'; -import { HistoryEntry } from './types'; export interface AppChromeState { chromeless?: boolean; @@ -34,7 +31,6 @@ export interface AppChromeState { export const DOCKED_LOCAL_STORAGE_KEY = 'grafana.navigation.docked'; export const DOCKED_MENU_OPEN_LOCAL_STORAGE_KEY = 'grafana.navigation.open'; -export const HISTORY_LOCAL_STORAGE_KEY = 'grafana.navigation.history'; export class AppChromeService { searchBarStorageKey = 'SearchBar_Hidden'; @@ -88,8 +84,6 @@ export class AppChromeService { newState.chromeless = newState.kioskMode === KioskMode.Full || this.currentRoute?.chromeless; if (!this.ignoreStateUpdate(newState, current)) { - config.featureToggles.unifiedHistory && - store.setObject(HISTORY_LOCAL_STORAGE_KEY, this.getUpdatedHistory(newState)); this.state.next(newState); } } @@ -118,40 +112,6 @@ export class AppChromeService { window.sessionStorage.removeItem('returnToPrevious'); }; - private getUpdatedHistory(newState: AppChromeState): HistoryEntry[] { - const breadcrumbs = buildBreadcrumbs(newState.sectionNav.node, newState.pageNav, { text: 'Home', url: '/' }); - const newPageNav = newState.pageNav || newState.sectionNav.node; - - let entries = store.getObject(HISTORY_LOCAL_STORAGE_KEY, []); - const clickedHistory = store.getObject('CLICKING_HISTORY'); - if (clickedHistory) { - store.setObject('CLICKING_HISTORY', false); - return entries; - } - if (!newPageNav) { - return entries; - } - - const lastEntry = entries[0]; - const newEntry = { name: newPageNav.text, views: [], breadcrumbs, time: Date.now(), url: window.location.href }; - const isSamePath = lastEntry && newEntry.url.split('?')[0] === lastEntry.url.split('?')[0]; - - // To avoid adding an entry with the same path twice, we always use the latest one - if (isSamePath) { - entries[0] = newEntry; - } else { - if (lastEntry && lastEntry.name === newEntry.name) { - logDuplicateUnifiedHistoryEntryEvent({ - entryName: newEntry.name, - lastEntryURL: lastEntry.url, - newEntryURL: newEntry.url, - }); - } - entries = [newEntry, ...entries]; - } - - return entries; - } private ignoreStateUpdate(newState: AppChromeState, current: AppChromeState) { if (isShallowEqual(newState, current)) { return true; diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx index c6c8ee37239..3efee947d5b 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx @@ -26,7 +26,7 @@ const mockDifferentComponent = { } as ExtensionInfo; const mockPluginMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [mockComponent, mockDifferentComponent], addedLinks: [], }; @@ -187,7 +187,7 @@ describe('ExtensionSidebarProvider', () => { it('should only include permitted plugins in available components', () => { const permittedPluginMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [mockComponent], addedLinks: [], }; @@ -256,7 +256,7 @@ describe('ExtensionSidebarProvider', () => { // Call it directly with the test event subscriberFn( new OpenExtensionSidebarEvent({ - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', componentTitle: 'Test Component', props: { testProp: 'test value' }, }) @@ -266,7 +266,7 @@ describe('ExtensionSidebarProvider', () => { expect(screen.getByTestId('is-open')).toHaveTextContent('true'); expect(screen.getByTestId('props')).toHaveTextContent('{"testProp":"test value"}'); const expectedComponentId = JSON.stringify({ - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', componentTitle: 'Test Component', }); expect(screen.getByTestId('docked-component-id')).toHaveTextContent(expectedComponentId); @@ -381,7 +381,7 @@ describe('ExtensionSidebarProvider', () => { subscriberFn( new ToggleExtensionSidebarEvent({ - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', componentTitle: 'Test Component', props: { testProp: 'test value' }, }) @@ -392,7 +392,7 @@ describe('ExtensionSidebarProvider', () => { expect(screen.getByTestId('is-open')).toHaveTextContent('true'); expect(screen.getByTestId('props')).toHaveTextContent('{"testProp":"test value"}'); const expectedComponentId = JSON.stringify({ - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', componentTitle: 'Test Component', }); expect(screen.getByTestId('docked-component-id')).toHaveTextContent(expectedComponentId); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 51d73d2e8e5..07f716d1a7d 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -11,7 +11,6 @@ import { DEFAULT_EXTENSION_SIDEBAR_WIDTH, MAX_EXTENSION_SIDEBAR_WIDTH } from './ export const EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY = 'grafana.navigation.extensionSidebarDocked'; export const EXTENSION_SIDEBAR_WIDTH_LOCAL_STORAGE_KEY = 'grafana.navigation.extensionSidebarWidth'; const PERMITTED_EXTENSION_SIDEBAR_PLUGINS = [ - 'grafana-investigations-app', 'grafana-assistant-app', 'grafana-dash-app', // The docs plugin ID is transitioning from grafana-grafanadocsplugin-app to grafana-pathfinder-app. diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx index 8fdb8b5eccb..cb717322af7 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx @@ -44,7 +44,7 @@ const mockComponent = { }; const mockPluginMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [mockComponent], }; @@ -109,7 +109,7 @@ describe('ExtensionToolbarItem', () => { it('should render a dropdown menu when multiple components are available', async () => { const multipleComponentsMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [ { ...mockComponent, title: 'Component 1' }, { ...mockComponent, title: 'Component 2' }, @@ -141,7 +141,7 @@ describe('ExtensionToolbarItem', () => { it('should show menu items when clicking the dropdown button', async () => { const multipleComponentsMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [ { ...mockComponent, title: 'Component 1' }, { ...mockComponent, title: 'Component 2' }, @@ -165,7 +165,7 @@ describe('ExtensionToolbarItem', () => { it('should toggle the sidebar when clicking a menu item', async () => { const multipleComponentsMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [ { ...mockComponent, title: 'Component 1' }, { ...mockComponent, title: 'Component 2' }, @@ -192,7 +192,7 @@ describe('ExtensionToolbarItem', () => { it('should close the sidebar when clicking an active menu item', async () => { const multipleComponentsMeta = { - pluginId: 'grafana-investigations-app', + pluginId: 'grafana-assistant-app', addedComponents: [ { ...mockComponent, title: 'Component 1' }, { ...mockComponent, title: 'Component 2' }, @@ -218,13 +218,13 @@ describe('ExtensionToolbarItem', () => { it('should render individual buttons when multiple plugins are available', async () => { const plugin1Meta = { - pluginId: 'grafana-investigations-app', - addedComponents: [{ ...mockComponent, title: 'Investigations' }], + pluginId: 'grafana-assistant-app', + addedComponents: [{ ...mockComponent, title: 'Assistant' }], }; const plugin2Meta = { - pluginId: 'grafana-assistant-app', - addedComponents: [{ ...mockComponent, title: 'Assistant' }], + pluginId: 'grafana-dash-app', + addedComponents: [{ ...mockComponent, title: 'Dash' }], }; (usePluginLinks as jest.Mock).mockReturnValue({ @@ -249,7 +249,7 @@ describe('ExtensionToolbarItem', () => { expect(buttons).toHaveLength(2); // Each button should have the correct title - expect(buttons[0]).toHaveAttribute('aria-label', 'Open Investigations'); - expect(buttons[1]).toHaveAttribute('aria-label', 'Open Assistant'); + expect(buttons[0]).toHaveAttribute('aria-label', 'Open Assistant'); + expect(buttons[1]).toHaveAttribute('aria-label', 'Open Dash'); }); }); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItemButton.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItemButton.tsx index 3145909e7f8..d819b0fa499 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItemButton.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItemButton.tsx @@ -17,8 +17,6 @@ function getPluginIcon(pluginId?: string): string { case 'grafana-grafanadocsplugin-app': case 'grafana-pathfinder-app': return 'book'; - case 'grafana-investigations-app': - return 'eye'; default: return 'ai-sparkle'; } diff --git a/public/app/core/components/AppChrome/History/HistoryContainer.tsx b/public/app/core/components/AppChrome/History/HistoryContainer.tsx deleted file mode 100644 index 88cbde02856..00000000000 --- a/public/app/core/components/AppChrome/History/HistoryContainer.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { css } from '@emotion/css'; -import { useEffect } from 'react'; -import { useToggle } from 'react-use'; - -import { GrafanaTheme2, store } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { Drawer, ToolbarButton, useStyles2 } from '@grafana/ui'; -import { appEvents } from 'app/core/app_events'; -import { RecordHistoryEntryEvent } from 'app/types/events'; - -import { HISTORY_LOCAL_STORAGE_KEY } from '../AppChromeService'; -import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator'; -import { HistoryEntry } from '../types'; - -import { HistoryWrapper } from './HistoryWrapper'; -import { logUnifiedHistoryDrawerInteractionEvent } from './eventsTracking'; - -export function HistoryContainer() { - const [showHistoryDrawer, onToggleShowHistoryDrawer] = useToggle(false); - const styles = useStyles2(getStyles); - - useEffect(() => { - const sub = appEvents.subscribe(RecordHistoryEntryEvent, (ev) => { - const clickedHistory = store.getObject('CLICKING_HISTORY'); - if (clickedHistory) { - store.setObject('CLICKING_HISTORY', false); - return; - } - const history = store.getObject(HISTORY_LOCAL_STORAGE_KEY, []); - let lastEntry = history[0]; - const newUrl = ev.payload.url; - const lastUrl = lastEntry.views[0]?.url; - if (lastUrl !== newUrl) { - lastEntry.views = [ - { - name: ev.payload.name, - description: ev.payload.description, - url: newUrl, - time: Date.now(), - }, - ...lastEntry.views, - ]; - store.setObject(HISTORY_LOCAL_STORAGE_KEY, [...history]); - } - return () => { - sub.unsubscribe(); - }; - }); - }, []); - - return ( - <> - { - onToggleShowHistoryDrawer(); - logUnifiedHistoryDrawerInteractionEvent({ type: 'open' }); - }} - iconOnly - icon="history" - aria-label={t('nav.history-container.drawer-tittle', 'History')} - /> - - {showHistoryDrawer && ( - { - onToggleShowHistoryDrawer(); - logUnifiedHistoryDrawerInteractionEvent({ type: 'close' }); - }} - size="sm" - > - onToggleShowHistoryDrawer(false)} /> - - )} - - ); -} - -const getStyles = (theme: GrafanaTheme2) => { - return { - separator: css({ - [theme.breakpoints.down('sm')]: { - display: 'none', - }, - }), - }; -}; diff --git a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx b/public/app/core/components/AppChrome/History/HistoryWrapper.tsx deleted file mode 100644 index d25c86d6f3d..00000000000 --- a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx +++ /dev/null @@ -1,291 +0,0 @@ -import { css, cx } from '@emotion/css'; -import moment from 'moment'; -import { useState } from 'react'; - -import { FieldType, GrafanaTheme2, store } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { Box, Button, Card, Icon, IconButton, Space, Sparkline, Stack, Text, useStyles2, useTheme2 } from '@grafana/ui'; -import { formatDate } from 'app/core/internationalization/dates'; - -import { HISTORY_LOCAL_STORAGE_KEY } from '../AppChromeService'; -import { HistoryEntry } from '../types'; - -import { logClickUnifiedHistoryEntryEvent, logUnifiedHistoryShowMoreEvent } from './eventsTracking'; - -export function HistoryWrapper({ onClose }: { onClose: () => void }) { - const history = store.getObject(HISTORY_LOCAL_STORAGE_KEY, []).filter((entry) => { - return moment(entry.time).isAfter(moment().subtract(2, 'day').startOf('day')); - }); - const [numItemsToShow, setNumItemsToShow] = useState(5); - - const selectedTime = history.find((entry) => { - return entry.url === window.location.href || entry.views.some((view) => view.url === window.location.href); - })?.time; - - const hist = history.slice(0, numItemsToShow).reduce((acc: { [key: string]: HistoryEntry[] }, entry) => { - const date = moment(entry.time); - let key = ''; - if (date.isSame(moment(), 'day')) { - key = t('nav.history-wrapper.today', 'Today'); - } else if (date.isSame(moment().subtract(1, 'day'), 'day')) { - key = t('nav.history-wrapper.yesterday', 'Yesterday'); - } else { - key = date.format('YYYY-MM-DD'); - } - acc[key] = [...(acc[key] || []), entry]; - return acc; - }, {}); - const styles = useStyles2(getStyles); - return ( - - - {Object.keys(hist).map((entries, date) => { - return ( - - - {entries} - -
- {hist[entries].map((entry, index) => { - return ( - onClose()} - /> - ); - })} -
-
- ); - })} -
- {history.length > numItemsToShow && ( - - - - )} -
- ); -} -interface ItemProps { - entry: HistoryEntry; - isSelected: boolean; - onClick: () => void; -} - -function HistoryEntryAppView({ entry, isSelected, onClick }: ItemProps) { - const styles = useStyles2(getStyles); - const theme = useTheme2(); - const [isExpanded, setIsExpanded] = useState(isSelected && entry.views.length > 0); - - const { breadcrumbs, views, time, url, sparklineData } = entry; - const expandedLabel = isExpanded - ? t('nav.history-wrapper.collapse', 'Collapse') - : t('nav.history-wrapper.expand', 'Expand'); - const entryIconLabel = isExpanded - ? t('nav.history-wrapper.icon-selected', 'Selected Entry') - : t('nav.history-wrapper.icon-unselected', 'Normal Entry'); - const selectedViewTime = - isSelected && - entry.views.find((entry) => { - return entry.url === window.location.href; - })?.time; - - return ( - - - - {views.length > 0 ? ( - setIsExpanded(!isExpanded)} - aria-label={expandedLabel} - className={styles.iconButton} - /> - ) : ( - - )} - - { - store.setObject('CLICKING_HISTORY', true); - onClick(); - logClickUnifiedHistoryEntryEvent({ entryURL: url }); - }} - href={url} - isCompact={true} - className={isSelected ? styles.card : cx(styles.card, styles.cardSelected)} - > - -
- {breadcrumbs.map((breadcrumb, index) => ( - - {breadcrumb.text}{' '} - {index !== breadcrumbs.length - 1 - ? // eslint-disable-next-line @grafana/i18n/no-untranslated-strings - '> ' - : ''} - - ))} -
- - {formatDate(time, { timeStyle: 'short' })} - - {sparklineData && ( - - )} -
-
-
- {isExpanded && ( -
- {views.map((view, index) => { - return ( - { - store.setObject('CLICKING_HISTORY', true); - onClick(); - logClickUnifiedHistoryEntryEvent({ entryURL: view.url, subEntry: 'timeRange' }); - }} - isCompact={true} - className={view.time === selectedViewTime ? undefined : styles.subCard} - > - - {view.name} - {view.description && ( - - {view.description} - - )} - - - ); - })} -
- )} -
-
- ); -} -const getStyles = (theme: GrafanaTheme2) => { - return { - card: css({ - label: 'card', - background: 'none', - margin: theme.spacing(0.5, 0), - }), - cardSelected: css({ - label: 'card-selected', - background: 'none', - }), - subCard: css({ - label: 'subcard', - background: 'none', - margin: 0, - }), - iconButton: css({ - label: 'expand-button', - margin: 0, - }), - iconButtonCircle: css({ - label: 'blue-circle-icon', - margin: 0, - background: theme.colors.background.primary, - fill: theme.colors.primary.main, - cursor: 'default', - '&:hover:before': { - background: 'none', - }, - //Need this to place the icon on the line, otherwise the line will appear on top of the icon - zIndex: 0, - }), - iconButtonDot: css({ - label: 'blue-dot-icon', - margin: 0, - color: theme.colors.primary.main, - border: theme.shape.radius.circle, - cursor: 'default', - '&:hover:before': { - background: 'none', - }, - //Need this to place the icon on the line, otherwise the line will appear on top of the icon - zIndex: 0, - }), - expanded: css({ - label: 'expanded', - display: 'flex', - flexDirection: 'column', - marginLeft: theme.spacing(6), - gap: theme.spacing(1), - position: 'relative', - '&:before': { - content: '""', - position: 'absolute', - left: 0, - top: 0, - height: '100%', - width: '1px', - background: theme.colors.border.weak, - }, - }), - timeline: css({ - label: 'timeline', - position: 'relative', - height: '100%', - width: '100%', - paddingLeft: theme.spacing(2), - '&:before': { - content: '""', - position: 'absolute', - left: theme.spacing(5.75), - top: 0, - height: '100%', - width: '1px', - borderLeft: `1px dashed ${theme.colors.border.strong}`, - }, - }), - }; -}; diff --git a/public/app/core/components/AppChrome/History/eventsTracking.ts b/public/app/core/components/AppChrome/History/eventsTracking.ts deleted file mode 100644 index 5d49bd1c978..00000000000 --- a/public/app/core/components/AppChrome/History/eventsTracking.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { reportInteraction } from '@grafana/runtime'; - -const UNIFIED_HISTORY_ENTRY_CLICKED = 'grafana_unified_history_entry_clicked'; -const UNIFIED_HISTORY_ENTRY_DUPLICATED = 'grafana_unified_history_duplicated_entry_rendered'; -const UNIFIED_HISTORY_DRAWER_INTERACTION = 'grafana_unified_history_drawer_interaction'; -const UNIFIED_HISTORY_DRAWER_SHOW_MORE = 'grafana_unified_history_show_more'; - -//Currently just 'timeRange' is supported -//in short term, we could add 'templateVariables' for example -type subEntryTypes = 'timeRange'; - -//Whether the user opens or closes the `HistoryDrawer` -type UnifiedHistoryDrawerInteraction = 'open' | 'close'; - -interface UnifiedHistoryEntryClicked { - //We will also work with the current URL but we will get this from Rudderstack data - //URL to return to - entryURL: string; - //In the case we want to go back to a specific query param, currently just a specific time range - subEntry?: subEntryTypes; -} - -interface UnifiedHistoryEntryDuplicated { - // Common name of the history entries - entryName: string; - // URL of the last entry - lastEntryURL: string; - // URL of the new entry - newEntryURL: string; -} - -//Event triggered when a user clicks on an entry of the `HistoryDrawer` -export const logClickUnifiedHistoryEntryEvent = ({ entryURL, subEntry }: UnifiedHistoryEntryClicked) => { - reportInteraction(UNIFIED_HISTORY_ENTRY_CLICKED, { - entryURL, - subEntry, - }); -}; - -//Event triggered when history entry name matches the previous one -//so we keep track of duplicated entries and be able to analyze them -export const logDuplicateUnifiedHistoryEntryEvent = ({ - entryName, - lastEntryURL, - newEntryURL, -}: UnifiedHistoryEntryDuplicated) => { - reportInteraction(UNIFIED_HISTORY_ENTRY_DUPLICATED, { - entryName, - lastEntryURL, - newEntryURL, - }); -}; - -//We keep track of users open and closing the drawer -export const logUnifiedHistoryDrawerInteractionEvent = ({ type }: { type: UnifiedHistoryDrawerInteraction }) => { - reportInteraction(UNIFIED_HISTORY_DRAWER_INTERACTION, { - type, - }); -}; - -//We keep track of users clicking on the `Show more` button -export const logUnifiedHistoryShowMoreEvent = () => { - reportInteraction(UNIFIED_HISTORY_DRAWER_SHOW_MORE); -}; diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx index d5179570ef3..23c1624dc05 100644 --- a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx @@ -6,7 +6,6 @@ import { Components } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { ScopesContextValue } from '@grafana/runtime'; import { Icon, Stack, ToolbarButton, useStyles2 } from '@grafana/ui'; -import { config } from 'app/core/config'; import { MEGA_MENU_TOGGLE_ID } from 'app/core/constants'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth'; @@ -19,7 +18,6 @@ import { HomeLink } from '../../Branding/Branding'; import { Breadcrumbs } from '../../Breadcrumbs/Breadcrumbs'; import { buildBreadcrumbs } from '../../Breadcrumbs/utils'; import { ExtensionToolbarItem } from '../ExtensionSidebar/ExtensionToolbarItem'; -import { HistoryContainer } from '../History/HistoryContainer'; import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator'; import { QuickAdd } from '../QuickAdd/QuickAdd'; @@ -60,7 +58,6 @@ export const SingleTopBar = memo(function SingleTopBar({ const profileNode = useSelector((state) => state.navIndex['profile']); const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID]; const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav); - const unifiedHistoryEnabled = config.featureToggles.unifiedHistory; const isSmallScreen = !useMediaQueryMinWidth('sm'); const isLargeScreen = useMediaQueryMinWidth('lg'); const topLevelScopes = !showToolbarLevel && isLargeScreen && scopes?.state.enabled; @@ -96,7 +93,6 @@ export const SingleTopBar = memo(function SingleTopBar({ > - {unifiedHistoryEnabled && !isSmallScreen && } {!isSmallScreen && } diff --git a/public/app/core/components/AppChrome/types.ts b/public/app/core/components/AppChrome/types.ts index 6cf72c936f5..de9183423c3 100644 --- a/public/app/core/components/AppChrome/types.ts +++ b/public/app/core/components/AppChrome/types.ts @@ -4,28 +4,3 @@ export interface ToolbarUpdateProps { pageNav?: NavModelItem; actions?: React.ReactNode; } - -export interface HistoryEntryView { - name: string; - description: string; - url: string; - time: number; -} - -export interface HistoryEntrySparkline { - values: number[]; - range: { - min: number; - max: number; - delta: number; - }; -} - -export interface HistoryEntry { - name: string; - time: number; - breadcrumbs: NavModelItem[]; - url: string; - views: HistoryEntryView[]; - sparklineData?: HistoryEntrySparkline; -} diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index d19ac4bb619..fd6cbf40fae 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -36,7 +36,11 @@ import { isExpressionQueryInAlert, } from '../../../rule-editor/formProcessing'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; -import { GRAFANA_RULES_SOURCE_NAME, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { + GRAFANA_RULES_SOURCE_NAME, + getDefaultOrFirstCompatibleDataSource, + getRulesDataSources, +} from '../../../utils/datasource'; import { PromOrLokiQuery, isPromOrLokiQuery } from '../../../utils/rule-form'; import { isCloudAlertingRuleByType, @@ -417,7 +421,9 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod ]); const { sectionTitle, helpLabel, helpContent, helpLink } = DESCRIPTIONS[type ?? RuleFormType.grafana]; - + // Only show the data source managed option if there are data sources with manageAlerts enabled + const hasAlertEnabledDataSources = useMemo(() => getRulesDataSources().length > 0, []); + const canSelectDataSourceManaged = onlyOneDSInQueries(queries) && hasAlertEnabledDataSources; if (!type) { return null; } @@ -437,8 +443,6 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod } : undefined; - const canSelectDataSourceManaged = onlyOneDSInQueries(queries); - return ( <>
- {mode === 'edit' && ( + {mode === 'edit' && hasAlertEnabledDataSources && ( <> { ]), }); }); + + it('should not show rule type switch when no data sources have manageAlerts enabled', async () => { + // Setup data source with manageAlerts explicitly disabled + setupDataSources( + mockDataSource( + { + type: 'prometheus', + name: 'Prom-disabled', + uid: 'prometheus-disabled', + isDefault: true, + jsonData: { manageAlerts: false }, + }, + { alerting: true, module: 'core:plugin/prometheus' } + ) + ); + + renderRuleEditor(); + + // Wait for the form to load + await screen.findByRole('textbox', { name: 'name' }); + + // The rule type switch should NOT be visible + expect(screen.queryByText('Rule type')).not.toBeInTheDocument(); + expect(screen.queryByTestId('rule-type-radio-group')).not.toBeInTheDocument(); + }); + + it('should show rule type switch when data sources have manageAlerts enabled', async () => { + // Setup data source with manageAlerts enabled + setupDataSources( + mockDataSource( + { + type: 'prometheus', + name: 'Prom-enabled', + uid: 'prometheus-enabled', + isDefault: true, + jsonData: { manageAlerts: true }, + }, + { alerting: true, module: 'core:plugin/prometheus' } + ) + ); + + renderRuleEditor(); + + // Wait for the form to load + await screen.findByRole('textbox', { name: 'name' }); + + // The rule type section should be visible + expect(await screen.findByText('Rule type')).toBeInTheDocument(); + }); }); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx index 7985791cfa2..1f33e3476ff 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx @@ -7,10 +7,11 @@ import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime'; import { AccessControlAction } from 'app/types/accessControl'; import { setupMswServer } from '../mockApi'; -import { grantUserPermissions, grantUserRole } from '../mocks'; +import { grantUserPermissions, grantUserRole, mockDataSource } from '../mocks'; import { setGrafanaRuleGroupExportResolver } from '../mocks/server/configure'; import { alertingFactory } from '../mocks/server/db'; import { RulesFilter } from '../search/rulesSearchParser'; +import { setupDataSources } from '../testSetup/datasources'; import RuleListPage, { RuleListActions } from './RuleList.v2'; import { loadDefaultSavedSearch } from './filter/useSavedSearches'; @@ -365,6 +366,51 @@ describe('RuleListActions', () => { expect(ui.exportDrawer.query()).toBeInTheDocument(); }); }); + + describe('Data source options visibility', () => { + it('should not show "New Data source recording rule" option when no data sources have manageAlerts enabled', async () => { + // Set up only data sources with manageAlerts explicitly set to false + // This replaces the default data sources that have manageAlerts defaulting to true + setupDataSources( + mockDataSource({ + name: 'Prometheus-disabled', + uid: 'prometheus-disabled', + type: 'prometheus', + jsonData: { manageAlerts: false }, + }) + ); + + grantUserPermissions([AccessControlAction.AlertingRuleExternalWrite]); + + const { user } = render(); + + await user.click(ui.moreButton.get()); + const menu = await ui.moreMenu.find(); + + expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).not.toBeInTheDocument(); + }); + + it('should show "New Data source recording rule" option when data sources have manageAlerts enabled', async () => { + // Set up data source with manageAlerts enabled + setupDataSources( + mockDataSource({ + name: 'Prometheus-enabled', + uid: 'prometheus-enabled', + type: 'prometheus', + jsonData: { manageAlerts: true }, + }) + ); + + grantUserPermissions([AccessControlAction.AlertingRuleExternalWrite]); + + const { user } = render(); + + await user.click(ui.moreButton.get()); + const menu = await ui.moreMenu.find(); + + expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).toBeInTheDocument(); + }); + }); }); describe('RuleListPage v2 - View switching', () => { diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx index 284bd6ad757..b90663200ce 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx @@ -13,6 +13,7 @@ import { useListViewMode } from '../components/rules/Filter/RulesViewModeSelecto import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton'; import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities'; import { useRulesFilter } from '../hooks/useFilteredRules'; +import { getRulesDataSources } from '../utils/datasource'; import { FilterView } from './FilterView'; import { GroupedView } from './GroupedView'; @@ -41,8 +42,11 @@ export function RuleListActions() { const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); + // Check if there are any data sources with manageAlerts enabled + const hasAlertEnabledDataSources = useMemo(() => getRulesDataSources().length > 0, []); + const canCreateGrafanaRules = createGrafanaRuleSupported && createGrafanaRuleAllowed; - const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; + const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed && hasAlertEnabledDataSources; const canExportRules = exportRulesSupported && exportRulesAllowed; const canCreateRules = canCreateGrafanaRules || canCreateCloudRules; diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 097c0d8d26c..515087010fb 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -40,7 +40,11 @@ import { PanelEditor } from '../panel-edit/PanelEditor'; import { DashboardScene } from '../scene/DashboardScene'; import { buildNewDashboardSaveModel, buildNewDashboardSaveModelV2 } from '../serialization/buildNewDashboardSaveModel'; import { transformSaveModelSchemaV2ToScene } from '../serialization/transformSaveModelSchemaV2ToScene'; -import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; +import { + createV2RowsLayout, + SceneCreationOptions, + transformSaveModelToScene, +} from '../serialization/transformSaveModelToScene'; import { restoreDashboardStateFromLocalStorage } from '../utils/dashboardSessionState'; import { processQueryParamsForDashboardLoad, updateNavModel } from './utils'; @@ -106,6 +110,34 @@ interface DashboardScenePageStateManagerLike { useState: () => DashboardScenePageState; } +/** + * Creates scene creation options with appropriate layout creator + * based on feature flags and dashboard type. + */ +export function getSceneCreationOptions( + loadOptions?: LoadDashboardOptions, + meta?: { isSnapshot?: boolean } +): SceneCreationOptions | undefined { + const isReport = loadOptions?.route === DashboardRoutes.Report; + const isTemplate = loadOptions?.route === DashboardRoutes.Template; + const isSnapshot = meta?.isSnapshot ?? false; + + // Don't use v2 layout for reports or snapshots + if (isReport || isSnapshot || isTemplate) { + return undefined; + } + + // Use v2 layout creator when v2 API is enabled + if (shouldForceV2API()) { + return { + createLayout: createV2RowsLayout, + targetVersion: 'v2', + }; + } + + return undefined; +} + abstract class DashboardScenePageStateManagerBase extends StateManagerBase implements DashboardScenePageStateManagerLike @@ -155,7 +187,7 @@ abstract class DashboardScenePageStateManagerBase private async loadHomeDashboard(): Promise { const rsp = await this.fetchHomeDashboard(); if (rsp) { - return transformSaveModelToScene(rsp); + return transformSaveModelToScene(rsp, undefined, getSceneCreationOptions()); } return null; @@ -441,7 +473,8 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag } if (rsp?.dashboard) { - const scene = transformSaveModelToScene(rsp, options); + const sceneCreationOptions = getSceneCreationOptions(options, rsp.meta); + const scene = transformSaveModelToScene(rsp, options, sceneCreationOptions); // Special handling for Template route - set up edit mode and dirty state if ( @@ -474,7 +507,8 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag throw new DashboardVersionError('v2beta1', 'Using legacy snapshot API to get a V2 dashboard'); } - const scene = transformSaveModelToScene(rsp); + // Snapshots should use default v1 layout + const scene = transformSaveModelToScene(rsp, undefined, getSceneCreationOptions(undefined, { isSnapshot: true })); return scene; } @@ -755,7 +789,8 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag return; } - const scene = transformSaveModelToScene(rsp); + const sceneCreationOptions = getSceneCreationOptions(undefined, rsp.meta); + const scene = transformSaveModelToScene(rsp, undefined, sceneCreationOptions); // we need to call and restore dashboard state on every reload that pulls a new dashboard version if (config.featureToggles.preserveDashboardStateWhenNavigating && Boolean(uid)) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 0bcea1e0ecd..bfb5095f3cf 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -27,6 +27,7 @@ import { createPanelSaveModel } from 'app/features/dashboard/state/__fixtures__/ import { SHARED_DASHBOARD_QUERY, DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/constants'; import { DashboardDataDTO } from 'app/types/dashboard'; +import { getSceneCreationOptions } from '../pages/DashboardScenePageStateManager'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; @@ -822,10 +823,14 @@ describe('transformSaveModelToScene', () => { }); it('Should convert legacy rows to new rows', () => { - const scene = transformSaveModelToScene({ - dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, - meta: {}, - }); + const scene = transformSaveModelToScene( + { + dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, + meta: {}, + }, + undefined, + getSceneCreationOptions() + ); const layout = scene.state.body as RowsLayoutManager; const row1 = layout.state.rows[0]; @@ -857,10 +862,14 @@ describe('transformSaveModelToScene', () => { }); it('Should convert legacy rows to new rows with free panels before first row', () => { - const scene = transformSaveModelToScene({ - dashboard: rowsAfterFreePanels as DashboardDataDTO, - meta: {}, - }); + const scene = transformSaveModelToScene( + { + dashboard: rowsAfterFreePanels as DashboardDataDTO, + meta: {}, + }, + undefined, + getSceneCreationOptions() + ); const layout = scene.state.body as RowsLayoutManager; const row1 = layout.state.rows[0]; diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 3da62d6cdf4..8f80dcc526e 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -29,11 +29,11 @@ import { } from 'app/features/dashboard/services/DashboardProfiler'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { DashboardDTO, DashboardDataDTO, DashboardRoutes } from 'app/types/dashboard'; +import { DashboardDTO, DashboardDataDTO } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { dashboardAnalyticsInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior'; -import { LoadDashboardOptions, shouldForceV2API } from '../pages/DashboardScenePageStateManager'; +import { LoadDashboardOptions } from '../pages/DashboardScenePageStateManager'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; @@ -76,11 +76,53 @@ export interface SaveModelToSceneOptions { isEmbedded?: boolean; } -export function transformSaveModelToScene(rsp: DashboardDTO, options?: LoadDashboardOptions): DashboardScene { +type LayoutCreator = (panels: PanelModel[], preload?: boolean) => DashboardLayoutManager; + +export interface SceneCreationOptions { + /** + * When provided, this function is used to create the dashboard body/layout instead of the default v1 behavior. + * This allows callers to inject v2 layout strategy. + */ + createLayout?: LayoutCreator; + /** + * Determines how the dashboard scene is serialized. + * @default 'v1' + */ + targetVersion?: 'v1' | 'v2'; +} + +// Rows as SceneGridRow within the grid. +const createDefaultGridLayout: LayoutCreator = (panels, preload) => { + return new DefaultGridLayoutManager({ + grid: new SceneGridLayout({ + isLazy: getIsLazy(preload), + children: createSceneObjectsForPanels(panels), + }), + }); +}; + +/** + * V2 layout creator - uses RowsLayoutManager when dashboard has rows. + * This creates a layout that can be properly serialized to v2 format. + */ +export const createV2RowsLayout: LayoutCreator = (panels, preload) => { + const hasRows = panels.some((p) => p.type === 'row'); + if (hasRows) { + return createRowsFromPanels(panels); + } + // Fall back to default grid layout when no rows + return createDefaultGridLayout(panels, preload); +}; + +export function transformSaveModelToScene( + rsp: DashboardDTO, + options?: LoadDashboardOptions, + sceneOptions?: SceneCreationOptions +): DashboardScene { // Just to have migrations run const oldModel = new DashboardModel(rsp.dashboard, rsp.meta); - const scene = createDashboardSceneFromDashboardModel(oldModel, rsp.dashboard, options); + const scene = createDashboardSceneFromDashboardModel(oldModel, rsp.dashboard, options, sceneOptions); // TODO: refactor createDashboardSceneFromDashboardModel to work on Dashboard schema model const apiVersion = config.featureToggles.kubernetesDashboards @@ -92,7 +134,7 @@ export function transformSaveModelToScene(rsp: DashboardDTO, options?: LoadDashb return scene; } -export function createRowsFromPanels(oldPanels: PanelModel[]): RowsLayoutManager { +function createRowsFromPanels(oldPanels: PanelModel[]): RowsLayoutManager { const rowItems: RowItem[] = []; let currentLegacyRow: PanelModel | null = null; @@ -143,7 +185,7 @@ export function createRowsFromPanels(oldPanels: PanelModel[]): RowsLayoutManager }); } -export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridItemLike[] { +function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridItemLike[] { // collects all panels and rows const panels: SceneGridItemLike[] = []; @@ -259,14 +301,14 @@ function createRowItemFromLegacyRow(row: PanelModel, panels: DashboardGridItem[] export function createDashboardSceneFromDashboardModel( oldModel: DashboardModel, dto: DashboardDataDTO, - options?: LoadDashboardOptions + options?: LoadDashboardOptions, + sceneOptions?: SceneCreationOptions ) { let variables: SceneVariableSet | undefined; let annotationLayers: SceneDataLayerProvider[] = []; let alertStatesLayer: AlertStatesDataLayer | undefined; const uid = oldModel.uid; - const isReport = options?.route === DashboardRoutes.Report; - const serializerVersion = shouldForceV2API() && !oldModel.meta.isSnapshot && !isReport ? 'v2' : 'v1'; + const targetVersion = sceneOptions?.targetVersion ?? 'v1'; if (oldModel.meta.isSnapshot) { variables = createVariablesForSnapshot(oldModel); @@ -354,9 +396,11 @@ export function createDashboardSceneFromDashboardModel( let body: DashboardLayoutManager; - if (serializerVersion === 'v2' && oldModel.panels.some((p) => p.type === 'row')) { - body = createRowsFromPanels(oldModel.panels); + if (sceneOptions?.createLayout) { + // Use injected layout creator (allows callers to specify v2 or custom layout strategy) + body = sceneOptions.createLayout(oldModel.panels, dto.preload); } else { + // Default v1 layout: DefaultGridLayoutManager body = new DefaultGridLayoutManager({ grid: new SceneGridLayout({ isLazy: getIsLazy(dto.preload), @@ -404,7 +448,7 @@ export function createDashboardSceneFromDashboardModel( hideTimeControls: oldModel.timepicker.hidden, }), }, - serializerVersion + targetVersion ); // Enable panel profiling for this dashboard using the composed SceneRenderProfiler diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts index 9dbf8e36f0a..a870f4feef5 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts @@ -1,6 +1,8 @@ import { readdirSync, readFileSync } from 'fs'; import path from 'path'; +import { getSceneCreationOptions } from '../pages/DashboardScenePageStateManager'; + import { normalizeBackendOutputForFrontendComparison } from './serialization-test-utils'; import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene'; import { transformSaveModelToScene } from './transformSaveModelToScene'; @@ -207,22 +209,26 @@ describe('V1 to V2 Dashboard Transformation Comparison', () => { delete dashboardSpec.snapshot; // Wrap in DashboardDTO structure that transformSaveModelToScene expects - const scene = transformSaveModelToScene({ - dashboard: dashboardSpec, - meta: { - isNew: false, - isFolder: false, - canSave: true, - canEdit: true, - canDelete: false, - canShare: false, - canStar: false, - canAdmin: false, - isSnapshot: false, - provisioned: false, - version: 1, + const scene = transformSaveModelToScene( + { + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, }, - }); + undefined, + getSceneCreationOptions() + ); const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); @@ -279,22 +285,26 @@ describe('V1 to V2 Dashboard Transformation Comparison', () => { delete dashboardSpec.snapshot; // Wrap in DashboardDTO structure that transformSaveModelToScene expects - const scene = transformSaveModelToScene({ - dashboard: dashboardSpec, - meta: { - isNew: false, - isFolder: false, - canSave: true, - canEdit: true, - canDelete: false, - canShare: false, - canStar: false, - canAdmin: false, - isSnapshot: false, - provisioned: false, - version: 1, + const scene = transformSaveModelToScene( + { + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, }, - }); + undefined, + getSceneCreationOptions() + ); const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts index 74208ff8a8f..af6116b9adf 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts @@ -6,6 +6,8 @@ import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboa import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { DashboardDataDTO } from 'app/types/dashboard'; +import { getSceneCreationOptions } from '../pages/DashboardScenePageStateManager'; + import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene'; import { transformSaveModelToScene } from './transformSaveModelToScene'; import { transformSceneToSaveModel } from './transformSceneToSaveModel'; @@ -228,22 +230,26 @@ function removeMetadata(spec: Dashboard): Partial { * identical processing. */ function loadAndSerializeV1SaveModel(dashboard: Dashboard): Dashboard { - const scene = transformSaveModelToScene({ - dashboard: dashboard as DashboardDataDTO, - meta: { - isNew: false, - isFolder: false, - canSave: true, - canEdit: true, - canDelete: false, - canShare: false, - canStar: false, - canAdmin: false, - isSnapshot: false, - provisioned: false, - version: 1, + const scene = transformSaveModelToScene( + { + dashboard: dashboard as DashboardDataDTO, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, }, - }); + undefined, + getSceneCreationOptions() + ); return transformSceneToSaveModel(scene, false); } diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx index 0e9d6ad49c5..c255cd4af5b 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx @@ -23,6 +23,7 @@ import { DashboardDataDTO } from 'app/types/dashboard'; import { DashboardScene } from '../scene/DashboardScene'; import { makeExportableV1, makeExportableV2 } from '../scene/export/exporters'; +import { createV2RowsLayout, transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { transformSceneToSaveModelSchemaV2 } from '../serialization/transformSceneToSaveModelSchemaV2'; import { getVariablesCompatibility } from '../utils/getVariablesCompatibility'; @@ -216,7 +217,27 @@ export class ShareExportTab extends SceneObjectBase impleme } if (exportMode === ExportMode.V2Resource) { - const spec = transformSceneToSaveModelSchemaV2(scene); + let sceneForV2Export = scene; + + // When exporting v1 dashboard as v2, we need to recreate the scene with v2 layout creator + // to ensure rows are properly serialized. The v1 scene uses DefaultGridLayoutManager which + // doesn't know about RowsLayoutManager structure needed for v2 serialization. + if (initialSaveModelVersion === 'v1' && initialSaveModel && isV1ClassicDashboard(initialSaveModel)) { + // Recreate scene with v2 layout creator to properly handle rows + sceneForV2Export = transformSaveModelToScene( + { + dashboard: { ...initialSaveModel, title: initialSaveModel.title ?? '', uid: initialSaveModel.uid ?? '' }, + meta: scene.state.meta, + }, + undefined, + { + createLayout: createV2RowsLayout, + targetVersion: 'v2', + } + ); + } + + const spec = transformSceneToSaveModelSchemaV2(sceneForV2Export); const specCopy = JSON.parse(JSON.stringify(spec)); const statelessSpec = await makeExportableV2(specCopy, isSharingExternally); const exportableV2 = isSharingExternally ? statelessSpec : spec; diff --git a/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts index 2ca8d2a2411..63fb8016294 100644 --- a/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts @@ -2,6 +2,7 @@ import { readdirSync, readFileSync } from 'fs'; import path from 'path'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; +import { getSceneCreationOptions } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager'; import { normalizeBackendOutputForFrontendComparison } from 'app/features/dashboard-scene/serialization/serialization-test-utils'; import { transformSaveModelSchemaV2ToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene'; import { transformSaveModelToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelToScene'; @@ -193,22 +194,26 @@ describe('V1 to V2 Dashboard Transformation Comparison (ResponseTransformers)', delete dashboardSpec.snapshot; // Wrap in DashboardDTO structure that transformSaveModelToScene expects - const scene = transformSaveModelToScene({ - dashboard: dashboardSpec, - meta: { - isNew: false, - isFolder: false, - canSave: true, - canEdit: true, - canDelete: false, - canShare: false, - canStar: false, - canAdmin: false, - isSnapshot: false, - provisioned: false, - version: 1, + const scene = transformSaveModelToScene( + { + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, }, - }); + undefined, + getSceneCreationOptions() + ); const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index fceb78b0b49..df2972c4483 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -200,7 +200,7 @@ describe('createSpanLinkFactory', () => { datasource: 'loki1_uid', queries: [ { - expr: '{cluster="cluster1", hostname="hostname1", service_namespace="namespace1"} | label_format log_line_contains_trace_id=`{{ contains "7946b05c2e2e4e5a" __line__ }}` | log_line_contains_trace_id="true" or trace_id="7946b05c2e2e4e5a" | label_format log_line_contains_span_id=`{{ contains "6605c7b08e715d6c" __line__ }}` | log_line_contains_span_id="true" or span_id="6605c7b08e715d6c"', + expr: '{cluster="cluster1", hostname="hostname1", service_namespace="namespace1"} | logfmt | json | drop __error__, __error_details__ | trace_id="7946b05c2e2e4e5a" | span_id="6605c7b08e715d6c"', refId: '', datasource: { uid: 'loki1_uid' }, }, diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 4feed49e07d..1b7cbc83054 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -446,12 +446,12 @@ function getQueryForLoki( let expr = '{${__tags}}'; if (filterByTraceID && span.traceID) { - expr += - ' | label_format log_line_contains_trace_id=`{{ contains "${__span.traceId}" __line__ }}` | log_line_contains_trace_id="true" or trace_id="${__span.traceId}"'; - } - if (filterBySpanID && span.spanID) { - expr += - ' | label_format log_line_contains_span_id=`{{ contains "${__span.spanId}" __line__ }}` | log_line_contains_span_id="true" or span_id="${__span.spanId}"'; + expr += ' | logfmt | json | drop __error__, __error_details__ | trace_id="${__span.traceId}"'; + if (filterBySpanID && span.spanID) { + expr += ' | span_id="${__span.spanId}"'; + } + } else if (filterBySpanID && span.spanID) { + expr += ' | logfmt | json | drop __error__, __error_details__ | span_id="${__span.spanId}"'; } return { diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 5c7e4ba151b..6fb728bb94c 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -1,6 +1,5 @@ import { AnnotationQuery, BusEventBase, BusEventWithPayload, eventFactory } from '@grafana/data'; import { IconName, ButtonVariant } from '@grafana/ui'; -import { HistoryEntryView } from 'app/core/components/AppChrome/types'; /** * Event Payloads @@ -217,7 +216,3 @@ export class PanelEditEnteredEvent extends BusEventWithPayload { export class PanelEditExitedEvent extends BusEventWithPayload { static type = 'panel-edit-finished'; } - -export class RecordHistoryEntryEvent extends BusEventWithPayload { - static type = 'record-history-entry'; -} diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 91e89a580ff..30a43035b36 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -3791,7 +3791,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4454,6 +4453,7 @@ }, "no-properties-changed": "Žádné relevantní vlastnosti se nezměnily", "table": { + "notes": "", "updated": "Datum", "updatedBy": "Aktualizoval uživatel", "version": "Verze" @@ -4912,7 +4912,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Hodnoty oddělené čárkou" }, "datasource-options": { "name-filter": "Filtr názvu", @@ -6010,6 +6011,9 @@ }, "custom-variable-form": { "custom-options": "Vlastní možnosti", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Hodnoty oddělené čárkou", "selection-options": "Možnosti výběru" }, @@ -6601,6 +6605,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Nástěnka byla uložena" }, @@ -6624,6 +6633,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6683,8 +6693,11 @@ "tooltip-show-usages": "Zobrazit použití" }, "variable-values-preview": { - "preview-of-values": "Náhled hodnot", - "show-more": "Zobrazit více" + "show-more": "Zobrazit více", + "preview-of-values_one": "", + "preview-of-values_few": "", + "preview-of-values_many": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index e0de5a6bf30..c5b867de844 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Keine relevanten Eigenschaften geändert", "table": { + "notes": "", "updated": "Datum", "updatedBy": "Aktualisiert von", "version": "Version" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Werte werden durch Komma getrennt" }, "datasource-options": { "name-filter": "Namensfilter", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Benutzerdefinierte Optionen", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Werte werden durch Komma getrennt", "selection-options": "Auswahloptionen" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Dashboard gespeichert" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Nutzungen anzeigen" }, "variable-values-preview": { - "preview-of-values": "Vorschau der Werte", - "show-more": "Mehr anzeigen" + "show-more": "Mehr anzeigen", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6b6d360ea07..b790dd913cc 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -10720,18 +10720,6 @@ "help/documentation": "Documentation", "help/keyboard-shortcuts": "Keyboard shortcuts", "help/support": "Support", - "history-container": { - "drawer-tittle": "History" - }, - "history-wrapper": { - "collapse": "Collapse", - "expand": "Expand", - "icon-selected": "Selected Entry", - "icon-unselected": "Normal Entry", - "show-more": "Show more", - "today": "Today", - "yesterday": "Yesterday" - }, "home": { "title": "Home" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 5530857665c..5f6c9d39720 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "No se ha cambiado ninguna propiedad relevante", "table": { + "notes": "", "updated": "Fecha", "updatedBy": "Actualizada por", "version": "Versión" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Valores separados por coma" }, "datasource-options": { "name-filter": "Nombrar filtro", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Opciones personalizadas", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Valores separados por comas", "selection-options": "Opciones de selección" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Dashboard guardado" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Mostrar usos" }, "variable-values-preview": { - "preview-of-values": "Vista previa de los valores", - "show-more": "Mostrar más" + "show-more": "Mostrar más", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 73d52b983df..180ef0eed48 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Aucune propriété pertinente n’a été modifiée", "table": { + "notes": "", "updated": "Date", "updatedBy": "Mis à jour par", "version": "Version" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Valeurs séparées par une virgule" }, "datasource-options": { "name-filter": "Nom du filtre", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Personnaliser les options", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Valeurs séparées par des virgules", "selection-options": "Options de sélection" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Tableau de bord enregistré" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Afficher les usages" }, "variable-values-preview": { - "preview-of-values": "Aperçu des valeurs", - "show-more": "Afficher plus" + "show-more": "Afficher plus", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index bbf358886d4..507d6e946e2 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Nem változtak meg a releváns tulajdonságok", "table": { + "notes": "", "updated": "Dátum", "updatedBy": "Frissítette:", "version": "Verzió" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Értékek vesszővel elválasztva" }, "datasource-options": { "name-filter": "Névszűrő", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Egyéni opciók", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Értékek vesszővel elválasztva", "selection-options": "Kijelölés beállításai" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Irányítópult elmentve" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Használatok megjelenítése" }, "variable-values-preview": { - "preview-of-values": "Értékek előnézete", - "show-more": "Több megjelenítése" + "show-more": "Több megjelenítése", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index b94e5d8ffec..c97b34b865a 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -3743,7 +3743,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4397,6 +4396,7 @@ }, "no-properties-changed": "Tidak ada properti yang relevan yang diubah", "table": { + "notes": "", "updated": "Tanggal", "updatedBy": "Diperbarui Oleh", "version": "Versi" @@ -4855,7 +4855,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Nilai dipisahkan dengan koma" }, "datasource-options": { "name-filter": "Filter nama", @@ -5947,6 +5948,9 @@ }, "custom-variable-form": { "custom-options": "Opsi kustom", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Nilai dipisahkan dengan koma", "selection-options": "Opsi pemilihan" }, @@ -6532,6 +6536,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Dasbor disimpan" }, @@ -6555,6 +6564,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6614,8 +6624,8 @@ "tooltip-show-usages": "Tampilkan penggunaan" }, "variable-values-preview": { - "preview-of-values": "Pratinjau nilai", - "show-more": "Tampilkan lebih banyak" + "show-more": "Tampilkan lebih banyak", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index e8d62fdaa78..4177ce34f0f 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Nessuna proprietà rilevante modificata", "table": { + "notes": "", "updated": "Data", "updatedBy": "Aggiornato da", "version": "Versione" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Valori separati da virgola" }, "datasource-options": { "name-filter": "Filtro nome", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Opzioni personalizzate", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Valori separati da virgola", "selection-options": "Seleziona opzioni" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Dashboard salvata" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Mostra utilizzi" }, "variable-values-preview": { - "preview-of-values": "Anteprima dei valori", - "show-more": "Mostra di più" + "show-more": "Mostra di più", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index fe83750305e..dc64233a849 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -3743,7 +3743,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4397,6 +4396,7 @@ }, "no-properties-changed": "関連するプロパティは変更されていません", "table": { + "notes": "", "updated": "日付", "updatedBy": "更新者", "version": "バージョン" @@ -4855,7 +4855,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "カンマで区切った値" }, "datasource-options": { "name-filter": "名前フィルター", @@ -5947,6 +5948,9 @@ }, "custom-variable-form": { "custom-options": "カスタムオプション", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "カンマ区切りの値", "selection-options": "選択オプション" }, @@ -6532,6 +6536,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "ダッシュボードが保存されました" }, @@ -6555,6 +6564,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6614,8 +6624,8 @@ "tooltip-show-usages": "使用状況を表示" }, "variable-values-preview": { - "preview-of-values": "値のプレビュー", - "show-more": "さらに表示" + "show-more": "さらに表示", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 2638c2e40ce..97b9f454eaa 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -3743,7 +3743,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4397,6 +4396,7 @@ }, "no-properties-changed": "변경된 관련 속성 없음", "table": { + "notes": "", "updated": "날짜", "updatedBy": "업데이트한 사용자", "version": "버전" @@ -4855,7 +4855,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "쉼표로 구분된 값" }, "datasource-options": { "name-filter": "이름 필터", @@ -5947,6 +5948,9 @@ }, "custom-variable-form": { "custom-options": "사용자 지정 옵션", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "쉼표로 구분된 값", "selection-options": "선택 옵션" }, @@ -6532,6 +6536,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "대시보드가 저장되었습니다" }, @@ -6555,6 +6564,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6614,8 +6624,8 @@ "tooltip-show-usages": "사용처 표시" }, "variable-values-preview": { - "preview-of-values": "값 미리 보기", - "show-more": "더 보기" + "show-more": "더 보기", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index e43d2ab91ec..9e3e2be0082 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Geen relevante eigenschappen gewijzigd", "table": { + "notes": "", "updated": "Datum", "updatedBy": "Bijgewerkt door", "version": "Versie" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Waarden gescheiden door komma" }, "datasource-options": { "name-filter": "Filter een naam geven", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Aangepaste opties", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Waarden gescheiden door komma", "selection-options": "Selectiemogelijkheden" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Dashboard opgeslagen" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Gebruik weergeven" }, "variable-values-preview": { - "preview-of-values": "Voorbeeldweergave van waarden", - "show-more": "Meer weergeven" + "show-more": "Meer weergeven", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 598411a8e47..a596b3fb9ef 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -3791,7 +3791,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4454,6 +4453,7 @@ }, "no-properties-changed": "Nie zmieniono istotnych właściwości", "table": { + "notes": "", "updated": "Data", "updatedBy": "Zaktualizowane przez", "version": "Wersja" @@ -4912,7 +4912,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Wartości rozdzielone przecinkami" }, "datasource-options": { "name-filter": "Filtr nazwy", @@ -6010,6 +6011,9 @@ }, "custom-variable-form": { "custom-options": "Opcje niestandardowe", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Wartości rozdzielone przecinkami", "selection-options": "Opcje wyboru" }, @@ -6601,6 +6605,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Pulpit został zapisany" }, @@ -6624,6 +6633,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6683,8 +6693,11 @@ "tooltip-show-usages": "Wyświetl użycie" }, "variable-values-preview": { - "preview-of-values": "Podgląd wartości", - "show-more": "Pokaż więcej" + "show-more": "Pokaż więcej", + "preview-of-values_one": "", + "preview-of-values_few": "", + "preview-of-values_many": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 8542e2c4258..23cab83c486 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Nenhuma propriedade relevante alterada", "table": { + "notes": "", "updated": "Data", "updatedBy": "Atualizada por", "version": "Versão" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Valores separados por vírgula" }, "datasource-options": { "name-filter": "Filtro de nome", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Opções personalizadas", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Valores separados por vírgula", "selection-options": "Opções de seleção" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Painel de controle salvo" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Exibir usos" }, "variable-values-preview": { - "preview-of-values": "Pré-visualização de valores", - "show-more": "Exibir mais" + "show-more": "Exibir mais", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 0374230582e..861b1389bbd 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Nenhuma propriedade relevante alterada", "table": { + "notes": "", "updated": "Data", "updatedBy": "Atualizado por", "version": "Versão" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Valores separados por vírgulas" }, "datasource-options": { "name-filter": "Filtro de nome", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Opções personalizadas", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Valores separados por vírgulas", "selection-options": "Opções de seleção" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Painel de controlo guardado" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Mostrar utilizações" }, "variable-values-preview": { - "preview-of-values": "Pré-visualização de valores", - "show-more": "Mostrar mais" + "show-more": "Mostrar mais", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index b5998cd2504..c5288f07d5b 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -3791,7 +3791,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4454,6 +4453,7 @@ }, "no-properties-changed": "Нет изменений соответствующих свойств", "table": { + "notes": "", "updated": "Дата", "updatedBy": "Обновлено", "version": "Версия" @@ -4912,7 +4912,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Значения, разделенные запятыми" }, "datasource-options": { "name-filter": "Фильтр по названию", @@ -6010,6 +6011,9 @@ }, "custom-variable-form": { "custom-options": "Пользовательские параметры", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Значения, разделенные запятыми", "selection-options": "Параметры выбора" }, @@ -6601,6 +6605,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Дашборд сохранен" }, @@ -6624,6 +6633,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6683,8 +6693,11 @@ "tooltip-show-usages": "Показать варианты использования" }, "variable-values-preview": { - "preview-of-values": "Просмотр значений", - "show-more": "Показать еще" + "show-more": "Показать еще", + "preview-of-values_one": "", + "preview-of-values_few": "", + "preview-of-values_many": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 34ad2defca4..1decc243650 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "Inga relevanta egenskaper har ändrats", "table": { + "notes": "", "updated": "Datum", "updatedBy": "Uppdaterad per", "version": "Version" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Värden åtskilda med kommatecken" }, "datasource-options": { "name-filter": "Namnfilter", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Anpassade alternativ", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Värden åtskilda med kommatecken", "selection-options": "Urvalsalternativ" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Kontrollpanelen sparades" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Visa användningar" }, "variable-values-preview": { - "preview-of-values": "Förhandsgranska värden", - "show-more": "Visa mer" + "show-more": "Visa mer", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 7e3b1738568..b8167bb5796 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -3759,7 +3759,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4416,6 +4415,7 @@ }, "no-properties-changed": "İlgili hiçbir özellik değiştirilmedi", "table": { + "notes": "", "updated": "Tarih", "updatedBy": "Güncelleyen:", "version": "Sürüm" @@ -4874,7 +4874,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "Virgülle ayrılmış değerler" }, "datasource-options": { "name-filter": "Ad filtresi", @@ -5968,6 +5969,9 @@ }, "custom-variable-form": { "custom-options": "Özel seçenekler", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "Virgülle ayrılmış değerler", "selection-options": "Seçim ayarları" }, @@ -6555,6 +6559,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "Pano kaydedildi" }, @@ -6578,6 +6587,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6637,8 +6647,9 @@ "tooltip-show-usages": "Kullanımları göster" }, "variable-values-preview": { - "preview-of-values": "Değerlerin ön izlemesi", - "show-more": "Daha fazla göster" + "show-more": "Daha fazla göster", + "preview-of-values_one": "", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 7d53f6e4fd1..517a69dac16 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -3743,7 +3743,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4397,6 +4396,7 @@ }, "no-properties-changed": "没有相关属性更改", "table": { + "notes": "", "updated": "日期", "updatedBy": "更新者", "version": "版本" @@ -4855,7 +4855,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "以逗号分隔的值" }, "datasource-options": { "name-filter": "名称筛选器", @@ -5947,6 +5948,9 @@ }, "custom-variable-form": { "custom-options": "自定义选项", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "以逗号分隔的值", "selection-options": "选择内容选项" }, @@ -6532,6 +6536,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "数据面板已保存" }, @@ -6555,6 +6564,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6614,8 +6624,8 @@ "tooltip-show-usages": "显示使用情况" }, "variable-values-preview": { - "preview-of-values": "值预览", - "show-more": "显示更多" + "show-more": "显示更多", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 2e6243b80c2..f4291af97aa 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -3743,7 +3743,6 @@ }, "recently-viewed": { "clear": "", - "empty": "", "error": "", "retry": "", "title": "" @@ -4397,6 +4396,7 @@ }, "no-properties-changed": "沒有相關的屬性變更", "table": { + "notes": "", "updated": "日期", "updatedBy": "更新者", "version": "版本" @@ -4855,7 +4855,8 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "" + "modal-title": "", + "values": "以逗號分隔的值" }, "datasource-options": { "name-filter": "名稱篩選", @@ -5947,6 +5948,9 @@ }, "custom-variable-form": { "custom-options": "自訂選項", + "json-values-tooltip": "", + "name-csv-values": "", + "name-json-values": "", "name-values-separated-comma": "以逗號分隔的值", "selection-options": "選擇選項" }, @@ -6532,6 +6536,11 @@ } } }, + "use-modal-editor": { + "description": { + "change-variable-query": "" + } + }, "use-save-dashboard": { "message-dashboard-saved": "儀表板已儲存" }, @@ -6555,6 +6564,7 @@ "label": "" }, "hidden": { + "description": "", "label": "" }, "hidden-label": { @@ -6614,8 +6624,8 @@ "tooltip-show-usages": "顯示使用情況" }, "variable-values-preview": { - "preview-of-values": "數值預覽", - "show-more": "顯示更多" + "show-more": "顯示更多", + "preview-of-values_other": "" }, "version-history": { "comparison": { diff --git a/public/openapi3.json b/public/openapi3.json index cda13e8a2dc..8bb45f8fcf0 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -10039,6 +10039,9 @@ }, "ReportOptions": { "properties": { + "csvEncoding": { + "type": "string" + }, "layout": { "type": "string" }, diff --git a/public/sass/_angular.scss b/public/sass/_angular.scss index 271965fff82..7282a7f02e6 100644 --- a/public/sass/_angular.scss +++ b/public/sass/_angular.scss @@ -1161,56 +1161,6 @@ div.editor-option label { content: '\e902'; } -.bootstrap-tagsinput { - display: inline-block; - padding: 0 0 0 6px; - vertical-align: middle; - max-width: 100%; - line-height: 22px; - background-color: $input-bg; - border: 1px solid $input-border-color; - - input { - display: inline-block; - border: none; - margin: 0px; - border-radius: 0; - padding: 8px 6px; - height: 100%; - width: 70px; - box-sizing: border-box; - - &.gf-form-input--has-help-icon { - padding-right: $space-xl; - } - } - - .tag { - margin-right: 2px; - color: $white; - - [data-role='remove'] { - margin-left: 8px; - cursor: pointer; - - &::after { - content: 'x'; - padding: 0px 2px; - } - - &:hover { - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.2), - 0 1px 2px rgba(0, 0, 0, 0.05); - - &:active { - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - } - } - } - } -} - .page-header { margin-top: $space-md; diff --git a/public/vendor/tagsinput/bootstrap-tagsinput.js b/public/vendor/tagsinput/bootstrap-tagsinput.js deleted file mode 100644 index 66e1aa270b4..00000000000 --- a/public/vendor/tagsinput/bootstrap-tagsinput.js +++ /dev/null @@ -1,512 +0,0 @@ -(function ($) { - "use strict"; - - var defaultOptions = { - tagClass: function(item) { - return 'label label-info'; - }, - itemValue: function(item) { - return item ? item.toString() : item; - }, - itemText: function(item) { - return this.itemValue(item); - }, - freeInput: true, - maxTags: undefined, - confirmKeys: [13], - onTagExists: function(item, $tag) { - $tag.hide().fadeIn(); - } - }; - - /** - * Constructor function - */ - function TagsInput(element, options) { - this.itemsArray = []; - - this.$element = $(element); - this.$element.hide(); - - this.widthClass = options.widthClass || 'width-9'; - this.isSelect = (element.tagName === 'SELECT'); - this.multiple = (this.isSelect && element.hasAttribute('multiple')); - this.objectItems = options && options.itemValue; - this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : ''; - - this.$container = $('
'); - this.$input = $('').appendTo(this.$container); - - this.$element.after(this.$container); - - this.build(options); - } - - TagsInput.prototype = { - constructor: TagsInput, - - /** - * Adds the given item as a new tag. Pass true to dontPushVal to prevent - * updating the elements val() - */ - add: function(item, dontPushVal) { - var self = this; - - if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags) - return; - - // Ignore falsey values, except false - if (item !== false && !item) - return; - - // Throw an error when trying to add an object while the itemValue option was not set - if (typeof item === "object" && !self.objectItems) - throw("Can't add objects when itemValue option is not set"); - - // Ignore strings only containg whitespace - if (item.toString().match(/^\s*$/)) - return; - - // If SELECT but not multiple, remove current tag - if (self.isSelect && !self.multiple && self.itemsArray.length > 0) - self.remove(self.itemsArray[0]); - - if (typeof item === "string" && this.$element[0].tagName === 'INPUT') { - var items = item.split(','); - if (items.length > 1) { - for (var i = 0; i < items.length; i++) { - this.add(items[i], true); - } - - if (!dontPushVal) - self.pushVal(); - return; - } - } - - var itemValue = self.options.itemValue(item), - itemText = self.options.itemText(item), - tagClass = self.options.tagClass(item); - - // Ignore items already added - var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0]; - if (existing) { - // Invoke onTagExists - if (self.options.onTagExists) { - var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; }); - self.options.onTagExists(item, $existingTag); - } - return; - } - - // register item in internal array and map - self.itemsArray.push(item); - - // add a tag element - var $tag = $('' + htmlEncode(itemText) + ''); - $tag.data('item', item); - self.findInputWrapper().before($tag); - $tag.after(' '); - - // add