diff --git a/.betterer.results b/.betterer.results index e6c62adfd30..f8eaec8ae3f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -863,6 +863,9 @@ exports[`better eslint`] = { "packages/grafana-ui/src/utils/useAsyncDependency.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/api/clients/folder/v1beta1/hooks.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/core/TableModel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3054,9 +3057,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], - "public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], "public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aec3137d4b1..350790df440 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,12 +77,14 @@ /public/app/features/provisioning @grafana/grafana-git-ui-sync-team /pkg/registry/apis/provisioning @grafana/grafana-git-ui-sync-team /pkg/tests/apis/provisioning @grafana/grafana-git-ui-sync-team -# Git Sync frontend owned by frontent team as a whole. +# Git Sync frontend owned by frontend team as a whole. /apps/alerting/ @grafana/alerting-backend /apps/dashboard/ @grafana/grafana-app-platform-squad @grafana/dashboards-squad /apps/folder/ @grafana/grafana-app-platform-squad /apps/playlist/ @grafana/grafana-app-platform-squad +/apps/plugins/ @grafana/plugins-platform-backend +/apps/preferences/ @grafana/grafana-app-platform-squad @grafana/grafana-frontend-platform /apps/shorturl/ @grafana/sharing-squad /apps/secret/ @grafana/grafana-operator-experience-squad /apps/investigations/ @fcjack @matryer @svennergr @@ -684,6 +686,7 @@ /project.json @grafana/frontend-ops /.nxignore @grafana/frontend-ops /tsconfig.json @grafana/frontend-ops +/scripts/tsconfig.base.json @grafana/frontend-ops /.editorconfig @grafana/frontend-ops /eslint.config.js @grafana/frontend-ops /.betterer.eslint.config.js @grafana/frontend-ops @@ -1107,6 +1110,7 @@ embed.go @grafana/grafana-as-code /pkg/registry/apis/userstorage @grafana/grafana-app-platform-squad @grafana/plugins-platform-backend /pkg/registry/apps/advisor @grafana/plugins-platform-backend /pkg/registry/apps/alerting @grafana/alerting-backend +/pkg/registry/apps/plugins @grafana/plugins-platform-backend /pkg/codegen/ @grafana/grafana-as-code /pkg/codegen/generators @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code diff --git a/Dockerfile b/Dockerfile index e7c0ec0fadb..b059c1c1a47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,6 +83,7 @@ COPY pkg/storage/unified/apistore pkg/storage/unified/apistore COPY pkg/semconv pkg/semconv COPY pkg/aggregator pkg/aggregator COPY apps/playlist apps/playlist +COPY apps/plugins apps/plugins COPY apps/shorturl apps/shorturl COPY apps/provisioning apps/provisioning COPY apps/secret apps/secret diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 5698787f545..17d35151499 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/alerting/notifications go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 - github.com/grafana/grafana-app-sdk/logging v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.3 + github.com/grafana/grafana-app-sdk/logging v0.40.2 k8s.io/apimachinery v0.33.3 k8s.io/apiserver v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff @@ -56,7 +56,7 @@ require ( github.com/onsi/gomega v1.36.2 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 2931a8f5200..30aa1308939 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -84,10 +84,10 @@ 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/grafana-app-sdk v0.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= @@ -151,8 +151,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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 v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 0d00cb6c511..4cf0617f40b 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -4,7 +4,7 @@ go 1.24.5 require ( cuelang.org/go v0.11.1 - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 github.com/grafana/grafana-plugin-sdk-go v0.278.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/stretchr/testify v1.10.0 @@ -44,7 +44,7 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect @@ -80,7 +80,7 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index bb90120dc2b..855fbd17a00 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -94,10 +94,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-app-sdk v0.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= @@ -209,8 +209,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= diff --git a/apps/dashboard/pkg/migration/conversion/conversion_test.go b/apps/dashboard/pkg/migration/conversion/conversion_test.go index ce59a99f53b..f27658ccd4f 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_test.go @@ -25,7 +25,7 @@ import ( func TestConversionMatrixExist(t *testing.T) { // Initialize the migrator with a test data source provider - migration.Initialize(testutil.GetTestProvider()) + migration.Initialize(testutil.GetTestDataSourceProvider(), testutil.GetTestPanelProvider()) versions := []v1.Object{ &dashv0.Dashboard{Spec: common.Unstructured{Object: map[string]any{"title": "dashboardV0"}}}, @@ -76,7 +76,7 @@ func TestDeepCopyValid(t *testing.T) { func TestDashboardConversionToAllVersions(t *testing.T) { // Initialize the migrator with a test data source provider - migration.Initialize(testutil.GetTestProvider()) + migration.Initialize(testutil.GetTestDataSourceProvider(), testutil.GetTestPanelProvider()) // Set up conversion scheme scheme := runtime.NewScheme() diff --git a/apps/dashboard/pkg/migration/migrate.go b/apps/dashboard/pkg/migration/migrate.go index fde02628d68..0cabb5f1acc 100644 --- a/apps/dashboard/pkg/migration/migrate.go +++ b/apps/dashboard/pkg/migration/migrate.go @@ -7,8 +7,8 @@ import ( ) // Initialize provides the migrator singleton with required dependencies and builds the map of migrations. -func Initialize(dsInfoProvider schemaversion.DataSourceInfoProvider) { - migratorInstance.init(dsInfoProvider) +func Initialize(dsInfoProvider schemaversion.DataSourceInfoProvider, panelProvider schemaversion.PanelPluginInfoProvider) { + migratorInstance.init(dsInfoProvider, panelProvider) } // Migrate migrates the given dashboard to the target version. @@ -30,9 +30,9 @@ type migrator struct { migrations map[int]schemaversion.SchemaVersionMigrationFunc } -func (m *migrator) init(dsInfoProvider schemaversion.DataSourceInfoProvider) { +func (m *migrator) init(dsInfoProvider schemaversion.DataSourceInfoProvider, panelProvider schemaversion.PanelPluginInfoProvider) { initOnce.Do(func() { - m.migrations = schemaversion.GetMigrations(dsInfoProvider) + m.migrations = schemaversion.GetMigrations(dsInfoProvider, panelProvider) close(m.ready) }) } @@ -57,7 +57,7 @@ func (m *migrator) migrate(dash map[string]interface{}, targetVersion int) error for nextVersion := inputVersion + 1; nextVersion <= targetVersion; nextVersion++ { if migration, ok := m.migrations[nextVersion]; ok { if err := migration(dash); err != nil { - return schemaversion.NewMigrationError("migration failed", inputVersion, nextVersion) + return schemaversion.NewMigrationError("migration failed: "+err.Error(), inputVersion, nextVersion) } dash["schemaVersion"] = nextVersion } diff --git a/apps/dashboard/pkg/migration/migrate_test.go b/apps/dashboard/pkg/migration/migrate_test.go index dfe60091bbc..fbcfdd0f754 100644 --- a/apps/dashboard/pkg/migration/migrate_test.go +++ b/apps/dashboard/pkg/migration/migrate_test.go @@ -23,7 +23,7 @@ func TestMigrate(t *testing.T) { require.NoError(t, err) // Use the same datasource provider as the frontend test to ensure consistency - migration.Initialize(testutil.GetTestProvider()) + migration.Initialize(testutil.GetTestDataSourceProvider(), testutil.GetTestPanelProvider()) t.Run("minimum version check", func(t *testing.T) { err := migration.Migrate(map[string]interface{}{ @@ -61,12 +61,12 @@ func TestMigrate(t *testing.T) { testName := fmt.Sprintf("%s v%d to v%d", f.Name(), inputVersion, schemaversion.LATEST_VERSION) t.Run(testName, func(t *testing.T) { - testMigration(t, inputDash, f.Name(), inputVersion, schemaversion.LATEST_VERSION) + testMigration(t, inputDash, f.Name(), schemaversion.LATEST_VERSION) }) } } -func testMigration(t *testing.T, dash map[string]interface{}, inputFileName string, inputVersion, targetVersion int) { +func testMigration(t *testing.T, dash map[string]interface{}, inputFileName string, targetVersion int) { t.Helper() require.NoError(t, migration.Migrate(dash, targetVersion), "%d migration failed", targetVersion) diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index 3ca5eea83bd..1863661e133 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -5,7 +5,7 @@ import ( ) const ( - MIN_VERSION = 28 + MIN_VERSION = 25 LATEST_VERSION = 41 ) @@ -24,8 +24,23 @@ type DataSourceInfoProvider interface { GetDataSourceInfo() []DataSourceInfo } -func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { +type PanelPluginInfo struct { + ID string + Version string +} + +type PanelPluginInfoProvider interface { + // Gets all the panels from the plugin store. + // Equivalent to grafanaBootData.settings.panels on the frontend. + GetPanels() []PanelPluginInfo + GetPanelPlugin(id string) PanelPluginInfo +} + +func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 26: V26, + 27: V27, + 28: V28(panelProvider), 29: V29, 30: V30, 31: V31, diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations_test.go b/apps/dashboard/pkg/migration/schemaversion/migrations_test.go index 02918b7b8fa..1bddf79290b 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations_test.go @@ -57,9 +57,10 @@ func TestGetSchemaVersion(t *testing.T) { } type migrationTestCase struct { - name string - input map[string]interface{} - expected map[string]interface{} + name string + input map[string]interface{} + expected map[string]interface{} + expectedError string } func runMigrationTests(t *testing.T, testCases []migrationTestCase, migrationFunc schemaversion.SchemaVersionMigrationFunc) { @@ -68,8 +69,13 @@ func runMigrationTests(t *testing.T, testCases []migrationTestCase, migrationFun for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { err := migrationFunc(tt.input) - require.NoError(t, err) - require.Equal(t, tt.expected, tt.input) + if tt.expectedError != "" { + require.Error(t, err) + require.Equal(t, tt.expectedError, err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, tt.input) + } }) } } diff --git a/apps/dashboard/pkg/migration/schemaversion/v26.go b/apps/dashboard/pkg/migration/schemaversion/v26.go new file mode 100644 index 00000000000..df860dbd03e --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v26.go @@ -0,0 +1,106 @@ +package schemaversion + +// V26 migration performs two main tasks: +// 1. Converts all text2 panels to text panels by changing the type field +// 2. Removes the angular field from panel options if it exists +// +// The migration includes comprehensive logic from the frontend: +// - Panel type conversion from "text2" to "text" +// - Angular field removal from panel options +// - Support for nested panels in rows +// +// Example before migration: +// +// "panels": [ +// { +// "id": 1, +// "type": "text2", +// "title": "Text Panel", +// "options": { +// "content": "Some content", +// "angular": true, +// "mode": "markdown" +// } +// }, +// { +// "id": 2, +// "type": "row", +// "title": "Row Panel", +// "panels": [ +// { +// "id": 3, +// "type": "text2", +// "title": "Nested Text Panel", +// "options": { +// "content": "Nested content", +// "angular": false, +// "mode": "html" +// } +// } +// ] +// }, +// { +// "id": 4, +// "type": "graph", +// "title": "Graph Panel" +// } +// ] +// +// Example after migration: +// +// "panels": [ +// { +// "id": 1, +// "type": "text", +// "title": "Text Panel", +// "options": { +// "content": "Some content", +// "mode": "markdown" +// } +// }, +// { +// "id": 2, +// "type": "row", +// "title": "Row Panel", +// "panels": [ +// { +// "id": 3, +// "type": "text", +// "title": "Nested Text Panel", +// "options": { +// "content": "Nested content", +// "mode": "html" +// } +// } +// ] +// }, +// { +// "id": 4, +// "type": "graph", +// "title": "Graph Panel" +// } +// ] +func V26(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 26 + + panels, ok := dashboard["panels"].([]interface{}) + if !ok { + return nil + } + + for _, panel := range panels { + panelMap, ok := panel.(map[string]interface{}) + if !ok { + continue + } + if panelMap["type"] == "text2" { + panelMap["type"] = "text" + + if options, ok := panelMap["options"].(map[string]interface{}); ok { + delete(options, "angular") + } + } + } + + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v26_test.go b/apps/dashboard/pkg/migration/schemaversion/v26_test.go new file mode 100644 index 00000000000..86e55bb5a95 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v26_test.go @@ -0,0 +1,100 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV26(t *testing.T) { + tests := []migrationTestCase{ + { + name: "migrate text2 to text", + input: map[string]interface{}{ + "schemaVersion": 25, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "text2", + "title": "Text2 Panel", + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 26, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "text", + "title": "Text2 Panel", + }, + }, + }, + }, + { + name: "should not migrate panel with old text panel id", + input: map[string]interface{}{ + "schemaVersion": 25, + "panels": []interface{}{ + map[string]interface{}{ + "id": 2, + "type": "text", + "title": "Angular Text Panel", + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown", + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 26, + "panels": []interface{}{ + map[string]interface{}{ + "id": 2, + "type": "text", + "title": "Angular Text Panel", + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown", + }, + }, + }, + }, + { + name: "should clean up old angular options for panels with new Text Panel id", + input: map[string]interface{}{ + "schemaVersion": 25, + "panels": []interface{}{ + map[string]interface{}{ + "id": 3, + "type": "text2", + "title": "React Text Panel from Angular Panel", + "options": map[string]interface{}{ + "mode": "markdown", + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "angular": map[string]interface{}{ + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 26, + "panels": []interface{}{ + map[string]interface{}{ + "id": 3, + "type": "text", + "title": "React Text Panel from Angular Panel", + "options": map[string]interface{}{ + "mode": "markdown", + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + }, + }, + }, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V26) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v27.go b/apps/dashboard/pkg/migration/schemaversion/v27.go new file mode 100644 index 00000000000..cdbe98a5907 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v27.go @@ -0,0 +1,167 @@ +package schemaversion + +// V27 migrates repeated panels and constant variables. +// +// The migration performs two main tasks: +// 1. Removes repeated panel leftovers by filtering out panels with repeatPanelId or repeatByRow +// 2. Migrates constant variables to textbox variables with proper current/options structure +// +// The migration includes comprehensive logic from the frontend: +// - Panel filtering to remove repeated panels +// - Constant variable migration with proper current/options structure +// - Support for both constant and textbox variable types +// +// Example before migration: +// +// "panels": [ +// { +// "id": 1, +// "type": "graph", +// "repeatPanelId": "panel1" +// }, +// { +// "id": 2, +// "type": "row", +// "panels": [ +// { +// "id": 3, +// "type": "graph", +// "repeatPanelId": "panel2" +// } +// ] +// } +// ], +// "templating": { +// "list": [ +// { +// "name": "constant_var", +// "type": "constant", +// "query": "default_value", +// "hide": 0 +// } +// ] +// } +// +// Example after migration: +// +// "panels": [ +// { +// "id": 2, +// "type": "row", +// "panels": [] +// } +// ], +// "templating": { +// "list": [ +// { +// "name": "constant_var", +// "type": "textbox", +// "query": "default_value", +// "current": { +// "selected": true, +// "text": "default_value", +// "value": "default_value" +// }, +// "options": [ +// { +// "selected": true, +// "text": "default_value", +// "value": "default_value" +// } +// ], +// "hide": 0 +// } +// ] +// } +func V27(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 27 + + // Remove repeated panels + if panels, ok := dashboard["panels"].([]interface{}); ok { + dashboard["panels"] = removeRepeatedPanels(panels) + } + + // Migrate constant variables + if templating, ok := dashboard["templating"].(map[string]interface{}); ok { + if list, ok := templating["list"].([]interface{}); ok { + for _, v := range list { + if variable, ok := v.(map[string]interface{}); ok { + migrateConstantVariable(variable) + } + } + } + } + + return nil +} + +// removeRepeatedPanels filters out panels with repeatPanelId or repeatByRow properties +// and cleans up repeated panels in collapsed rows +func removeRepeatedPanels(panels []interface{}) []interface{} { + newPanels := []interface{}{} + + for _, panel := range panels { + p, ok := panel.(map[string]interface{}) + if !ok { + continue + } + + // Skip panels with repeatPanelId or repeatByRow + if _, hasRepeatPanelId := p["repeatPanelId"]; hasRepeatPanelId { + continue + } + if _, hasRepeatByRow := p["repeatByRow"]; hasRepeatByRow { + continue + } + + // Filter out repeats in collapsed rows + if p["type"] == "row" { + if nestedPanels, ok := p["panels"].([]interface{}); ok { + var filteredNestedPanels []interface{} + for _, nestedPanel := range nestedPanels { + if np, ok := nestedPanel.(map[string]interface{}); ok { + if _, hasRepeatPanelId := np["repeatPanelId"]; !hasRepeatPanelId { + filteredNestedPanels = append(filteredNestedPanels, nestedPanel) + } + } + } + p["panels"] = filteredNestedPanels + } + } + + newPanels = append(newPanels, panel) + } + + return newPanels +} + +// migrateConstantVariable converts constant variables to textbox variables with proper current/options structure +func migrateConstantVariable(variable map[string]interface{}) { + if variableType, ok := variable["type"].(string); !ok || variableType != "constant" { + return + } + + query := "" + if queryVal, ok := variable["query"].(string); ok { + query = queryVal + } + variable["query"] = query + + current := map[string]interface{}{ + "selected": true, + "text": query, + "value": query, + } + + options := []interface{}{current} + + variable["current"] = current + variable["options"] = options + + // Convert to textbox if hide is 0 (dontHide) or 1 (hideLabel) + if hide, ok := variable["hide"].(float64); ok { + if hide == 0 || hide == 1 { + variable["type"] = "textbox" + } + } +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v27_test.go b/apps/dashboard/pkg/migration/schemaversion/v27_test.go new file mode 100644 index 00000000000..2f79856f412 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v27_test.go @@ -0,0 +1,172 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV27(t *testing.T) { + tests := []migrationTestCase{ + { + name: "remove repeated panels with repeatPanelId and repeatByRow", + input: map[string]interface{}{ + "schemaVersion": 26, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graph", + "title": "Repeated Panel 1", + "repeatPanelId": "panel1", + }, + map[string]interface{}{ + "id": 2, + "type": "graph", + "title": "Repeated Panel 2", + "repeatByRow": true, + }, + map[string]interface{}{ + "id": 3, + "type": "graph", + "title": "Normal Panel", + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 3, + "type": "graph", + "title": "Normal Panel", + }, + }, + }, + }, + { + name: "filter repeated panels in row panels", + input: map[string]interface{}{ + "schemaVersion": 26, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "row", + "title": "Row with repeated panels", + "panels": []interface{}{ + map[string]interface{}{ + "id": 2, + "type": "graph", + "title": "Repeated nested panel", + "repeatPanelId": "nested_panel1", + }, + map[string]interface{}{ + "id": 3, + "type": "graph", + "title": "Normal nested panel", + }, + }, + }, + map[string]interface{}{ + "id": 4, + "type": "graph", + "title": "Normal panel outside row", + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "row", + "title": "Row with repeated panels", + "panels": []interface{}{ + map[string]interface{}{ + "id": 3, + "type": "graph", + "title": "Normal nested panel", + }, + }, + }, + map[string]interface{}{ + "id": 4, + "type": "graph", + "title": "Normal panel outside row", + }, + }, + }, + }, + { + name: "migrate constant variable to textbox with hide=0", + input: map[string]interface{}{ + "schemaVersion": 26, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "constant_var", + "type": "constant", + "query": "default_value", + "hide": 0.0, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 27, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "constant_var", + "type": "textbox", + "query": "default_value", + "hide": 0.0, + "current": map[string]interface{}{ + "selected": true, + "text": "default_value", + "value": "default_value", + }, + "options": []interface{}{ + map[string]interface{}{ + "selected": true, + "text": "default_value", + "value": "default_value", + }, + }, + }, + }, + }, + }, + }, + { + name: "do not migrate non-constant variable", + input: map[string]interface{}{ + "schemaVersion": 26, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "query_var", + "type": "query", + "query": "some_query", + "hide": 0.0, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 27, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "query_var", + "type": "query", + "query": "some_query", + "hide": 0.0, + }, + }, + }, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V27) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v28.go b/apps/dashboard/pkg/migration/schemaversion/v28.go new file mode 100644 index 00000000000..9509e8a8a87 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v28.go @@ -0,0 +1,746 @@ +package schemaversion + +import ( + "fmt" + "strconv" + "strings" +) + +// V28 migrates singlestat panels to stat/gauge panels and removes deprecated variable properties. +// +// The migration performs two main tasks: +// 1. Migrates singlestat panels to either stat or gauge panels based on their configuration +// 2. Removes deprecated variable properties (tags, tagsQuery, tagValuesQuery, useTags) +// +// The migration includes comprehensive logic from the frontend: +// - Panel type migration (singlestat -> stat/gauge) +// - Field config migration with thresholds, mappings, and display options +// - Options migration including reduceOptions, orientation, and other panel-specific settings +// - Support for both angular singlestat and grafana-singlestat-panel migrations +// +// Example before migration: +// +// "panels": [ +// { +// "type": "singlestat", +// "gauge": { "show": true }, +// "targets": [{ "refId": "A" }] +// } +// ], +// "templating": { +// "list": [ +// { "name": "var1", "tags": ["tag1"], "tagsQuery": "query", "tagValuesQuery": "values", "useTags": true } +// ] +// } +// +// Example after migration: +// +// "panels": [ +// { +// "type": "gauge", +// "targets": [{ "refId": "A" }] +// } +// ], +// "templating": { +// "list": [ +// { "name": "var1" } +// ] +// } +type v28Migrator struct { + panelProvider PanelPluginInfoProvider + panelPlugins []PanelPluginInfo + statPanelVersion string // Cached stat panel version +} + +func V28(panelProvider PanelPluginInfoProvider) SchemaVersionMigrationFunc { + // Get stat panel version once during initialization + statPanelPlugin := panelProvider.GetPanelPlugin("stat") + statPanelVersion := "" + if statPanelPlugin.ID != "" { + statPanelVersion = statPanelPlugin.Version + } + + migrator := &v28Migrator{ + panelProvider: panelProvider, + panelPlugins: panelProvider.GetPanels(), + statPanelVersion: statPanelVersion, + } + + return func(dashboard map[string]interface{}) error { + return migrator.migrate(dashboard) + } +} + +func (m *v28Migrator) migrate(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 28 + + // Migrate singlestat panels + if panels, ok := dashboard["panels"].([]interface{}); ok { + if err := m.processPanels(panels); err != nil { + return err + } + } + + // Remove deprecated variable properties + if templating, ok := dashboard["templating"].(map[string]interface{}); ok { + if list, ok := templating["list"].([]interface{}); ok { + for _, v := range list { + if variable, ok := v.(map[string]interface{}); ok { + removeDeprecatedVariableProperties(variable) + } + } + } + } + + return nil +} + +func (m *v28Migrator) processPanels(panels []interface{}) error { + for _, panel := range panels { + p, ok := panel.(map[string]interface{}) + if !ok { + continue + } + + // Process nested panels if this is a row panel + if p["type"] == "row" { + if nestedPanels, ok := p["panels"].([]interface{}); ok { + if err := m.processPanels(nestedPanels); err != nil { + return err + } + } + continue + } + + // Migrate singlestat panels + if p["type"] == "singlestat" || p["type"] == "grafana-singlestat-panel" { + if err := m.migrateSinglestatPanel(p); err != nil { + return err + } + } + + // Normalize existing stat panels to ensure they have current default options + if p["type"] == "stat" { + m.normalizeStatPanel(p) + } + } + + return nil +} + +func (m *v28Migrator) migrateSinglestatPanel(panel map[string]interface{}) error { + targetType := "stat" + + // NOTE: DashboardMigrator's migrateSinglestat function has some logic that never gets called + // migrateSinglestat will only run if (panel.type === 'singlestat') + // but this will not be the case because PanelModel runs restoreModel in the constructor + // and since singlestat is in the autoMigrateAngular map, it will be migrated to stat, + // and therefore migrateSinglestat will never run so this logic inside of it will never apply + // if ((panel as any).gauge?.show) { + // gaugePanelPlugin.meta = config.panels['gauge'] + // panel.changePlugin(gaugePanelPlugin) + + // Store original type for migration context (only for stat/gauge migration) + // This matches the frontend behavior where autoMigrateFrom is set in PanelModel.restoreModel + originalType := panel["type"].(string) + panel["autoMigrateFrom"] = panel["type"] + panel["type"] = targetType + + // Use cached stat panel version + if m.statPanelVersion == "" { + return NewMigrationError("stat panel plugin not found when migrating dashboard to schema version 28", 28, LATEST_VERSION) + } + + panel["pluginVersion"] = m.statPanelVersion + + // Migrate panel options and field config + m.migrateSinglestatOptions(panel, originalType) + + return nil +} + +// normalizeStatPanel ensures existing stat panels have all current default options +func (m *v28Migrator) normalizeStatPanel(panel map[string]interface{}) { + if panel["options"] == nil { + panel["options"] = map[string]interface{}{} + } + + options := panel["options"].(map[string]interface{}) + + // Apply missing default options that might not be present in older stat panels + if _, exists := options["percentChangeColorMode"]; !exists { + options["percentChangeColorMode"] = "standard" + } + + // Ensure other critical defaults are present + if _, exists := options["justifyMode"]; !exists { + options["justifyMode"] = "auto" + } + + if _, exists := options["textMode"]; !exists { + options["textMode"] = "auto" + } + + if _, exists := options["wideLayout"]; !exists { + options["wideLayout"] = true + } + + if _, exists := options["showPercentChange"]; !exists { + options["showPercentChange"] = false + } +} + +// migrateSinglestatOptions handles the complete migration of singlestat panel options and field config +func (m *v28Migrator) migrateSinglestatOptions(panel map[string]interface{}, originalType string) { + // Initialize field config if not present + if panel["fieldConfig"] == nil { + panel["fieldConfig"] = map[string]interface{}{ + "defaults": map[string]interface{}{}, + "overrides": []interface{}{}, + } + } + + fieldConfig := panel["fieldConfig"].(map[string]interface{}) + defaults := fieldConfig["defaults"].(map[string]interface{}) + + // Migrate from angular singlestat configuration using appropriate strategy + if originalType == "grafana-singlestat-panel" { + m.migrateGrafanaSinglestatPanel(panel, defaults) + } else { + m.migratetSinglestat(panel, defaults) + } + + // Apply shared migration logic + m.applySharedSinglestatMigration(defaults) + + // Clean up old angular properties after migration + m.cleanupAngularProperties(panel) +} + +// getDefaultStatOptions returns the default options structure for stat panels +func (m *v28Migrator) getDefaultStatOptions() map[string]interface{} { + return map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"mean"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + } +} + +// migratetSinglestat handles explicit migration from 'singlestat' panels +// Based on explicit migration logic in DashboardMigrator.ts +func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults map[string]interface{}) { + angularOpts := m.extractAngularOptions(panel) + + // Explicit migration uses standard stat panel defaults + options := m.getDefaultStatOptions() + + // Explicit migration: always set a reducer with fallback + var valueName string + if vn, ok := angularOpts["valueName"].(string); ok { + valueName = vn + } + + if reducer := m.getReducerForValueName(valueName); reducer != "" { + options["reduceOptions"].(map[string]interface{})["calcs"] = []string{reducer} + } else { + // Explicit migration fallback: use mean for invalid reducers + options["reduceOptions"].(map[string]interface{})["calcs"] = []string{"mean"} + } + + // Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings) + m.migrateThresholds(angularOpts, defaults) + + // Apply common angular option migrations (value mappings can now use threshold colors) + m.applyCommonAngularMigration(panel, defaults, options, angularOpts) + + panel["options"] = options +} + +// migrateGrafanaSinglestatPanel handles auto-migration from 'grafana-singlestat-panel' +// Based on frontend changePlugin() and sharedSingleStatPanelChangedHandler logic +func (m *v28Migrator) migrateGrafanaSinglestatPanel(panel map[string]interface{}, defaults map[string]interface{}) { + angularOpts := m.extractAngularOptions(panel) + + // Auto-migration uses different defaults (matches frontend changePlugin behavior) + options := map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"lastNotNull"}, // Auto-migration default + "fields": "", + "values": false, + }, + "orientation": "auto", // Auto-migration uses auto + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + } + + // Auto-migration: only override if valid, otherwise keep default "lastNotNull" + var valueName string + if vn, ok := angularOpts["valueName"].(string); ok { + valueName = vn + } + + if reducer := m.getReducerForValueName(valueName); reducer != "" { + options["reduceOptions"].(map[string]interface{})["calcs"] = []string{reducer} + } + // No fallback - keeps the auto-migration default "lastNotNull" + + // Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings) + m.migrateThresholds(angularOpts, defaults) + + // Apply common angular option migrations (value mappings can now use threshold colors) + m.applyCommonAngularMigration(panel, defaults, options, angularOpts) + + panel["options"] = options +} + +// migrateThresholds handles threshold migration for both singlestat panel types +// Both panel types now create DEFAULT_THRESHOLDS when threshold string is empty (consolidated behavior) +func (m *v28Migrator) migrateThresholds(angularOpts map[string]interface{}, defaults map[string]interface{}) { + if thresholds, ok := angularOpts["thresholds"].(string); ok { + if colors, ok := angularOpts["colors"].([]interface{}); ok { + if thresholds != "" { + // Non-empty thresholds: use normal migration + m.migrateThresholdsAndColors(defaults, thresholds, colors) + } else { + // Empty thresholds: use frontend DEFAULT_THRESHOLDS fallback (both panel types) + defaults["thresholds"] = map[string]interface{}{ + "mode": "absolute", + "steps": []interface{}{ + map[string]interface{}{ + "color": "green", + "value": nil, + }, + map[string]interface{}{ + "color": "red", + "value": 80, + }, + }, + } + } + } + } +} + +// applyCommonAngularMigration applies migrations common to both singlestat types +func (m *v28Migrator) applyCommonAngularMigration(panel map[string]interface{}, defaults map[string]interface{}, options map[string]interface{}, angularOpts map[string]interface{}) { + // Migrate table column + // Based on sharedSingleStatPanelChangedHandler line ~125: options.reduceOptions.fields = `/^${prevPanel.tableColumn}$/` + if tableColumn, ok := angularOpts["tableColumn"].(string); ok && tableColumn != "" { + options["reduceOptions"].(map[string]interface{})["fields"] = "/^" + tableColumn + "$/" + } + + // Migrate format to unit + // Based on sharedSingleStatPanelChangedHandler line ~130: defaults.unit = prevPanel.format + if format, ok := angularOpts["format"].(string); ok { + defaults["unit"] = format + } + + // Migrate decimals + if decimals, ok := angularOpts["decimals"]; ok { + defaults["decimals"] = decimals + } + + // Migrate null point mode + if nullPointMode, ok := angularOpts["nullPointMode"]; ok { + defaults["nullValueMode"] = nullPointMode + } + + // Migrate null text + if nullText, ok := angularOpts["nullText"].(string); ok { + defaults["noValue"] = nullText + } + + // Migrate value mappings (thresholds should already be migrated) + valueMaps, _ := angularOpts["valueMaps"].([]interface{}) + m.migrateValueMappings(angularOpts, defaults, valueMaps) + + // Migrate sparkline configuration + // Based on statPanelChangedHandler lines ~25-35: sparkline migration logic + if sparkline, ok := angularOpts["sparkline"].(map[string]interface{}); ok { + if show, ok := sparkline["show"].(bool); ok && show { + options["graphMode"] = "area" + + // Handle sparkline color + // Based on statPanelChangedHandler lines ~30-35: sparkline lineColor handling + if lineColor, ok := sparkline["lineColor"].(string); ok { + defaults["color"] = map[string]interface{}{ + "mode": "fixed", + "fixedColor": lineColor, + } + } + } else { + options["graphMode"] = "none" + } + } else { + // Default to no graph mode if no sparkline configuration + options["graphMode"] = "none" + } + + // Migrate color configuration + // Based on statPanelChangedHandler lines ~35-45: colorBackground and colorValue migration + if colorBackground, ok := angularOpts["colorBackground"].(bool); ok && colorBackground { + options["colorMode"] = "background" + } else if colorValue, ok := angularOpts["colorValue"].(bool); ok && colorValue { + options["colorMode"] = "value" + } else { + options["colorMode"] = "none" + } + + // Migrate text mode + // Based on statPanelChangedHandler lines ~45-47: valueName === 'name' migration + if valueName, ok := angularOpts["valueName"].(string); ok && valueName == "name" { + options["textMode"] = "name" + } + + if angularOpts["gauge"] != nil && angularOpts["gauge"].(map[string]interface{})["show"] == true { + defaults["min"] = angularOpts["gauge"].(map[string]interface{})["minValue"] + defaults["max"] = angularOpts["gauge"].(map[string]interface{})["maxValue"] + } +} + +// applySharedSinglestatMigration applies shared migration logic for all singlestat panels +// Based on sharedSingleStatMigrationHandler in packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +func (m *v28Migrator) applySharedSinglestatMigration(defaults map[string]interface{}) { + // Ensure thresholds have proper structure + if thresholds, ok := defaults["thresholds"].(map[string]interface{}); ok { + if steps, ok := thresholds["steps"].([]interface{}); ok { + // Ensure first threshold is -Infinity (represented as null in JSON) + if len(steps) > 0 { + if firstStep, ok := steps[0].(map[string]interface{}); ok { + if firstStep["value"] == nil { + firstStep["value"] = nil // Use null instead of -math.Inf(1) + } + } + } + } + } + + // Handle percent/percentunit units + // Based on sharedSingleStatMigrationHandler lines ~280-300: percent/percentunit min/max handling + if unit, ok := defaults["unit"].(string); ok { + switch unit { + case "percent": + if defaults["min"] == nil { + defaults["min"] = 0 + } + if defaults["max"] == nil { + defaults["max"] = 100 + } + case "percentunit": + if defaults["min"] == nil { + defaults["min"] = 0 + } + if defaults["max"] == nil { + defaults["max"] = 1 + } + } + } +} + +// Helper functions + +func (m *v28Migrator) extractAngularOptions(panel map[string]interface{}) map[string]interface{} { + // Some panels might have angular options directly in the root + // Check for common angular properties + angularProps := []string{ + "valueName", "tableColumn", "format", "decimals", "nullPointMode", "nullText", + "thresholds", "colors", "valueMaps", "gauge", "sparkline", "colorBackground", "colorValue", + } + for _, prop := range angularProps { + if _, exists := panel[prop]; exists { + return panel + } + } + + return map[string]interface{}{} +} + +// getReducerForValueName returns the mapped reducer or empty string for invalid values +func (m *v28Migrator) getReducerForValueName(valueName string) string { + reducerMap := map[string]string{ + "min": "min", + "max": "max", + "mean": "mean", + "median": "median", + "sum": "sum", + "count": "count", + "first": "firstNotNull", + "last": "lastNotNull", + "name": "lastNotNull", + "current": "lastNotNull", + "total": "sum", + } + + if reducer, ok := reducerMap[valueName]; ok { + return reducer + } + + return "" +} + +func (m *v28Migrator) migrateThresholdsAndColors(defaults map[string]interface{}, thresholdsStr string, colors []interface{}) { + // Parse thresholds string (e.g., "10,20,30") + // Based on sharedSingleStatPanelChangedHandler lines ~145-165: Convert thresholds and color values + thresholds := []interface{}{} + thresholdValues := strings.Split(thresholdsStr, ",") + + // Create threshold steps + for i, color := range colors { + step := map[string]interface{}{ + "color": color, + } + + if i == 0 { + step["value"] = nil + } else if i-1 < len(thresholdValues) { + if val, err := strconv.ParseFloat(strings.TrimSpace(thresholdValues[i-1]), 64); err == nil { + step["value"] = val + } + } + + thresholds = append(thresholds, step) + } + + defaults["thresholds"] = map[string]interface{}{ + "mode": "absolute", + "steps": thresholds, + } +} + +func (m *v28Migrator) migrateValueMappings(panel map[string]interface{}, defaults map[string]interface{}, valueMappings []interface{}) { + mappings := []interface{}{} + mappingType := panel["mappingType"] + + if mappingType == nil { + if panel["valueMaps"] != nil && len(panel["valueMaps"].([]interface{})) > 0 { + mappingType = 1 + } else if panel["rangeMaps"] != nil && len(panel["rangeMaps"].([]interface{})) > 0 { + mappingType = 2 + } + } + + switch mappingType { + case 1: + for _, valueMap := range valueMappings { + valueMapping := valueMap.(map[string]interface{}) + upgradedMapping := m.upgradeOldAngularValueMapping(valueMapping, defaults["thresholds"]) + if upgradedMapping != nil { + mappings = append(mappings, upgradedMapping) + } + } + case 2: + // Handle range mappings + if rangeMaps, ok := panel["rangeMaps"].([]interface{}); ok { + for _, rangeMap := range rangeMaps { + rangeMapping := rangeMap.(map[string]interface{}) + upgradedMapping := m.upgradeOldAngularValueMapping(rangeMapping, defaults["thresholds"]) + if upgradedMapping != nil { + mappings = append(mappings, upgradedMapping) + } + } + } + } + + defaults["mappings"] = mappings +} + +// upgradeOldAngularValueMapping converts old angular value mappings to new format +// Based on upgradeOldAngularValueMapping in packages/grafana-data/src/utils/valueMappings.ts +func (m *v28Migrator) upgradeOldAngularValueMapping(old map[string]interface{}, thresholds interface{}) map[string]interface{} { + valueMaps := map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{}, + } + newMappings := []interface{}{} + + // Use the color we would have picked from thresholds + var color interface{} + if value, ok := old["value"]; ok { + if numeric, err := m.parseNumericValue(value); err == nil { + if thresholdsMap, ok := thresholds.(map[string]interface{}); ok { + if steps, ok := thresholdsMap["steps"].([]interface{}); ok { + level := m.getActiveThreshold(numeric, steps) + if level != nil { + if levelColor, ok := level["color"]; ok { + color = levelColor + } + } + } + } + } + } + + // Determine mapping type + mappingType := old["type"] + if mappingType == nil { + // Try to guess from available properties + if old["value"] != nil { + mappingType = 1 // ValueToText + } else if old["from"] != nil || old["to"] != nil { + mappingType = 2 // RangeToText + } + } + + switch mappingType { + case 1: // ValueToText + if value, ok := old["value"]; ok && value != nil { + if valueStr, ok := value.(string); ok && valueStr == "null" { + newMappings = append(newMappings, map[string]interface{}{ + "type": "special", + "options": map[string]interface{}{ + "match": "null", + "result": map[string]interface{}{"text": old["text"], "color": color}, + }, + }) + } else { + valueMaps["options"].(map[string]interface{})[fmt.Sprintf("%v", value)] = map[string]interface{}{ + "text": old["text"], + "color": color, + } + } + } + case 2: // RangeToText + from := old["from"] + to := old["to"] + if (from != nil && fmt.Sprintf("%v", from) == "null") || (to != nil && fmt.Sprintf("%v", to) == "null") { + newMappings = append(newMappings, map[string]interface{}{ + "type": "special", + "options": map[string]interface{}{ + "match": "null", + "result": map[string]interface{}{"text": old["text"], "color": color}, + }, + }) + } else { + var fromVal, toVal interface{} + if from != nil { + if fromStr, ok := from.(string); ok { + if fromFloat, err := strconv.ParseFloat(fromStr, 64); err == nil { + fromVal = fromFloat + } + } else { + fromVal = from + } + } + if to != nil { + if toStr, ok := to.(string); ok { + if toFloat, err := strconv.ParseFloat(toStr, 64); err == nil { + toVal = toFloat + } + } else { + toVal = to + } + } + + newMappings = append(newMappings, map[string]interface{}{ + "type": "range", + "options": map[string]interface{}{ + "from": fromVal, + "to": toVal, + "result": map[string]interface{}{"text": old["text"], "color": color}, + }, + }) + } + } + + // Add valueMaps if it has options + if len(valueMaps["options"].(map[string]interface{})) > 0 { + newMappings = append([]interface{}{valueMaps}, newMappings...) + } + + if len(newMappings) > 0 { + return newMappings[0].(map[string]interface{}) + } + + return nil +} + +// getActiveThreshold finds the active threshold for a given value +// Based on getActiveThreshold in packages/grafana-data/src/field/thresholds.ts +func (m *v28Migrator) getActiveThreshold(value float64, steps []interface{}) map[string]interface{} { + for i := len(steps) - 1; i >= 0; i-- { + if step, ok := steps[i].(map[string]interface{}); ok { + if stepValue, ok := step["value"]; ok { + if stepValue == nil { + // First step with null value (represents -Infinity) + return step + } + if stepFloat, ok := stepValue.(float64); ok && value >= stepFloat { + return step + } + } + } + } + return nil +} + +// parseNumericValue converts various types to float64 for threshold calculations +func (m *v28Migrator) parseNumericValue(value interface{}) (float64, error) { + switch v := value.(type) { + case string: + return strconv.ParseFloat(v, 64) + case float64: + return v, nil + case float32: + return float64(v), nil + case int: + return float64(v), nil + case int32: + return float64(v), nil + case int64: + return float64(v), nil + default: + return 0, fmt.Errorf("cannot convert %T to numeric value", value) + } +} + +// cleanupAngularProperties removes old angular properties after migration +// Based on PanelModel.clearPropertiesBeforePluginChange in public/app/features/dashboard/state/PanelModel.ts +func (m *v28Migrator) cleanupAngularProperties(panel map[string]interface{}) { + // Remove PanelModel's autoMigrateFrom property + delete(panel, "autoMigrateFrom") + + // Remove angular singlestat properties + delete(panel, "valueName") + delete(panel, "format") + delete(panel, "decimals") + delete(panel, "thresholds") + delete(panel, "colors") + delete(panel, "gauge") + delete(panel, "sparkline") + delete(panel, "colorBackground") + delete(panel, "colorValue") + delete(panel, "nullPointMode") + delete(panel, "nullText") + delete(panel, "valueMaps") + delete(panel, "tableColumn") + delete(panel, "angular") + // Remove legacy options properties + if options, ok := panel["options"].(map[string]interface{}); ok { + delete(options, "valueOptions") + delete(options, "thresholds") + delete(options, "valueMaps") + delete(options, "minValue") + delete(options, "maxValue") + } +} + +// removeDeprecatedVariableProperties removes deprecated properties from variables +// Based on DashboardMigrator.ts v28 migration: variable property cleanup +func removeDeprecatedVariableProperties(variable map[string]interface{}) { + // Remove deprecated properties + delete(variable, "tags") + delete(variable, "tagsQuery") + delete(variable, "tagValuesQuery") + delete(variable, "useTags") +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v28_test.go b/apps/dashboard/pkg/migration/schemaversion/v28_test.go new file mode 100644 index 00000000000..f61e332f1ac --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v28_test.go @@ -0,0 +1,726 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" + "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" +) + +func TestV28(t *testing.T) { + tests := []migrationTestCase{ + { + name: "migrate angular singlestat to stat panel", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "avg", + "format": "ms", + "decimals": 2, + "thresholds": "10,20,30", + "colors": []interface{}{"green", "yellow", "red"}, + "gauge": map[string]interface{}{ + "show": false, + }, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "tags": []interface{}{"tag1"}, + "tagsQuery": "query", + "tagValuesQuery": "values", + "useTags": true, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"mean"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "ms", + "decimals": 2, + "thresholds": map[string]interface{}{ + "mode": "absolute", + "steps": []interface{}{ + map[string]interface{}{ + "color": "green", + "value": nil, + }, + map[string]interface{}{ + "color": "yellow", + "value": 10.0, + }, + map[string]interface{}{ + "color": "red", + "value": 20.0, + }, + }, + }, + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + }, + }, + }, + }, + }, + { + name: "migrate angular singlestat to stat panel with gauge options", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "current", + "format": "percent", + "gauge": map[string]interface{}{ + "show": true, + "thresholdMarkers": true, + "thresholdLabels": false, + }, + "sparkline": map[string]interface{}{ + "show": true, + "lineColor": "#ff0000", + }, + "colorBackground": true, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"lastNotNull"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "percent", + "min": 0, + "max": 100, + "color": map[string]interface{}{ + "mode": "fixed", + "fixedColor": "#ff0000", + }, + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate grafana-singlestat-panel to stat panel", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "grafana-singlestat-panel", + "valueName": "current", + "format": "percent", + "gauge": map[string]interface{}{ + "show": true, + "thresholdMarkers": true, + "thresholdLabels": false, + }, + "sparkline": map[string]interface{}{ + "show": true, + "lineColor": "#ff0000", + }, + "colorBackground": true, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"lastNotNull"}, + "fields": "", + "values": false, + }, + "orientation": "auto", + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "percent", + "min": 0, + "max": 100, + "color": map[string]interface{}{ + "mode": "fixed", + "fixedColor": "#ff0000", + }, + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate singlestat with empty thresholds to stat panel", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "min", + "format": "bytes", + "thresholds": "", + "colors": []interface{}{"green", "red"}, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"min"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "bytes", + "thresholds": map[string]interface{}{ + "mode": "absolute", + "steps": []interface{}{ + map[string]interface{}{ + "color": "green", + "value": nil, + }, + map[string]interface{}{ + "color": "red", + "value": 80, + }, + }, + }, + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate grafana-singlestat-panel with empty thresholds to stat panel", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "grafana-singlestat-panel", + "valueName": "max", + "format": "short", + "thresholds": "", + "colors": []interface{}{"green", "red"}, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"max"}, + "fields": "", + "values": false, + }, + "orientation": "auto", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "short", + "thresholds": map[string]interface{}{ + "mode": "absolute", + "steps": []interface{}{ + map[string]interface{}{ + "color": "green", + "value": nil, + }, + map[string]interface{}{ + "color": "red", + "value": 80, + }, + }, + }, + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate singlestat with value mappings and threshold colors", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "current", + "format": "short", + "thresholds": "50,80", + "colors": []interface{}{"green", "orange", "red"}, + "valueMaps": []interface{}{ + map[string]interface{}{ + "value": "40", + "text": "Warning", + }, + map[string]interface{}{ + "value": "90", + "text": "Critical", + }, + }, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"lastNotNull"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "short", + "thresholds": map[string]interface{}{ + "mode": "absolute", + "steps": []interface{}{ + map[string]interface{}{ + "color": "green", + "value": nil, + }, + map[string]interface{}{ + "color": "orange", + "value": 50.0, + }, + map[string]interface{}{ + "color": "red", + "value": 80.0, + }, + }, + }, + "mappings": []interface{}{ + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "40": map[string]interface{}{ + "text": "Warning", + "color": "green", + }, + }, + }, + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "90": map[string]interface{}{ + "text": "Critical", + "color": "red", + }, + }, + }, + }, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate singlestat with invalid valueName fallback to mean", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "invalid_reducer", + "format": "short", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"mean"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "short", + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "migrate grafana-singlestat-panel with invalid valueName keeps lastNotNull", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "grafana-singlestat-panel", + "valueName": "invalid_reducer", + "format": "short", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"lastNotNull"}, + "fields": "", + "values": false, + }, + "orientation": "auto", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "short", + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + { + name: "handle nested panels in rows", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "row", + "panels": []interface{}{ + map[string]interface{}{ + "id": 2, + "type": "singlestat", + "valueName": "sum", + "format": "bytes", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "row", + "panels": []interface{}{ + map[string]interface{}{ + "id": 2, + "type": "stat", + "options": map[string]interface{}{ + "reduceOptions": map[string]interface{}{ + "calcs": []string{"sum"}, + "fields": "", + "values": false, + }, + "orientation": "horizontal", + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true, + }, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "unit": "bytes", + "mappings": []interface{}{}, + }, + "overrides": []interface{}{}, + }, + "pluginVersion": "1.0.0", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + }, + }, + { + name: "remove deprecated variable properties", + input: map[string]interface{}{ + "schemaVersion": 27, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "type": "query", + "tags": []interface{}{"tag1", "tag2"}, + "tagsQuery": "SELECT * FROM tags", + "tagValuesQuery": "SELECT value FROM tag_values", + "useTags": true, + }, + map[string]interface{}{ + "name": "var2", + "type": "custom", + // No deprecated properties + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 28, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "type": "query", + }, + map[string]interface{}{ + "name": "var2", + "type": "custom", + }, + }, + }, + }, + }, + } + + errorTests := []migrationTestCase{ + { + name: "throw an error if stat panel plugin not found", + input: map[string]interface{}{ + "schemaVersion": 27, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "singlestat", + "valueName": "avg", + "format": "ms", + "decimals": 2, + "thresholds": "10,20,30", + "colors": []interface{}{"green", "yellow", "red"}, + "gauge": map[string]interface{}{ + "show": false, + }, + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "tags": []interface{}{"tag1"}, + "tagsQuery": "query", + "tagValuesQuery": "values", + "useTags": true, + }, + }, + }, + }, + expectedError: "schema migration from version 28 to 41 failed: stat panel plugin not found when migrating dashboard to schema version 28", + }, + } + + runMigrationTests(t, tests, schemaversion.V28(testutil.GetTestPanelProvider())) + runMigrationTests(t, errorTests, schemaversion.V28(testutil.GetTestPanelProviderWithCustomPanels([]schemaversion.PanelPluginInfo{ + {ID: "fake-plugin", Version: "1.0.0"}, + }))) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v30.go b/apps/dashboard/pkg/migration/schemaversion/v30.go index d892abecb50..41b890e5a76 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v30.go +++ b/apps/dashboard/pkg/migration/schemaversion/v30.go @@ -186,108 +186,21 @@ func upgradeValueMappings(oldMappings []interface{}, thresholds map[string]inter return oldMappings } - valueMaps := map[string]interface{}{ - "type": "value", - "options": map[string]interface{}{}, + // Check if all mappings are already in the new format + if areAllMappingsNewFormat(oldMappings) { + return oldMappings } + valueMaps := createValueMaps() var newMappings []interface{} hasValueMappings := false for _, mapping := range oldMappings { if mappingMap, ok := mapping.(map[string]interface{}); ok { - // Check if this is already the new format - if mappingType, ok := mappingMap["type"].(string); ok && mappingType != "" { - if mappingType == "value" { - // Consolidate existing value mappings - if options, ok := mappingMap["options"].(map[string]interface{}); ok { - valueMapsOptions := valueMaps["options"].(map[string]interface{}) - for k, v := range options { - valueMapsOptions[k] = v - } - hasValueMappings = true - } - } else { - newMappings = append(newMappings, mappingMap) - } - continue - } - - // Handle legacy format - var color interface{} - if thresholds != nil { - // Try to get color from threshold based on the mapping value - if value, ok := mappingMap["value"]; ok { - if valueStr, ok := value.(string); ok { - if numeric, err := strconv.ParseFloat(valueStr, 64); err == nil { - color = getActiveThresholdColor(numeric, thresholds) - } - } - } - // For range mappings, use the 'from' value to determine color - if fromVal, ok := mappingMap["from"]; ok { - if fromStr, ok := fromVal.(string); ok { - if numeric, err := strconv.ParseFloat(fromStr, 64); err == nil { - color = getActiveThresholdColor(numeric, thresholds) - } - } - } - } - - // Convert legacy type numbers to new format - if mappingType, ok := mappingMap["type"].(float64); ok { - switch int(mappingType) { - case 1: // ValueToText - if value, ok := mappingMap["value"]; ok { - if valueStr, ok := value.(string); ok && valueStr == "null" { - // Handle null values as special value mapping - // For null values, use the base threshold color (lowest step) - if thresholds != nil { - color = getBaseThresholdColor(thresholds) - } - newMappings = append(newMappings, map[string]interface{}{ - "type": "special", - "options": map[string]interface{}{ - "match": "null", - "result": map[string]interface{}{ - "text": mappingMap["text"], - "color": color, - }, - }, - }) - } else { - // Regular value mapping - valueMapsOptions := valueMaps["options"].(map[string]interface{}) - result := map[string]interface{}{ - "text": mappingMap["text"], - } - if color != nil { - result["color"] = color - } - valueMapsOptions[stringifyValue(value)] = result - hasValueMappings = true - } - } - case 2: // RangeToText - result := map[string]interface{}{ - "text": mappingMap["text"], - } - if color != nil { - result["color"] = color - } - - from, _ := strconv.ParseFloat(stringifyValue(mappingMap["from"]), 64) - to, _ := strconv.ParseFloat(stringifyValue(mappingMap["to"]), 64) - - newMappings = append(newMappings, map[string]interface{}{ - "type": "range", - "options": map[string]interface{}{ - "from": from, - "to": to, - "result": result, - }, - }) - } + if isNewFormatMapping(mappingMap) { + hasValueMappings = processNewFormatMapping(mappingMap, valueMaps, &newMappings, hasValueMappings) + } else { + hasValueMappings = processLegacyMapping(mappingMap, thresholds, valueMaps, &newMappings, hasValueMappings) } } } @@ -300,6 +213,168 @@ func upgradeValueMappings(oldMappings []interface{}, thresholds map[string]inter return newMappings } +// areAllMappingsNewFormat checks if all mappings are already in the new format +func areAllMappingsNewFormat(oldMappings []interface{}) bool { + for _, mapping := range oldMappings { + if mappingMap, ok := mapping.(map[string]interface{}); ok { + if mappingType, ok := mappingMap["type"].(string); ok && mappingType != "" { + // This is already in new format, keep it as-is + continue + } else { + // Found a legacy format mapping, need to process + return false + } + } + } + return true +} + +// createValueMaps creates the base value maps structure +func createValueMaps() map[string]interface{} { + return map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{}, + } +} + +// isNewFormatMapping checks if a mapping is already in the new format +func isNewFormatMapping(mappingMap map[string]interface{}) bool { + if mappingType, ok := mappingMap["type"].(string); ok && mappingType != "" { + return true + } + return false +} + +// processNewFormatMapping handles mappings that are already in the new format +func processNewFormatMapping(mappingMap map[string]interface{}, valueMaps map[string]interface{}, newMappings *[]interface{}, hasValueMappings bool) bool { + mappingType := mappingMap["type"].(string) + if mappingType == "value" { + // Consolidate existing value mappings + if options, ok := mappingMap["options"].(map[string]interface{}); ok { + valueMapsOptions := valueMaps["options"].(map[string]interface{}) + for k, v := range options { + valueMapsOptions[k] = v + } + hasValueMappings = true + } + } else { + *newMappings = append(*newMappings, mappingMap) + } + return hasValueMappings +} + +// processLegacyMapping handles legacy format mappings +func processLegacyMapping(mappingMap map[string]interface{}, thresholds map[string]interface{}, valueMaps map[string]interface{}, newMappings *[]interface{}, hasValueMappings bool) bool { + color := getColorFromThresholds(mappingMap, thresholds) + + // Convert legacy type numbers to new format + if mappingType, ok := mappingMap["type"].(float64); ok { + switch int(mappingType) { + case 1: // ValueToText + hasValueMappings = processValueToTextMapping(mappingMap, color, thresholds, valueMaps, newMappings, hasValueMappings) + case 2: // RangeToText + processRangeToTextMapping(mappingMap, color, newMappings) + } + } + + return hasValueMappings +} + +// getColorFromThresholds extracts color from thresholds based on mapping values +func getColorFromThresholds(mappingMap map[string]interface{}, thresholds map[string]interface{}) interface{} { + if thresholds == nil { + return nil + } + + // Try to get color from threshold based on the mapping value + if value, ok := mappingMap["value"]; ok { + if valueStr, ok := value.(string); ok { + if numeric, err := strconv.ParseFloat(valueStr, 64); err == nil { + return getActiveThresholdColor(numeric, thresholds) + } + } + } + + // For range mappings, use the 'from' value to determine color + if fromVal, ok := mappingMap["from"]; ok { + if fromStr, ok := fromVal.(string); ok { + if numeric, err := strconv.ParseFloat(fromStr, 64); err == nil { + return getActiveThresholdColor(numeric, thresholds) + } + } + } + + return nil +} + +// processValueToTextMapping handles ValueToText legacy mappings +func processValueToTextMapping(mappingMap map[string]interface{}, color interface{}, thresholds map[string]interface{}, valueMaps map[string]interface{}, newMappings *[]interface{}, hasValueMappings bool) bool { + if value, ok := mappingMap["value"]; ok { + if valueStr, ok := value.(string); ok && valueStr == "null" { + // Handle null values as special value mapping + processNullValueMapping(mappingMap, color, thresholds, newMappings) + } else { + // Regular value mapping + processRegularValueMapping(mappingMap, value, color, valueMaps) + hasValueMappings = true + } + } + return hasValueMappings +} + +// processNullValueMapping creates a special value mapping for null values +func processNullValueMapping(mappingMap map[string]interface{}, color interface{}, thresholds map[string]interface{}, newMappings *[]interface{}) { + // For null values, use the base threshold color (lowest step) + if thresholds != nil && color == nil { + color = getBaseThresholdColor(thresholds) + } + + *newMappings = append(*newMappings, map[string]interface{}{ + "type": "special", + "options": map[string]interface{}{ + "match": "null", + "result": map[string]interface{}{ + "text": mappingMap["text"], + "color": color, + }, + }, + }) +} + +// processRegularValueMapping creates a regular value mapping +func processRegularValueMapping(mappingMap map[string]interface{}, value interface{}, color interface{}, valueMaps map[string]interface{}) { + valueMapsOptions := valueMaps["options"].(map[string]interface{}) + result := map[string]interface{}{ + "text": mappingMap["text"], + } + if color != nil { + result["color"] = color + } + valueMapsOptions[stringifyValue(value)] = result +} + +// processRangeToTextMapping handles RangeToText legacy mappings +func processRangeToTextMapping(mappingMap map[string]interface{}, color interface{}, newMappings *[]interface{}) { + result := map[string]interface{}{ + "text": mappingMap["text"], + } + if color != nil { + result["color"] = color + } + + from, _ := strconv.ParseFloat(stringifyValue(mappingMap["from"]), 64) + to, _ := strconv.ParseFloat(stringifyValue(mappingMap["to"]), 64) + + *newMappings = append(*newMappings, map[string]interface{}{ + "type": "range", + "options": map[string]interface{}{ + "from": from, + "to": to, + "result": result, + }, + }) +} + // getActiveThresholdColor returns the color for a value based on thresholds func getActiveThresholdColor(value float64, thresholds map[string]interface{}) interface{} { if steps, ok := thresholds["steps"].([]interface{}); ok { diff --git a/apps/dashboard/pkg/migration/schemaversion/v30_test.go b/apps/dashboard/pkg/migration/schemaversion/v30_test.go index bbae0e9a412..60bd693c371 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v30_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/v30_test.go @@ -414,6 +414,97 @@ func TestV30(t *testing.T) { }, }, }, + { + name: "already migrated value mappings are preserved correctly", + input: map[string]interface{}{ + "title": "V30 Already Migrated Value Mappings Test Dashboard", + "schemaVersion": 29, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with already migrated value mappings", + "id": 1, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "mappings": []interface{}{ + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "20": map[string]interface{}{ + "color": nil, + "text": "test", + }, + }, + }, + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "30": map[string]interface{}{ + "color": nil, + "text": "test1", + }, + }, + }, + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "40": map[string]interface{}{ + "color": "orange", + "text": "50", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V30 Already Migrated Value Mappings Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with already migrated value mappings", + "id": 1, + "fieldConfig": map[string]interface{}{ + "defaults": map[string]interface{}{ + "mappings": []interface{}{ + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "20": map[string]interface{}{ + "color": nil, + "text": "test", + }, + }, + }, + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "30": map[string]interface{}{ + "color": nil, + "text": "test1", + }, + }, + }, + map[string]interface{}{ + "type": "value", + "options": map[string]interface{}{ + "40": map[string]interface{}{ + "color": "orange", + "text": "50", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { name: "graph panels with different configurations remain unchanged in V30", input: map[string]interface{}{ diff --git a/apps/dashboard/pkg/migration/schemaversion/v33_test.go b/apps/dashboard/pkg/migration/schemaversion/v33_test.go index 61916a1e8e2..26042f94177 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v33_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/v33_test.go @@ -9,7 +9,7 @@ import ( func TestV33(t *testing.T) { // Pass the mock provider to V33 - migration := schemaversion.V33(testutil.GetTestProvider()) + migration := schemaversion.V33(testutil.GetTestDataSourceProvider()) tests := []migrationTestCase{ { diff --git a/apps/dashboard/pkg/migration/schemaversion/v36_test.go b/apps/dashboard/pkg/migration/schemaversion/v36_test.go index 7bc4b5cba80..5d9c5d4c7d9 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v36_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/v36_test.go @@ -9,7 +9,7 @@ import ( func TestV36(t *testing.T) { // Pass the mock provider to V36 - migration := schemaversion.V36(testutil.GetTestProvider()) + migration := schemaversion.V36(testutil.GetTestDataSourceProvider()) tests := []migrationTestCase{ { diff --git a/apps/dashboard/pkg/migration/testdata/input/v26.text2_to_text.json b/apps/dashboard/pkg/migration/testdata/input/v26.text2_to_text.json new file mode 100644 index 00000000000..26b367949c0 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v26.text2_to_text.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 25, + "panels": [ + { + "id": 1, + "type": "text2", + "title": "Text2 Panel" + }, + { + "id": 2, + "type": "text", + "title": "Angular Text Panel", + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + { + "id": 3, + "type": "text2", + "title": "React Text Panel from Angular Panel", + "options": { + "mode": "markdown", + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "angular": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown", + "options": {} + } + } + } + ] +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v27.repeated_panels_and_constant_variable.json b/apps/dashboard/pkg/migration/testdata/input/v27.repeated_panels_and_constant_variable.json new file mode 100644 index 00000000000..cc9b178b05a --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v27.repeated_panels_and_constant_variable.json @@ -0,0 +1,43 @@ +{ + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard", + "schemaVersion": 26, + "panels": [ + { + "id": 1, + "type": "graph", + "repeatPanelId": "panel1" + }, + { + "id": 2, + "type": "graph", + "title": "Normal Panel" + }, + { + "id": 4, + "type": "row", + "title": "Row with repeated panels", + "panels": [ + { + "id": 5, + "type": "graph", + "repeatPanelId": "panel2" + }, + { + "id": 6, + "type": "graph", + "title": "Normal nested panel" + } + ] + } + ], + "templating": { + "list": [ + { + "name": "constant_var", + "type": "constant", + "query": "default_value", + "hide": 0 + } + ] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v28.remove_variable_properties.json b/apps/dashboard/pkg/migration/testdata/input/v28.remove_variable_properties.json new file mode 100644 index 00000000000..f3dc633fc74 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v28.remove_variable_properties.json @@ -0,0 +1,46 @@ +{ + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "schemaVersion": 27, + "panels": [ + ], + "templating": { + "list": [ + { + "name": "query_variable_with_tags", + "type": "query", + "datasource": "prometheus", + "query": "label_values(up, instance)", + "tags": ["instance", "job"], + "tagsQuery": "label_values(up, job)", + "tagValuesQuery": "label_values(up{job=\"$job\"}, instance)", + "useTags": true, + "refresh": 1 + }, + { + "name": "custom_variable_with_tags", + "type": "custom", + "options": [ + {"text": "Option 1", "value": "opt1"}, + {"text": "Option 2", "value": "opt2"} + ], + "tags": ["custom_tag"], + "tagsQuery": "custom query", + "useTags": false + }, + { + "name": "clean_variable", + "type": "textbox", + "options": [ + {"text": "Hello", "value": "World"} + ] + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_and_variable_properties.json b/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_and_variable_properties.json new file mode 100644 index 00000000000..4ebd39ffd3d --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_and_variable_properties.json @@ -0,0 +1,105 @@ +{ + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "schemaVersion": 27, + "panels": [ + { + "type": "singlestat", + "legend": true, + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "grid": { "min": 1, "max": 10 }, + "targets": [{ "refId": "A" }, {}] + }, + { + "type": "singlestat", + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "gauge": { + "show": true, + "thresholdMarkers": true, + "thresholdLabels": false + }, + "grid": { "min": 1, "max": 10 } + }, + { + "type": "singlestat", + "legend": true, + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "grid": { "min": 1, "max": 10 }, + "targets": [{ "refId": "A" }, {}], + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + }, + { + "type": "timeseries", + "targets": [ + { + "refId": "A", + "expr": "rate(http_requests_total[5m])" + } + ] + } + ], + "templating": { + "list": [ + { + "name": "query_variable_with_tags", + "type": "query", + "datasource": "prometheus", + "query": "label_values(up, instance)", + "tags": ["instance", "job"], + "tagsQuery": "label_values(up, job)", + "tagValuesQuery": "label_values(up{job=\"$job\"}, instance)", + "useTags": true, + "refresh": 1 + }, + { + "name": "custom_variable_with_tags", + "type": "custom", + "options": [ + {"text": "Option 1", "value": "opt1"}, + {"text": "Option 2", "value": "opt2"} + ], + "tags": ["custom_tag"], + "tagsQuery": "custom query", + "useTags": false + }, + { + "name": "clean_variable", + "type": "textbox", + "options": [ + {"text": "Hello", "value": "World"} + ] + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_migration.json b/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_migration.json new file mode 100644 index 00000000000..d885d770fbb --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v28.singlestat_migration.json @@ -0,0 +1,265 @@ +{ + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "schemaVersion": 27, + "panels": [ + { + "id": 1, + "type": "singlestat", + "legend": true, + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "grid": { "min": 1, "max": 10 }, + "targets": [{ "refId": "A" }, {}] + }, + { + "id": 2, + "type": "singlestat", + "legend": true, + "thresholds": "", + "colors": ["#FF0000", "green", "orange"], + "grid": { "min": 1, "max": 10 }, + "targets": [{ "refId": "A" }, {}] + }, + { + "id": 3, + "type": "singlestat", + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "gauge": { + "show": true, + "thresholdMarkers": true, + "thresholdLabels": false + }, + "grid": { "min": 1, "max": 10 } + }, + { + "id": 4, + "type": "singlestat", + "legend": true, + "thresholds": "10,20,30", + "colors": ["#FF0000", "green", "orange"], + "grid": { "min": 1, "max": 10 }, + "targets": [{ "refId": "A" }, {}], + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + }, + { + "id": 5, + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": { "type": "prometheus" }, + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "thresholds": "", + "title": "grafana-singlestat-panel", + "type": "grafana-singlestat-panel", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "id": 6, + "datasource": { "type": "prometheus" }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema < 28)", + "type": "stat" + }, + { + "id": 7, + "datasource": { "type": "prometheus" }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat >> Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "id": 8, + "type": "timeseries", + "targets": [ + { + "refId": "A", + "expr": "rate(http_requests_total[5m])" + } + ] + } + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v26.text2_to_text.json b/apps/dashboard/pkg/migration/testdata/output/v26.text2_to_text.json new file mode 100644 index 00000000000..58133d838e9 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v26.text2_to_text.json @@ -0,0 +1,72 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Text2 Panel", + "type": "text" + }, + { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "mode": "markdown", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Angular Text Panel", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "options": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "React Text Panel from Angular Panel", + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 41 +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v27.repeated_panels_and_constant_variable.json b/apps/dashboard/pkg/migration/testdata/output/v27.repeated_panels_and_constant_variable.json new file mode 100644 index 00000000000..2f1587a61de --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v27.repeated_panels_and_constant_variable.json @@ -0,0 +1,91 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Normal Panel", + "type": "graph" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Normal nested panel", + "type": "graph" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with repeated panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "default_value", + "value": "default_value" + }, + "hide": 0, + "name": "constant_var", + "options": [ + { + "selected": true, + "text": "default_value", + "value": "default_value" + } + ], + "query": "default_value", + "type": "textbox" + } + ] + }, + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v28.remove_variable_properties.json b/apps/dashboard/pkg/migration/testdata/output/v28.remove_variable_properties.json new file mode 100644 index 00000000000..c642babb64a --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v28.remove_variable_properties.json @@ -0,0 +1,61 @@ +{ + "panels": [], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [ + { + "datasource": { + "uid": "prometheus" + }, + "name": "query_variable_with_tags", + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "name": "custom_variable_with_tags", + "options": [ + { + "text": "Option 1", + "value": "opt1" + }, + { + "text": "Option 2", + "value": "opt2" + } + ], + "type": "custom" + }, + { + "name": "clean_variable", + "options": [ + { + "text": "Hello", + "value": "World" + } + ], + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_and_variable_properties.json b/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_and_variable_properties.json new file mode 100644 index 00000000000..e3a659a383e --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_and_variable_properties.json @@ -0,0 +1,322 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "max": null, + "min": null, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "20": { + "color": "orange", + "text": "test" + } + }, + "type": "value" + }, + { + "options": { + "30": { + "color": "orange", + "text": "test1" + } + }, + "type": "value" + }, + { + "options": { + "40": { + "color": "orange", + "text": "50" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [ + { + "datasource": { + "uid": "prometheus" + }, + "name": "query_variable_with_tags", + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "name": "custom_variable_with_tags", + "options": [ + { + "text": "Option 1", + "value": "opt1" + }, + { + "text": "Option 2", + "value": "opt2" + } + ], + "type": "custom" + }, + { + "name": "clean_variable", + "options": [ + { + "text": "Hello", + "value": "World" + } + ], + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_migration.json b/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_migration.json new file mode 100644 index 00000000000..949a888b921 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v28.singlestat_migration.json @@ -0,0 +1,561 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 1, + "legend": true, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 2, + "legend": true, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "max": null, + "min": null, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 3, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "20": { + "color": "orange", + "text": "test" + } + }, + "type": "value" + }, + { + "options": { + "30": { + "color": "orange", + "text": "test1" + } + }, + "type": "value" + }, + { + "options": { + "40": { + "color": "orange", + "text": "50" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#FF0000", + "value": null + }, + { + "color": "green", + "value": 10 + }, + { + "color": "orange", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 4, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgb(31, 120, 193)", + "mode": "fixed" + }, + "mappings": [], + "nullValueMode": "connected", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "areaF2" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 5, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "grafana-singlestat-panel", + "type": "stat", + "valueFontSize": "80%" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 6, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testutil/mocks.go b/apps/dashboard/pkg/migration/testutil/mocks.go index 7b7321b8ca1..6835d7f2fcc 100644 --- a/apps/dashboard/pkg/migration/testutil/mocks.go +++ b/apps/dashboard/pkg/migration/testutil/mocks.go @@ -1,9 +1,15 @@ package testutil -import "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +import ( + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) type TestDataSourceProvider struct{} +type TestPanelProvider struct { + customPanels []schemaversion.PanelPluginInfo +} + func (m *TestDataSourceProvider) GetDataSourceInfo() []schemaversion.DataSourceInfo { return []schemaversion.DataSourceInfo{ { @@ -57,7 +63,50 @@ func (m *TestDataSourceProvider) GetDataSourceInfo() []schemaversion.DataSourceI } } -// GetTestProvider returns a singleton instance of the test provider -func GetTestProvider() *TestDataSourceProvider { +func (m *TestPanelProvider) GetPanels() []schemaversion.PanelPluginInfo { + if len(m.customPanels) > 0 { + return m.customPanels + } + + // Default panels + return []schemaversion.PanelPluginInfo{ + { + ID: "gauge", + Version: "1.0.0", + }, + { + ID: "stat", + Version: "1.0.0", + }, + // Note: grafana-singlestat-panel is not included to match frontend test environment + // This ensures both frontend and backend migrations produce the same result + } +} + +func (m *TestPanelProvider) GetPanelPlugin(id string) schemaversion.PanelPluginInfo { + // check if it exists in the list of mocked panels + for _, panel := range m.GetPanels() { + if panel.ID == id { + return panel + } + } + + return schemaversion.PanelPluginInfo{} +} + +// GetTestDataSourceProvider returns a singleton instance of the test provider +func GetTestDataSourceProvider() *TestDataSourceProvider { return &TestDataSourceProvider{} } + +// GetTestPanelProvider returns a singleton instance of the test panel provider +func GetTestPanelProvider() *TestPanelProvider { + return &TestPanelProvider{} +} + +// GetTestPanelProviderWithCustomPanels returns a test panel provider with custom panels +func GetTestPanelProviderWithCustomPanels(customPanels []schemaversion.PanelPluginInfo) *TestPanelProvider { + return &TestPanelProvider{ + customPanels: customPanels, + } +} diff --git a/apps/dashboard/pkg/migration/testutil/utils.go b/apps/dashboard/pkg/migration/testutil/utils.go new file mode 100644 index 00000000000..7800093ecfc --- /dev/null +++ b/apps/dashboard/pkg/migration/testutil/utils.go @@ -0,0 +1,15 @@ +package testutil + +import ( + "encoding/json" + "fmt" +) + +func PrettyPrint(label string, i interface{}) { + b, err := json.MarshalIndent(i, "", " ") + if err != nil { + fmt.Println("error:", err) + return + } + fmt.Println(label, string(b)) +} diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 7736f202c1c..713d1da9cda 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/folder go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff @@ -23,7 +23,7 @@ require ( github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // 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 @@ -37,7 +37,7 @@ require ( github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // 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.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 3612fc451ee..efbd47987b9 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -32,10 +32,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -79,8 +79,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= @@ -107,6 +107,8 @@ go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 30df8cdcd1f..2c9d80d827b 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/iam go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff @@ -23,7 +23,7 @@ require ( github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // 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 @@ -37,7 +37,7 @@ require ( github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // 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.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 3612fc451ee..efbd47987b9 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -32,10 +32,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -79,8 +79,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= @@ -107,6 +107,8 @@ go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 6a3f8d519fd..e4a0e59576c 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/playlist go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 k8s.io/apimachinery v0.33.3 k8s.io/client-go v0.33.3 k8s.io/klog/v2 v2.130.1 @@ -30,7 +30,7 @@ require ( 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.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -46,7 +46,7 @@ require ( 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/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index 88512ac2ed8..c586ffa88d2 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -47,10 +47,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -98,8 +98,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile new file mode 100644 index 00000000000..230bfd4149a --- /dev/null +++ b/apps/plugins/Makefile @@ -0,0 +1,9 @@ +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 \ + --defencoding=none \ No newline at end of file diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod new file mode 100644 index 00000000000..45c31ca73c5 --- /dev/null +++ b/apps/plugins/go.mod @@ -0,0 +1,99 @@ +module github.com/grafana/grafana/apps/plugins + +go 1.24.4 + +require ( + github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 + github.com/grafana/grafana-app-sdk v0.40.3 + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // 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.12.1 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/getkin/kin-openapi v0.132.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // 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.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-test/deep v1.1.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.6.9 // 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/authlib v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // 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.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + 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/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.11.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/apiextensions-apiserver v0.33.3 // indirect + k8s.io/client-go v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect +) diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum new file mode 100644 index 00000000000..4e9f056ba68 --- /dev/null +++ b/apps/plugins/go.sum @@ -0,0 +1,287 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/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.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/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.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= +github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-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.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde h1:ydSrBIOCxJQ84+JU+cyYsOLL40QeXrB7rYfsY/ezU4w= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde/go.mod h1:3MwgP0ISxGviTy3ZUJZsNz/56NNtHztMlH+gcxDt6Tw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +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.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +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.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +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= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= +k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= +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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +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/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/plugins/kinds/cue.mod/module.cue b/apps/plugins/kinds/cue.mod/module.cue new file mode 100644 index 00000000000..95dc1c62455 --- /dev/null +++ b/apps/plugins/kinds/cue.mod/module.cue @@ -0,0 +1,4 @@ +module: "github.com/grafana/grafana/apps/plugins/kinds" +language: { + version: "v0.9.0" +} diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue new file mode 100644 index 00000000000..f16ba2d1a72 --- /dev/null +++ b/apps/plugins/kinds/manifest.cue @@ -0,0 +1,19 @@ +package plugins + +manifest: { + appName: "plugins" + groupOverride: "plugins.grafana.app" + versions: { + "v0alpha1": { + served: false + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + pluginMetaV0Alpha1, + pluginInstallV0Alpha1, + ] + } + } +} diff --git a/apps/plugins/kinds/plugininstall.cue b/apps/plugins/kinds/plugininstall.cue new file mode 100644 index 00000000000..82181fda1d4 --- /dev/null +++ b/apps/plugins/kinds/plugininstall.cue @@ -0,0 +1,13 @@ +package plugins + +pluginInstallV0Alpha1: { + kind: "PluginInstall" + plural: "plugininstalls" + scope: "Namespaced" + schema: { + spec: { + id: string + version: string + } + } +} diff --git a/apps/plugins/kinds/pluginmeta.cue b/apps/plugins/kinds/pluginmeta.cue new file mode 100644 index 00000000000..93f41095707 --- /dev/null +++ b/apps/plugins/kinds/pluginmeta.cue @@ -0,0 +1,220 @@ +package plugins + +import ( + "time" +) + +pluginMetaV0Alpha1: { + kind: "PluginMeta" + plural: "pluginsmeta" + scope: "Namespaced" + schema: { + spec: { + pluginJSON: #JSONData, + } + } +} + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +#JSONData: { + // Unique name of the plugin + id: string + + // Plugin type + type: "app" | "datasource" | "panel" | "renderer" + + // Human-readable name of the plugin + name: string + + // Metadata for the plugin + info: #Info + + // Dependency information + dependencies: #Dependencies + + // Optional fields + alerting?: bool + annotations?: bool + autoEnabled?: bool + backend?: bool + buildMode?: string + builtIn?: bool + category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other" + enterpriseFeatures?: #EnterpriseFeatures + executable?: string + hideFromList?: bool + // +listType=atomic + includes?: [...#Include] + logs?: bool + metrics?: bool + multiValueFilterOperators?: bool + pascalName?: string + preload?: bool + queryOptions?: #QueryOptions + // +listType=atomic + routes?: [...#Route] + skipDataQuery?: bool + state?: "alpha" | "beta" + streaming?: bool + tracing?: bool + iam?: #IAM + // +listType=atomic + roles?: [...#Role] + extensions?: #Extensions +} + +#Info: { + // Required fields + // +listType=set + keywords: [...string] + logos: { + small: string + large: string + } + updated: string & time.Time + version: string + // Optional fields + author?: { + name?: string + email?: string + url?: string + } + description?: string + // +listType=atomic + links?: [...{ + name?: string + url?: string + }] + // +listType=atomic + screenshots?: [...{ + name?: string + path?: string + }] +} + +#Dependencies: { + // Required field + grafanaDependency: string + + // Optional fields + grafanaVersion?: string + // +listType=set + // +listMapKey=id + plugins?: [...{ + id: string + type: "app" | "datasource" | "panel" + name: string + }] + extensions?: { + // +listType=set + exposedComponents?: [...string] + } +} + +#EnterpriseFeatures: { + healthDiagnosticsErrors?: bool | *false + // Allow additional properties + [string]: _ +} + +#Include: { + uid?: string + type?: "dashboard" | "page" | "panel" | "datasource" + name?: string + component?: string + role?: "Admin" | "Editor" | "Viewer" + action?: string + path?: string + addToNav?: bool + defaultNav?: bool + icon?: string +} + +#QueryOptions: { + maxDataPoints?: bool + minInterval?: bool + cacheTimeout?: bool +} + +#Route: { + path?: string + method?: string + url?: string + reqSignedIn?: bool + reqRole?: string + reqAction?: string + // +listType=atomic + headers?: [...string] + body?: [string]: _ + tokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + jwtTokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + // +listType=atomic + urlParams?: [...{ + name?: string + content?: string + }] +} + +#IAM: { + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] +} + +#Role: { + role?: { + name?: string + description?: string + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] + } + // +listType=set + grants?: [...string] +} + +#Extensions: { + // +listType=atomic + addedComponents?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=atomic + addedLinks?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=set + // +listMapKey=id + exposedComponents?: [...{ + id: string + title?: string + description?: string + }] + // +listType=set + // +listMapKey=id + extensionPoints?: [...{ + id: string + title?: string + description?: string + }] +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go b/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go new file mode 100644 index 00000000000..664e44131ab --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go @@ -0,0 +1,18 @@ +package v0alpha1 + +import "k8s.io/apimachinery/pkg/runtime/schema" + +const ( + // APIGroup is the API group used by all kinds in this package + APIGroup = "plugins.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/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go new file mode 100644 index 00000000000..48b7e98fd2f --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PluginInstallJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginInstallJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PluginInstallJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*PluginInstallJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PluginInstallJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go new file mode 100644 index 00000000000..0e944597301 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type PluginInstallMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewPluginInstallMetadata creates a new PluginInstallMetadata object. +func NewPluginInstallMetadata() *PluginInstallMetadata { + return &PluginInstallMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go new file mode 100644 index 00000000000..6ceaa603baa --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type PluginInstall struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the PluginInstall + Spec PluginInstallSpec `json:"spec" yaml:"spec"` + + Status PluginInstallStatus `json:"status" yaml:"status"` +} + +func (o *PluginInstall) GetSpec() any { + return o.Spec +} + +func (o *PluginInstall) SetSpec(spec any) error { + cast, ok := spec.(PluginInstallSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *PluginInstall) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *PluginInstall) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *PluginInstall) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PluginInstallStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PluginInstallStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *PluginInstall) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *PluginInstall) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *PluginInstall) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *PluginInstall) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *PluginInstall) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *PluginInstall) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *PluginInstall) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *PluginInstall) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *PluginInstall) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *PluginInstall) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *PluginInstall) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *PluginInstall) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginInstall) DeepCopy() *PluginInstall { + cpy := &PluginInstall{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginInstall) DeepCopyInto(dst *PluginInstall) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &PluginInstall{} + +// +k8s:openapi-gen=true +type PluginInstallList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []PluginInstall `json:"items" yaml:"items"` +} + +func (o *PluginInstallList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginInstallList) Copy() resource.ListObject { + cpy := &PluginInstallList{ + TypeMeta: o.TypeMeta, + Items: make([]PluginInstall, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*PluginInstall); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PluginInstallList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *PluginInstallList) SetItems(items []resource.Object) { + o.Items = make([]PluginInstall, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*PluginInstall) + } +} + +func (o *PluginInstallList) DeepCopy() *PluginInstallList { + cpy := &PluginInstallList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginInstallList) DeepCopyInto(dst *PluginInstallList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PluginInstallList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PluginInstallSpec) DeepCopy() *PluginInstallSpec { + cpy := &PluginInstallSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PluginInstallSpec) DeepCopyInto(dst *PluginInstallSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PluginInstallStatus +func (s *PluginInstallStatus) DeepCopy() *PluginInstallStatus { + cpy := &PluginInstallStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PluginInstallStatus into another PluginInstallStatus object +func (s *PluginInstallStatus) DeepCopyInto(dst *PluginInstallStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go new file mode 100644 index 00000000000..85ea40cd157 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPluginInstall = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginInstall{}, &PluginInstallList{}, resource.WithKind("PluginInstall"), + resource.WithPlural("plugininstalls"), resource.WithScope(resource.NamespacedScope)) + kindPluginInstall = resource.Kind{ + Schema: schemaPluginInstall, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginInstallJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginInstallKind() resource.Kind { + return kindPluginInstall +} + +// Schema returns a resource.SimpleSchema representation of PluginInstall +func PluginInstallSchema() *resource.SimpleSchema { + return schemaPluginInstall +} + +// Interface compliance checks +var _ resource.Schema = kindPluginInstall diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go new file mode 100644 index 00000000000..c815a69eac2 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go @@ -0,0 +1,14 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginInstallSpec struct { + Id string `json:"id"` + Version string `json:"version"` +} + +// NewPluginInstallSpec creates a new PluginInstallSpec object. +func NewPluginInstallSpec() *PluginInstallSpec { + return &PluginInstallSpec{} +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go new file mode 100644 index 00000000000..77fc07860b0 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginInstallstatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State PluginInstallStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewPluginInstallstatusOperatorState creates a new PluginInstallstatusOperatorState object. +func NewPluginInstallstatusOperatorState() *PluginInstallstatusOperatorState { + return &PluginInstallstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PluginInstallStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]PluginInstallstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPluginInstallStatus creates a new PluginInstallStatus object. +func NewPluginInstallStatus() *PluginInstallStatus { + return &PluginInstallStatus{} +} + +// +k8s:openapi-gen=true +type PluginInstallStatusOperatorStateState string + +const ( + PluginInstallStatusOperatorStateStateSuccess PluginInstallStatusOperatorStateState = "success" + PluginInstallStatusOperatorStateStateInProgress PluginInstallStatusOperatorStateState = "in_progress" + PluginInstallStatusOperatorStateStateFailed PluginInstallStatusOperatorStateState = "failed" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go new file mode 100644 index 00000000000..77fb6f918a2 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PluginMetaJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginMetaJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PluginMetaJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*PluginMetaJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PluginMetaJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go new file mode 100644 index 00000000000..7d3b3c9c6b8 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go @@ -0,0 +1,31 @@ +// 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 PluginMetaMetadata 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"` +} + +// NewPluginMetaMetadata creates a new PluginMetaMetadata object. +func NewPluginMetaMetadata() *PluginMetaMetadata { + return &PluginMetaMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go new file mode 100644 index 00000000000..dac431ebf12 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go @@ -0,0 +1,319 @@ +// +// 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 PluginMeta struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the PluginMeta + Spec PluginMetaSpec `json:"spec" yaml:"spec"` + + Status PluginMetaStatus `json:"status" yaml:"status"` +} + +func (o *PluginMeta) GetSpec() any { + return o.Spec +} + +func (o *PluginMeta) SetSpec(spec any) error { + cast, ok := spec.(PluginMetaSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *PluginMeta) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *PluginMeta) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *PluginMeta) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PluginMetaStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PluginMetaStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *PluginMeta) 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 *PluginMeta) 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 *PluginMeta) 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 *PluginMeta) 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 *PluginMeta) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *PluginMeta) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *PluginMeta) 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 *PluginMeta) 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 *PluginMeta) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *PluginMeta) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *PluginMeta) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *PluginMeta) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMeta) DeepCopy() *PluginMeta { + cpy := &PluginMeta{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &PluginMeta{} + +// +k8s:openapi-gen=true +type PluginMetaList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []PluginMeta `json:"items" yaml:"items"` +} + +func (o *PluginMetaList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMetaList) Copy() resource.ListObject { + cpy := &PluginMetaList{ + TypeMeta: o.TypeMeta, + Items: make([]PluginMeta, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*PluginMeta); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PluginMetaList) 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 *PluginMetaList) SetItems(items []resource.Object) { + o.Items = make([]PluginMeta, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*PluginMeta) + } +} + +func (o *PluginMetaList) DeepCopy() *PluginMetaList { + cpy := &PluginMetaList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMetaList) DeepCopyInto(dst *PluginMetaList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PluginMetaList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PluginMetaSpec) DeepCopy() *PluginMetaSpec { + cpy := &PluginMetaSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PluginMetaSpec) DeepCopyInto(dst *PluginMetaSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PluginMetaStatus +func (s *PluginMetaStatus) DeepCopy() *PluginMetaStatus { + cpy := &PluginMetaStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PluginMetaStatus into another PluginMetaStatus object +func (s *PluginMetaStatus) DeepCopyInto(dst *PluginMetaStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go new file mode 100644 index 00000000000..a4022c8de97 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPluginMeta = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginMeta{}, &PluginMetaList{}, resource.WithKind("PluginMeta"), + resource.WithPlural("pluginmetas"), resource.WithScope(resource.NamespacedScope)) + kindPluginMeta = resource.Kind{ + Schema: schemaPluginMeta, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginMetaJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginMetaKind() resource.Kind { + return kindPluginMeta +} + +// Schema returns a resource.SimpleSchema representation of PluginMeta +func PluginMetaSchema() *resource.SimpleSchema { + return schemaPluginMeta +} + +// Interface compliance checks +var _ resource.Schema = kindPluginMeta diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go new file mode 100644 index 00000000000..8020e34efda --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go @@ -0,0 +1,477 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +// +k8s:openapi-gen=true +type PluginMetaJSONData struct { + // Unique name of the plugin + Id string `json:"id"` + // Plugin type + Type PluginMetaJSONDataType `json:"type"` + // Human-readable name of the plugin + Name string `json:"name"` + // Metadata for the plugin + Info PluginMetaInfo `json:"info"` + // Dependency information + Dependencies PluginMetaDependencies `json:"dependencies"` + // Optional fields + Alerting *bool `json:"alerting,omitempty"` + Annotations *bool `json:"annotations,omitempty"` + AutoEnabled *bool `json:"autoEnabled,omitempty"` + Backend *bool `json:"backend,omitempty"` + BuildMode *string `json:"buildMode,omitempty"` + BuiltIn *bool `json:"builtIn,omitempty"` + Category *PluginMetaJSONDataCategory `json:"category,omitempty"` + EnterpriseFeatures *PluginMetaEnterpriseFeatures `json:"enterpriseFeatures,omitempty"` + Executable *string `json:"executable,omitempty"` + HideFromList *bool `json:"hideFromList,omitempty"` + // +listType=atomic + Includes []PluginMetaInclude `json:"includes,omitempty"` + Logs *bool `json:"logs,omitempty"` + Metrics *bool `json:"metrics,omitempty"` + MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` + PascalName *string `json:"pascalName,omitempty"` + Preload *bool `json:"preload,omitempty"` + QueryOptions *PluginMetaQueryOptions `json:"queryOptions,omitempty"` + // +listType=atomic + Routes []PluginMetaRoute `json:"routes,omitempty"` + SkipDataQuery *bool `json:"skipDataQuery,omitempty"` + State *PluginMetaJSONDataState `json:"state,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + Tracing *bool `json:"tracing,omitempty"` + Iam *PluginMetaIAM `json:"iam,omitempty"` + // +listType=atomic + Roles []PluginMetaRole `json:"roles,omitempty"` + Extensions *PluginMetaExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaJSONData creates a new PluginMetaJSONData object. +func NewPluginMetaJSONData() *PluginMetaJSONData { + return &PluginMetaJSONData{ + Info: *NewPluginMetaInfo(), + Dependencies: *NewPluginMetaDependencies(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInfo struct { + // Required fields + // +listType=set + Keywords []string `json:"keywords"` + Logos PluginMetaV0alpha1InfoLogos `json:"logos"` + Updated time.Time `json:"updated"` + Version string `json:"version"` + // Optional fields + Author *PluginMetaV0alpha1InfoAuthor `json:"author,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Links []PluginMetaV0alpha1InfoLinks `json:"links,omitempty"` + // +listType=atomic + Screenshots []PluginMetaV0alpha1InfoScreenshots `json:"screenshots,omitempty"` +} + +// NewPluginMetaInfo creates a new PluginMetaInfo object. +func NewPluginMetaInfo() *PluginMetaInfo { + return &PluginMetaInfo{ + Keywords: []string{}, + Logos: *NewPluginMetaV0alpha1InfoLogos(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaDependencies struct { + // Required field + GrafanaDependency string `json:"grafanaDependency"` + // Optional fields + GrafanaVersion *string `json:"grafanaVersion,omitempty"` + // +listType=set + // +listMapKey=id + Plugins []PluginMetaV0alpha1DependenciesPlugins `json:"plugins,omitempty"` + Extensions *PluginMetaV0alpha1DependenciesExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaDependencies creates a new PluginMetaDependencies object. +func NewPluginMetaDependencies() *PluginMetaDependencies { + return &PluginMetaDependencies{} +} + +// +k8s:openapi-gen=true +type PluginMetaEnterpriseFeatures struct { + // Allow additional properties + HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` +} + +// NewPluginMetaEnterpriseFeatures creates a new PluginMetaEnterpriseFeatures object. +func NewPluginMetaEnterpriseFeatures() *PluginMetaEnterpriseFeatures { + return &PluginMetaEnterpriseFeatures{ + HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInclude struct { + Uid *string `json:"uid,omitempty"` + Type *PluginMetaIncludeType `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Component *string `json:"component,omitempty"` + Role *PluginMetaIncludeRole `json:"role,omitempty"` + Action *string `json:"action,omitempty"` + Path *string `json:"path,omitempty"` + AddToNav *bool `json:"addToNav,omitempty"` + DefaultNav *bool `json:"defaultNav,omitempty"` + Icon *string `json:"icon,omitempty"` +} + +// NewPluginMetaInclude creates a new PluginMetaInclude object. +func NewPluginMetaInclude() *PluginMetaInclude { + return &PluginMetaInclude{} +} + +// +k8s:openapi-gen=true +type PluginMetaQueryOptions struct { + MaxDataPoints *bool `json:"maxDataPoints,omitempty"` + MinInterval *bool `json:"minInterval,omitempty"` + CacheTimeout *bool `json:"cacheTimeout,omitempty"` +} + +// NewPluginMetaQueryOptions creates a new PluginMetaQueryOptions object. +func NewPluginMetaQueryOptions() *PluginMetaQueryOptions { + return &PluginMetaQueryOptions{} +} + +// +k8s:openapi-gen=true +type PluginMetaRoute struct { + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Url *string `json:"url,omitempty"` + ReqSignedIn *bool `json:"reqSignedIn,omitempty"` + ReqRole *string `json:"reqRole,omitempty"` + ReqAction *string `json:"reqAction,omitempty"` + // +listType=atomic + Headers []string `json:"headers,omitempty"` + Body map[string]interface{} `json:"body,omitempty"` + TokenAuth *PluginMetaV0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` + JwtTokenAuth *PluginMetaV0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` + // +listType=atomic + UrlParams []PluginMetaV0alpha1RouteUrlParams `json:"urlParams,omitempty"` +} + +// NewPluginMetaRoute creates a new PluginMetaRoute object. +func NewPluginMetaRoute() *PluginMetaRoute { + return &PluginMetaRoute{} +} + +// +k8s:openapi-gen=true +type PluginMetaIAM struct { + // +listType=atomic + Permissions []PluginMetaV0alpha1IAMPermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaIAM creates a new PluginMetaIAM object. +func NewPluginMetaIAM() *PluginMetaIAM { + return &PluginMetaIAM{} +} + +// +k8s:openapi-gen=true +type PluginMetaRole struct { + Role *PluginMetaV0alpha1RoleRole `json:"role,omitempty"` + // +listType=set + Grants []string `json:"grants,omitempty"` +} + +// NewPluginMetaRole creates a new PluginMetaRole object. +func NewPluginMetaRole() *PluginMetaRole { + return &PluginMetaRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaExtensions struct { + // +listType=atomic + AddedComponents []PluginMetaV0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` + // +listType=atomic + AddedLinks []PluginMetaV0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` + // +listType=set + // +listMapKey=id + ExposedComponents []PluginMetaV0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` + // +listType=set + // +listMapKey=id + ExtensionPoints []PluginMetaV0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` +} + +// NewPluginMetaExtensions creates a new PluginMetaExtensions object. +func NewPluginMetaExtensions() *PluginMetaExtensions { + return &PluginMetaExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaSpec struct { + PluginJSON PluginMetaJSONData `json:"pluginJSON"` +} + +// NewPluginMetaSpec creates a new PluginMetaSpec object. +func NewPluginMetaSpec() *PluginMetaSpec { + return &PluginMetaSpec{ + PluginJSON: *NewPluginMetaJSONData(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +// NewPluginMetaV0alpha1InfoLogos creates a new PluginMetaV0alpha1InfoLogos object. +func NewPluginMetaV0alpha1InfoLogos() *PluginMetaV0alpha1InfoLogos { + return &PluginMetaV0alpha1InfoLogos{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoAuthor struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoAuthor creates a new PluginMetaV0alpha1InfoAuthor object. +func NewPluginMetaV0alpha1InfoAuthor() *PluginMetaV0alpha1InfoAuthor { + return &PluginMetaV0alpha1InfoAuthor{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLinks struct { + Name *string `json:"name,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoLinks creates a new PluginMetaV0alpha1InfoLinks object. +func NewPluginMetaV0alpha1InfoLinks() *PluginMetaV0alpha1InfoLinks { + return &PluginMetaV0alpha1InfoLinks{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoScreenshots struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` +} + +// NewPluginMetaV0alpha1InfoScreenshots creates a new PluginMetaV0alpha1InfoScreenshots object. +func NewPluginMetaV0alpha1InfoScreenshots() *PluginMetaV0alpha1InfoScreenshots { + return &PluginMetaV0alpha1InfoScreenshots{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPlugins struct { + Id string `json:"id"` + Type PluginMetaV0alpha1DependenciesPluginsType `json:"type"` + Name string `json:"name"` +} + +// NewPluginMetaV0alpha1DependenciesPlugins creates a new PluginMetaV0alpha1DependenciesPlugins object. +func NewPluginMetaV0alpha1DependenciesPlugins() *PluginMetaV0alpha1DependenciesPlugins { + return &PluginMetaV0alpha1DependenciesPlugins{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesExtensions struct { + // +listType=set + ExposedComponents []string `json:"exposedComponents,omitempty"` +} + +// NewPluginMetaV0alpha1DependenciesExtensions creates a new PluginMetaV0alpha1DependenciesExtensions object. +func NewPluginMetaV0alpha1DependenciesExtensions() *PluginMetaV0alpha1DependenciesExtensions { + return &PluginMetaV0alpha1DependenciesExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteTokenAuth creates a new PluginMetaV0alpha1RouteTokenAuth object. +func NewPluginMetaV0alpha1RouteTokenAuth() *PluginMetaV0alpha1RouteTokenAuth { + return &PluginMetaV0alpha1RouteTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteJwtTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteJwtTokenAuth creates a new PluginMetaV0alpha1RouteJwtTokenAuth object. +func NewPluginMetaV0alpha1RouteJwtTokenAuth() *PluginMetaV0alpha1RouteJwtTokenAuth { + return &PluginMetaV0alpha1RouteJwtTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteUrlParams struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` +} + +// NewPluginMetaV0alpha1RouteUrlParams creates a new PluginMetaV0alpha1RouteUrlParams object. +func NewPluginMetaV0alpha1RouteUrlParams() *PluginMetaV0alpha1RouteUrlParams { + return &PluginMetaV0alpha1RouteUrlParams{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1IAMPermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1IAMPermissions creates a new PluginMetaV0alpha1IAMPermissions object. +func NewPluginMetaV0alpha1IAMPermissions() *PluginMetaV0alpha1IAMPermissions { + return &PluginMetaV0alpha1IAMPermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRolePermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRolePermissions creates a new PluginMetaV0alpha1RoleRolePermissions object. +func NewPluginMetaV0alpha1RoleRolePermissions() *PluginMetaV0alpha1RoleRolePermissions { + return &PluginMetaV0alpha1RoleRolePermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRole struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Permissions []PluginMetaV0alpha1RoleRolePermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRole creates a new PluginMetaV0alpha1RoleRole object. +func NewPluginMetaV0alpha1RoleRole() *PluginMetaV0alpha1RoleRole { + return &PluginMetaV0alpha1RoleRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedComponents struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedComponents creates a new PluginMetaV0alpha1ExtensionsAddedComponents object. +func NewPluginMetaV0alpha1ExtensionsAddedComponents() *PluginMetaV0alpha1ExtensionsAddedComponents { + return &PluginMetaV0alpha1ExtensionsAddedComponents{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedLinks struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedLinks creates a new PluginMetaV0alpha1ExtensionsAddedLinks object. +func NewPluginMetaV0alpha1ExtensionsAddedLinks() *PluginMetaV0alpha1ExtensionsAddedLinks { + return &PluginMetaV0alpha1ExtensionsAddedLinks{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExposedComponents struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExposedComponents creates a new PluginMetaV0alpha1ExtensionsExposedComponents object. +func NewPluginMetaV0alpha1ExtensionsExposedComponents() *PluginMetaV0alpha1ExtensionsExposedComponents { + return &PluginMetaV0alpha1ExtensionsExposedComponents{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExtensionPoints struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExtensionPoints creates a new PluginMetaV0alpha1ExtensionsExtensionPoints object. +func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1ExtensionsExtensionPoints { + return &PluginMetaV0alpha1ExtensionsExtensionPoints{} +} + +// +k8s:openapi-gen=true +type PluginMetaJSONDataType string + +const ( + PluginMetaJSONDataTypeApp PluginMetaJSONDataType = "app" + PluginMetaJSONDataTypeDatasource PluginMetaJSONDataType = "datasource" + PluginMetaJSONDataTypePanel PluginMetaJSONDataType = "panel" + PluginMetaJSONDataTypeRenderer PluginMetaJSONDataType = "renderer" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataCategory string + +const ( + PluginMetaJSONDataCategoryTsdb PluginMetaJSONDataCategory = "tsdb" + PluginMetaJSONDataCategoryLogging PluginMetaJSONDataCategory = "logging" + PluginMetaJSONDataCategoryCloud PluginMetaJSONDataCategory = "cloud" + PluginMetaJSONDataCategoryTracing PluginMetaJSONDataCategory = "tracing" + PluginMetaJSONDataCategoryProfiling PluginMetaJSONDataCategory = "profiling" + PluginMetaJSONDataCategorySql PluginMetaJSONDataCategory = "sql" + PluginMetaJSONDataCategoryEnterprise PluginMetaJSONDataCategory = "enterprise" + PluginMetaJSONDataCategoryIot PluginMetaJSONDataCategory = "iot" + PluginMetaJSONDataCategoryOther PluginMetaJSONDataCategory = "other" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataState string + +const ( + PluginMetaJSONDataStateAlpha PluginMetaJSONDataState = "alpha" + PluginMetaJSONDataStateBeta PluginMetaJSONDataState = "beta" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeType string + +const ( + PluginMetaIncludeTypeDashboard PluginMetaIncludeType = "dashboard" + PluginMetaIncludeTypePage PluginMetaIncludeType = "page" + PluginMetaIncludeTypePanel PluginMetaIncludeType = "panel" + PluginMetaIncludeTypeDatasource PluginMetaIncludeType = "datasource" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeRole string + +const ( + PluginMetaIncludeRoleAdmin PluginMetaIncludeRole = "Admin" + PluginMetaIncludeRoleEditor PluginMetaIncludeRole = "Editor" + PluginMetaIncludeRoleViewer PluginMetaIncludeRole = "Viewer" +) + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPluginsType string + +const ( + PluginMetaV0alpha1DependenciesPluginsTypeApp PluginMetaV0alpha1DependenciesPluginsType = "app" + PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource" + PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go new file mode 100644 index 00000000000..60fa37dbb32 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginMetastatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State PluginMetaStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewPluginMetastatusOperatorState creates a new PluginMetastatusOperatorState object. +func NewPluginMetastatusOperatorState() *PluginMetastatusOperatorState { + return &PluginMetastatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]PluginMetastatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPluginMetaStatus creates a new PluginMetaStatus object. +func NewPluginMetaStatus() *PluginMetaStatus { + return &PluginMetaStatus{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatusOperatorStateState string + +const ( + PluginMetaStatusOperatorStateStateSuccess PluginMetaStatusOperatorStateState = "success" + PluginMetaStatusOperatorStateStateInProgress PluginMetaStatusOperatorStateState = "in_progress" + PluginMetaStatusOperatorStateStateFailed PluginMetaStatusOperatorStateState = "failed" +) diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go new file mode 100644 index 00000000000..2449dc9a726 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -0,0 +1,87 @@ +// +// 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" + + v0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +var ( + rawSchemaPluginMetav0alpha1 = []byte(`{"spec":{"properties":{"pluginJSON":{"properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"description":"Dependency information","properties":{"extensions":{"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"enterpriseFeatures":{"additionalProperties":{"description":"Allow additional properties"},"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"executable":{"type":"string"},"extensions":{"properties":{"addedComponents":{"description":"+listType=atomic","items":{"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"hideFromList":{"type":"boolean"},"iam":{"properties":{"permissions":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"type":"array"},"info":{"description":"Metadata for the plugin","properties":{"author":{"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"format":"date-time","type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"roles":{"description":"+listType=atomic","items":{"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"properties":{"body":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"properties":{"params":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"properties":{"params":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"}},"required":["pluginJSON"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"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","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginMetav0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginMetav0alpha1, &versionSchemaPluginMetav0alpha1) + rawSchemaPluginInstallv0alpha1 = []byte(`{"spec":{"properties":{"id":{"type":"string"},"version":{"type":"string"}},"required":["id","version"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"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","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginInstallv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginInstallv0alpha1, &versionSchemaPluginInstallv0alpha1) +) + +var appManifestData = app.ManifestData{ + AppName: "plugins", + Group: "plugins.grafana.app", + Versions: []app.ManifestVersion{ + { + Name: "v0alpha1", + Served: false, + Kinds: []app.ManifestVersionKind{ + { + Kind: "PluginMeta", + Plural: "PluginMetas", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPluginMetav0alpha1, + }, + + { + Kind: "PluginInstall", + Plural: "PluginInstalls", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPluginInstallv0alpha1, + }, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("plugins") +} + +var kindVersionToGoType = map[string]resource.Kind{ + "PluginMeta/v0alpha1": v0alpha1.PluginMetaKind(), + "PluginInstall/v0alpha1": v0alpha1.PluginInstallKind(), +} + +// 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. +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 +} diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go new file mode 100644 index 00000000000..99fc20a8d2c --- /dev/null +++ b/apps/plugins/pkg/app/app.go @@ -0,0 +1,65 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana-app-sdk/simple" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" + + pluginsapi "github.com/grafana/grafana/apps/plugins/pkg/apis" +) + +func New(cfg app.Config) (app.App, error) { + managedKinds := []simple.AppManagedKind{} + for _, kinds := range GetKinds() { + for _, k := range kinds { + managedKinds = append(managedKinds, simple.AppManagedKind{ + Kind: k, + }) + } + } + + simpleConfig := simple.AppConfig{ + Name: "plugins", + KubeConfig: cfg.KubeConfig, + InformerConfig: simple.AppInformerConfig{ + ErrorHandler: func(ctx context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, + }, + ManagedKinds: managedKinds, + } + + 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 { + kinds := make(map[schema.GroupVersion][]resource.Kind) + manifest := pluginsapi.LocalManifest() + for _, v := range manifest.ManifestData.Versions { + gv := schema.GroupVersion{ + Group: manifest.ManifestData.Group, + Version: v.Name, + } + for _, k := range v.Kinds { + kind, ok := pluginsapi.ManifestGoTypeAssociator(k.Kind, v.Name) + if ok { + kinds[gv] = append(kinds[gv], kind) + } + } + } + return kinds +} diff --git a/apps/plugins/pkg/app/authorizer.go b/apps/plugins/pkg/app/authorizer.go new file mode 100644 index 00000000000..b6a800694f7 --- /dev/null +++ b/apps/plugins/pkg/app/authorizer.go @@ -0,0 +1,32 @@ +package app + +import ( + "context" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +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 + } + + // require a user + u, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "valid user is required", err + } + + // check if is admin + if u.HasRole(identity.RoleAdmin) { + return authorizer.DecisionAllow, "", nil + } + + return authorizer.DecisionDeny, "forbidden", nil + }) +} diff --git a/apps/plugins/pkg/app/storage.go b/apps/plugins/pkg/app/storage.go new file mode 100644 index 00000000000..9fca277abfc --- /dev/null +++ b/apps/plugins/pkg/app/storage.go @@ -0,0 +1,75 @@ +package app + +import ( + "context" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + + claims "github.com/grafana/authlib/types" + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +var ( + _ rest.Scoper = (*PluginMetaStorage)(nil) + _ rest.SingularNameProvider = (*PluginMetaStorage)(nil) + _ rest.Getter = (*PluginMetaStorage)(nil) + _ rest.Lister = (*PluginMetaStorage)(nil) + _ rest.Storage = (*PluginMetaStorage)(nil) + _ rest.TableConvertor = (*PluginMetaStorage)(nil) +) + +type PluginMetaStorage struct { + gr schema.GroupResource + namespacer claims.NamespaceFormatter + tableConverter rest.TableConvertor +} + +func NewPluginMetaStorage( + namespacer claims.NamespaceFormatter, +) *PluginMetaStorage { + gr := schema.GroupResource{ + Group: pluginsv0alpha1.PluginMetaKind().Group(), + Resource: strings.ToLower(pluginsv0alpha1.PluginMetaKind().Plural()), + } + return &PluginMetaStorage{ + gr: gr, + namespacer: namespacer, + tableConverter: rest.NewDefaultTableConvertor(gr), + } +} + +func (s *PluginMetaStorage) New() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroValue() +} + +func (s *PluginMetaStorage) Destroy() {} + +func (s *PluginMetaStorage) NamespaceScoped() bool { + return true +} + +func (s *PluginMetaStorage) GetSingularName() string { + return strings.ToLower(pluginsv0alpha1.PluginMetaKind().Kind()) +} + +func (s *PluginMetaStorage) NewList() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroListValue() +} + +func (s *PluginMetaStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +} + +func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + return s.NewList(), nil +} + +func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return nil, apierrors.NewNotFound(s.gr, name) +} diff --git a/apps/preferences/Makefile b/apps/preferences/Makefile new file mode 100644 index 00000000000..230bfd4149a --- /dev/null +++ b/apps/preferences/Makefile @@ -0,0 +1,9 @@ +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 \ + --defencoding=none \ No newline at end of file diff --git a/apps/preferences/go.mod b/apps/preferences/go.mod new file mode 100644 index 00000000000..1fc0f8a0c2f --- /dev/null +++ b/apps/preferences/go.mod @@ -0,0 +1,3 @@ +module github.com/grafana/grafana/apps/preferences + +go 1.24.5 diff --git a/apps/preferences/go.sum b/apps/preferences/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/preferences/kinds/cue.mod/module.cue b/apps/preferences/kinds/cue.mod/module.cue new file mode 100644 index 00000000000..6280fd8f227 --- /dev/null +++ b/apps/preferences/kinds/cue.mod/module.cue @@ -0,0 +1,4 @@ +module: "github.com/grafana/grafana/apps/preferences/kinds" +language: { + version: "v0.9.0" +} diff --git a/apps/preferences/kinds/manifest.cue b/apps/preferences/kinds/manifest.cue new file mode 100644 index 00000000000..3d7044dc1e8 --- /dev/null +++ b/apps/preferences/kinds/manifest.cue @@ -0,0 +1,17 @@ +package preferences + +manifest: { + appName: "preferences" + groupOverride: "preferences.grafana.app" + versions: { + "v1alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + preferencesv1alpha1, + ] + } + } +} \ No newline at end of file diff --git a/kinds/preferences/preferences_kind.cue b/apps/preferences/kinds/preferences.cue similarity index 78% rename from kinds/preferences/preferences_kind.cue rename to apps/preferences/kinds/preferences.cue index 1cbd78185db..579b8150642 100644 --- a/kinds/preferences/preferences_kind.cue +++ b/apps/preferences/kinds/preferences.cue @@ -1,15 +1,11 @@ -package kind +package preferences -name: "Preferences" -pluralName: "Preferences" -maturity: "merged" -description: "The user or team frontend preferences" +preferencesv1alpha1: { + kind: "Preferences" + pluralName: "Preferences" + scope: "Namespaced" -lineage: schemas: [{ - version: [0, 0] schema: { - // Spec defines user, team or org Grafana preferences - // swagger:model Preferences spec: { // UID for the home dashboard homeDashboardUID?: string @@ -55,4 +51,4 @@ lineage: schemas: [{ bookmarkUrls: [...string] } @cuetsy(kind="interface") } -}] +} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/constants.go b/apps/preferences/pkg/apis/preferences/v1alpha1/constants.go new file mode 100644 index 00000000000..e32cf591d54 --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/constants.go @@ -0,0 +1,18 @@ +package v1alpha1 + +import "k8s.io/apimachinery/pkg/runtime/schema" + +const ( + // APIGroup is the API group used by all kinds in this package + APIGroup = "preferences.grafana.app" + // APIVersion is the API version used by all kinds in this package + APIVersion = "v1alpha1" +) + +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/preferences/pkg/apis/preferences/v1alpha1/preferences_codec_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_codec_gen.go new file mode 100644 index 00000000000..b2b7c6ec23b --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v1alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PreferencesJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PreferencesJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PreferencesJSONCodec) 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 (*PreferencesJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PreferencesJSONCodec{} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_metadata_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_metadata_gen.go new file mode 100644 index 00000000000..1e630081901 --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v1alpha1 + +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 PreferencesMetadata 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"` +} + +// NewPreferencesMetadata creates a new PreferencesMetadata object. +func NewPreferencesMetadata() *PreferencesMetadata { + return &PreferencesMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go new file mode 100644 index 00000000000..9f1857a5f7b --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v1alpha1 + +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 Preferences struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the Preferences + Spec PreferencesSpec `json:"spec" yaml:"spec"` + + Status PreferencesStatus `json:"status" yaml:"status"` +} + +func (o *Preferences) GetSpec() any { + return o.Spec +} + +func (o *Preferences) SetSpec(spec any) error { + cast, ok := spec.(PreferencesSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *Preferences) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *Preferences) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *Preferences) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PreferencesStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PreferencesStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *Preferences) 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 *Preferences) 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 *Preferences) 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 *Preferences) 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 *Preferences) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *Preferences) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *Preferences) 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 *Preferences) 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 *Preferences) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *Preferences) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *Preferences) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *Preferences) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *Preferences) DeepCopy() *Preferences { + cpy := &Preferences{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *Preferences) DeepCopyInto(dst *Preferences) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &Preferences{} + +// +k8s:openapi-gen=true +type PreferencesList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []Preferences `json:"items" yaml:"items"` +} + +func (o *PreferencesList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PreferencesList) Copy() resource.ListObject { + cpy := &PreferencesList{ + TypeMeta: o.TypeMeta, + Items: make([]Preferences, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*Preferences); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PreferencesList) 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 *PreferencesList) SetItems(items []resource.Object) { + o.Items = make([]Preferences, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*Preferences) + } +} + +func (o *PreferencesList) DeepCopy() *PreferencesList { + cpy := &PreferencesList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PreferencesList) DeepCopyInto(dst *PreferencesList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PreferencesList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PreferencesSpec) DeepCopy() *PreferencesSpec { + cpy := &PreferencesSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PreferencesSpec) DeepCopyInto(dst *PreferencesSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PreferencesStatus +func (s *PreferencesStatus) DeepCopy() *PreferencesStatus { + cpy := &PreferencesStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PreferencesStatus into another PreferencesStatus object +func (s *PreferencesStatus) DeepCopyInto(dst *PreferencesStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_schema_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_schema_gen.go new file mode 100644 index 00000000000..6fbdd36d766 --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v1alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPreferences = resource.NewSimpleSchema("preferences.grafana.app", "v1alpha1", &Preferences{}, &PreferencesList{}, resource.WithKind("Preferences"), + resource.WithPlural("preferences"), resource.WithScope(resource.NamespacedScope)) + kindPreferences = resource.Kind{ + Schema: schemaPreferences, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PreferencesJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PreferencesKind() resource.Kind { + return kindPreferences +} + +// Schema returns a resource.SimpleSchema representation of Preferences +func PreferencesSchema() *resource.SimpleSchema { + return schemaPreferences +} + +// Interface compliance checks +var _ resource.Schema = kindPreferences diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_spec_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_spec_gen.go new file mode 100644 index 00000000000..0a1c8356858 --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_spec_gen.go @@ -0,0 +1,66 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v1alpha1 + +// +k8s:openapi-gen=true +type PreferencesQueryHistoryPreference struct { + // one of: '' | 'query' | 'starred'; + HomeTab *string `json:"homeTab,omitempty"` +} + +// NewPreferencesQueryHistoryPreference creates a new PreferencesQueryHistoryPreference object. +func NewPreferencesQueryHistoryPreference() *PreferencesQueryHistoryPreference { + return &PreferencesQueryHistoryPreference{} +} + +// +k8s:openapi-gen=true +type PreferencesCookiePreferences struct { + Analytics interface{} `json:"analytics,omitempty"` + Performance interface{} `json:"performance,omitempty"` + Functional interface{} `json:"functional,omitempty"` +} + +// NewPreferencesCookiePreferences creates a new PreferencesCookiePreferences object. +func NewPreferencesCookiePreferences() *PreferencesCookiePreferences { + return &PreferencesCookiePreferences{} +} + +// +k8s:openapi-gen=true +type PreferencesNavbarPreference struct { + BookmarkUrls []string `json:"bookmarkUrls"` +} + +// NewPreferencesNavbarPreference creates a new PreferencesNavbarPreference object. +func NewPreferencesNavbarPreference() *PreferencesNavbarPreference { + return &PreferencesNavbarPreference{ + BookmarkUrls: []string{}, + } +} + +// +k8s:openapi-gen=true +type PreferencesSpec struct { + // UID for the home dashboard + HomeDashboardUID *string `json:"homeDashboardUID,omitempty"` + // The timezone selection + // TODO: this should use the timezone defined in common + Timezone *string `json:"timezone,omitempty"` + // day of the week (sunday, monday, etc) + WeekStart *string `json:"weekStart,omitempty"` + // light, dark, empty is default + Theme *string `json:"theme,omitempty"` + // Selected language (beta) + Language *string `json:"language,omitempty"` + // Selected locale (beta) + RegionalFormat *string `json:"regionalFormat,omitempty"` + // Explore query history preferences + QueryHistory *PreferencesQueryHistoryPreference `json:"queryHistory,omitempty"` + // Cookie preferences + CookiePreferences *PreferencesCookiePreferences `json:"cookiePreferences,omitempty"` + // Navigation preferences + Navbar *PreferencesNavbarPreference `json:"navbar,omitempty"` +} + +// NewPreferencesSpec creates a new PreferencesSpec object. +func NewPreferencesSpec() *PreferencesSpec { + return &PreferencesSpec{} +} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go new file mode 100644 index 00000000000..420dd054d3d --- /dev/null +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v1alpha1 + +// +k8s:openapi-gen=true +type PreferencesstatusOperatorState 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 PreferencesStatusOperatorStateState `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"` +} + +// NewPreferencesstatusOperatorState creates a new PreferencesstatusOperatorState object. +func NewPreferencesstatusOperatorState() *PreferencesstatusOperatorState { + return &PreferencesstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PreferencesStatus 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]PreferencesstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPreferencesStatus creates a new PreferencesStatus object. +func NewPreferencesStatus() *PreferencesStatus { + return &PreferencesStatus{} +} + +// +k8s:openapi-gen=true +type PreferencesStatusOperatorStateState string + +const ( + PreferencesStatusOperatorStateStateSuccess PreferencesStatusOperatorStateState = "success" + PreferencesStatusOperatorStateStateInProgress PreferencesStatusOperatorStateState = "in_progress" + PreferencesStatusOperatorStateStateFailed PreferencesStatusOperatorStateState = "failed" +) diff --git a/apps/preferences/pkg/apis/preferences_manifest.go b/apps/preferences/pkg/apis/preferences_manifest.go new file mode 100644 index 00000000000..0b42a0e0713 --- /dev/null +++ b/apps/preferences/pkg/apis/preferences_manifest.go @@ -0,0 +1,75 @@ +// +// 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" + + v1alpha1 "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1" +) + +var ( + rawSchemaPreferencesv1alpha1 = []byte(`{"spec":{"properties":{"cookiePreferences":{"description":"Cookie preferences","properties":{"analytics":{"type":"object"},"functional":{"type":"object"},"performance":{"type":"object"}},"type":"object"},"homeDashboardUID":{"description":"UID for the home dashboard","type":"string"},"language":{"description":"Selected language (beta)","type":"string"},"navbar":{"description":"Navigation preferences","properties":{"bookmarkUrls":{"items":{"type":"string"},"type":"array"}},"required":["bookmarkUrls"],"type":"object"},"queryHistory":{"description":"Explore query history preferences","properties":{"homeTab":{"description":"one of: '' | 'query' | 'starred';","type":"string"}},"type":"object"},"regionalFormat":{"description":"Selected locale (beta)","type":"string"},"theme":{"description":"light, dark, empty is default","type":"string"},"timezone":{"description":"The timezone selection\nTODO: this should use the timezone defined in common","type":"string"},"weekStart":{"description":"day of the week (sunday, monday, etc)","type":"string"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"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","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPreferencesv1alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPreferencesv1alpha1, &versionSchemaPreferencesv1alpha1) +) + +var appManifestData = app.ManifestData{ + AppName: "preferences", + Group: "preferences.grafana.app", + Versions: []app.ManifestVersion{ + { + Name: "v1alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ + { + Kind: "Preferences", + Plural: "Preferences", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPreferencesv1alpha1, + }, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("preferences") +} + +var kindVersionToGoType = map[string]resource.Kind{ + "Preferences/v1alpha1": v1alpha1.PreferencesKind(), +} + +// 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. +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 +} diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index 80a98f86697..e568b4c36c4 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/provisioning go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 k8s.io/apimachinery v0.33.3 k8s.io/client-go v0.33.3 @@ -28,7 +28,7 @@ require ( 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.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // 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 @@ -45,7 +45,7 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect @@ -72,7 +72,3 @@ require ( sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect ) - -// transitive dependencies that need replaced -// TODO: stop depending on grafana core -replace github.com/grafana/grafana => ../.. diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index 79da45ea597..60de9834b49 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -36,10 +36,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -87,8 +87,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go index 4379d2365c1..e11ab36d147 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go @@ -4,6 +4,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// When this code is changed, make sure to update the code generation. +// As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning +// If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors. +// +genclient + // The repository name and type are stored as labels // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Job struct { @@ -230,20 +235,3 @@ type JobResourceSummary struct { // This may not be an exhaustive list and recommend looking at the logs for more info Errors []string `json:"errors,omitempty"` } - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type WebhookResponse struct { - metav1.TypeMeta `json:",inline"` - - // HTTP Status code - // 200 implies that the payload was understood but nothing is required - // 202 implies that an async job has been scheduled to handle the request - Code int `json:"code,omitempty"` - - // Optional message - Message string `json:"added,omitempty"` - - // Jobs to be processed - // When the response is 202 (Accepted) the queued jobs will be returned - Job *JobSpec `json:"job,omitempty"` -} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/webhook.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/webhook.go new file mode 100644 index 00000000000..d81b49e69cc --- /dev/null +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/webhook.go @@ -0,0 +1,20 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type WebhookResponse struct { + metav1.TypeMeta `json:",inline"` + + // HTTP Status code + // 200 implies that the payload was understood but nothing is required + // 202 implies that an async job has been scheduled to handle the request + Code int `json:"code,omitempty"` + + // Optional message + Message string `json:"added,omitempty"` + + // Jobs to be processed + // When the response is 202 (Accepted) the queued jobs will be returned + Job *JobSpec `json:"job,omitempty"` +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/deletejoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/deletejoboptions.go new file mode 100644 index 00000000000..22c0e35da96 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/deletejoboptions.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// DeleteJobOptionsApplyConfiguration represents a declarative configuration of the DeleteJobOptions type for use +// with apply. +type DeleteJobOptionsApplyConfiguration struct { + Ref *string `json:"ref,omitempty"` + Paths []string `json:"paths,omitempty"` + Resources []ResourceRefApplyConfiguration `json:"resources,omitempty"` +} + +// DeleteJobOptionsApplyConfiguration constructs a declarative configuration of the DeleteJobOptions type for use with +// apply. +func DeleteJobOptions() *DeleteJobOptionsApplyConfiguration { + return &DeleteJobOptionsApplyConfiguration{} +} + +// WithRef sets the Ref field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ref field is set to the value of the last call. +func (b *DeleteJobOptionsApplyConfiguration) WithRef(value string) *DeleteJobOptionsApplyConfiguration { + b.Ref = &value + return b +} + +// WithPaths adds the given value to the Paths field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Paths field. +func (b *DeleteJobOptionsApplyConfiguration) WithPaths(values ...string) *DeleteJobOptionsApplyConfiguration { + for i := range values { + b.Paths = append(b.Paths, values[i]) + } + return b +} + +// WithResources adds the given value to the Resources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Resources field. +func (b *DeleteJobOptionsApplyConfiguration) WithResources(values ...*ResourceRefApplyConfiguration) *DeleteJobOptionsApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithResources") + } + b.Resources = append(b.Resources, *values[i]) + } + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go new file mode 100644 index 00000000000..f1d543cdd90 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// ExportJobOptionsApplyConfiguration represents a declarative configuration of the ExportJobOptions type for use +// with apply. +type ExportJobOptionsApplyConfiguration struct { + Message *string `json:"message,omitempty"` + Folder *string `json:"folder,omitempty"` + Branch *string `json:"branch,omitempty"` + Path *string `json:"path,omitempty"` +} + +// ExportJobOptionsApplyConfiguration constructs a declarative configuration of the ExportJobOptions type for use with +// apply. +func ExportJobOptions() *ExportJobOptionsApplyConfiguration { + return &ExportJobOptionsApplyConfiguration{} +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *ExportJobOptionsApplyConfiguration) WithMessage(value string) *ExportJobOptionsApplyConfiguration { + b.Message = &value + return b +} + +// WithFolder sets the Folder field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Folder field is set to the value of the last call. +func (b *ExportJobOptionsApplyConfiguration) WithFolder(value string) *ExportJobOptionsApplyConfiguration { + b.Folder = &value + return b +} + +// WithBranch sets the Branch field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Branch field is set to the value of the last call. +func (b *ExportJobOptionsApplyConfiguration) WithBranch(value string) *ExportJobOptionsApplyConfiguration { + b.Branch = &value + return b +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *ExportJobOptionsApplyConfiguration) WithPath(value string) *ExportJobOptionsApplyConfiguration { + b.Path = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/job.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/job.go new file mode 100644 index 00000000000..5fdc2cf345d --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/job.go @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// JobApplyConfiguration represents a declarative configuration of the Job type for use +// with apply. +type JobApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *JobSpecApplyConfiguration `json:"spec,omitempty"` + Status *JobStatusApplyConfiguration `json:"status,omitempty"` +} + +// Job constructs a declarative configuration of the Job type for use with +// apply. +func Job(name, namespace string) *JobApplyConfiguration { + b := &JobApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Job") + b.WithAPIVersion("provisioning.grafana.app/v0alpha1") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *JobApplyConfiguration) WithKind(value string) *JobApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *JobApplyConfiguration) WithAPIVersion(value string) *JobApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *JobApplyConfiguration) WithName(value string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *JobApplyConfiguration) WithGenerateName(value string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *JobApplyConfiguration) WithNamespace(value string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *JobApplyConfiguration) WithUID(value types.UID) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *JobApplyConfiguration) WithResourceVersion(value string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *JobApplyConfiguration) WithGeneration(value int64) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *JobApplyConfiguration) WithCreationTimestamp(value metav1.Time) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *JobApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *JobApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *JobApplyConfiguration) WithLabels(entries map[string]string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *JobApplyConfiguration) WithAnnotations(entries map[string]string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *JobApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *JobApplyConfiguration) WithFinalizers(values ...string) *JobApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *JobApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *JobApplyConfiguration) WithSpec(value *JobSpecApplyConfiguration) *JobApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *JobApplyConfiguration) WithStatus(value *JobStatusApplyConfiguration) *JobApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *JobApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go new file mode 100644 index 00000000000..75b69663794 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// JobResourceSummaryApplyConfiguration represents a declarative configuration of the JobResourceSummary type for use +// with apply. +type JobResourceSummaryApplyConfiguration struct { + Group *string `json:"group,omitempty"` + Resource *string `json:"resource,omitempty"` + Total *int64 `json:"total,omitempty"` + Create *int64 `json:"create,omitempty"` + Update *int64 `json:"update,omitempty"` + Delete *int64 `json:"delete,omitempty"` + Write *int64 `json:"write,omitempty"` + Error *int64 `json:"error,omitempty"` + Noop *int64 `json:"noop,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +// JobResourceSummaryApplyConfiguration constructs a declarative configuration of the JobResourceSummary type for use with +// apply. +func JobResourceSummary() *JobResourceSummaryApplyConfiguration { + return &JobResourceSummaryApplyConfiguration{} +} + +// WithGroup sets the Group field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Group field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithGroup(value string) *JobResourceSummaryApplyConfiguration { + b.Group = &value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithResource(value string) *JobResourceSummaryApplyConfiguration { + b.Resource = &value + return b +} + +// WithTotal sets the Total field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Total field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithTotal(value int64) *JobResourceSummaryApplyConfiguration { + b.Total = &value + return b +} + +// WithCreate sets the Create field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Create field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithCreate(value int64) *JobResourceSummaryApplyConfiguration { + b.Create = &value + return b +} + +// WithUpdate sets the Update field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Update field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithUpdate(value int64) *JobResourceSummaryApplyConfiguration { + b.Update = &value + return b +} + +// WithDelete sets the Delete field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Delete field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithDelete(value int64) *JobResourceSummaryApplyConfiguration { + b.Delete = &value + return b +} + +// WithWrite sets the Write field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Write field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithWrite(value int64) *JobResourceSummaryApplyConfiguration { + b.Write = &value + return b +} + +// WithError sets the Error field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Error field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithError(value int64) *JobResourceSummaryApplyConfiguration { + b.Error = &value + return b +} + +// WithNoop sets the Noop field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Noop field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithNoop(value int64) *JobResourceSummaryApplyConfiguration { + b.Noop = &value + return b +} + +// WithErrors adds the given value to the Errors field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Errors field. +func (b *JobResourceSummaryApplyConfiguration) WithErrors(values ...string) *JobResourceSummaryApplyConfiguration { + for i := range values { + b.Errors = append(b.Errors, values[i]) + } + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobspec.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobspec.go new file mode 100644 index 00000000000..91cc1b21906 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobspec.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// JobSpecApplyConfiguration represents a declarative configuration of the JobSpec type for use +// with apply. +type JobSpecApplyConfiguration struct { + Action *provisioningv0alpha1.JobAction `json:"action,omitempty"` + Repository *string `json:"repository,omitempty"` + PullRequest *PullRequestJobOptionsApplyConfiguration `json:"pr,omitempty"` + Push *ExportJobOptionsApplyConfiguration `json:"push,omitempty"` + Pull *SyncJobOptionsApplyConfiguration `json:"pull,omitempty"` + Migrate *MigrateJobOptionsApplyConfiguration `json:"migrate,omitempty"` + Delete *DeleteJobOptionsApplyConfiguration `json:"delete,omitempty"` + Move *MoveJobOptionsApplyConfiguration `json:"move,omitempty"` +} + +// JobSpecApplyConfiguration constructs a declarative configuration of the JobSpec type for use with +// apply. +func JobSpec() *JobSpecApplyConfiguration { + return &JobSpecApplyConfiguration{} +} + +// WithAction sets the Action field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Action field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithAction(value provisioningv0alpha1.JobAction) *JobSpecApplyConfiguration { + b.Action = &value + return b +} + +// WithRepository sets the Repository field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Repository field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithRepository(value string) *JobSpecApplyConfiguration { + b.Repository = &value + return b +} + +// WithPullRequest sets the PullRequest field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PullRequest field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithPullRequest(value *PullRequestJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.PullRequest = value + return b +} + +// WithPush sets the Push field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Push field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithPush(value *ExportJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.Push = value + return b +} + +// WithPull sets the Pull field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Pull field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithPull(value *SyncJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.Pull = value + return b +} + +// WithMigrate sets the Migrate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Migrate field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithMigrate(value *MigrateJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.Migrate = value + return b +} + +// WithDelete sets the Delete field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Delete field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithDelete(value *DeleteJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.Delete = value + return b +} + +// WithMove sets the Move field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Move field is set to the value of the last call. +func (b *JobSpecApplyConfiguration) WithMove(value *MoveJobOptionsApplyConfiguration) *JobSpecApplyConfiguration { + b.Move = value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go new file mode 100644 index 00000000000..37a025730e3 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// JobStatusApplyConfiguration represents a declarative configuration of the JobStatus type for use +// with apply. +type JobStatusApplyConfiguration struct { + State *provisioningv0alpha1.JobState `json:"state,omitempty"` + Started *int64 `json:"started,omitempty"` + Finished *int64 `json:"finished,omitempty"` + Message *string `json:"message,omitempty"` + Errors []string `json:"errors,omitempty"` + Progress *float64 `json:"progress,omitempty"` + Summary []*provisioningv0alpha1.JobResourceSummary `json:"summary,omitempty"` +} + +// JobStatusApplyConfiguration constructs a declarative configuration of the JobStatus type for use with +// apply. +func JobStatus() *JobStatusApplyConfiguration { + return &JobStatusApplyConfiguration{} +} + +// WithState sets the State field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the State field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithState(value provisioningv0alpha1.JobState) *JobStatusApplyConfiguration { + b.State = &value + return b +} + +// WithStarted sets the Started field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Started field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithStarted(value int64) *JobStatusApplyConfiguration { + b.Started = &value + return b +} + +// WithFinished sets the Finished field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Finished field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithFinished(value int64) *JobStatusApplyConfiguration { + b.Finished = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithMessage(value string) *JobStatusApplyConfiguration { + b.Message = &value + return b +} + +// WithErrors adds the given value to the Errors field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Errors field. +func (b *JobStatusApplyConfiguration) WithErrors(values ...string) *JobStatusApplyConfiguration { + for i := range values { + b.Errors = append(b.Errors, values[i]) + } + return b +} + +// WithProgress sets the Progress field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Progress field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithProgress(value float64) *JobStatusApplyConfiguration { + b.Progress = &value + return b +} + +// WithSummary adds the given value to the Summary field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Summary field. +func (b *JobStatusApplyConfiguration) WithSummary(values ...**provisioningv0alpha1.JobResourceSummary) *JobStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSummary") + } + b.Summary = append(b.Summary, *values[i]) + } + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/migratejoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/migratejoboptions.go new file mode 100644 index 00000000000..aee0b05fa3a --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/migratejoboptions.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// MigrateJobOptionsApplyConfiguration represents a declarative configuration of the MigrateJobOptions type for use +// with apply. +type MigrateJobOptionsApplyConfiguration struct { + History *bool `json:"history,omitempty"` + Message *string `json:"message,omitempty"` +} + +// MigrateJobOptionsApplyConfiguration constructs a declarative configuration of the MigrateJobOptions type for use with +// apply. +func MigrateJobOptions() *MigrateJobOptionsApplyConfiguration { + return &MigrateJobOptionsApplyConfiguration{} +} + +// WithHistory sets the History field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the History field is set to the value of the last call. +func (b *MigrateJobOptionsApplyConfiguration) WithHistory(value bool) *MigrateJobOptionsApplyConfiguration { + b.History = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *MigrateJobOptionsApplyConfiguration) WithMessage(value string) *MigrateJobOptionsApplyConfiguration { + b.Message = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/movejoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/movejoboptions.go new file mode 100644 index 00000000000..e1517ba1509 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/movejoboptions.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// MoveJobOptionsApplyConfiguration represents a declarative configuration of the MoveJobOptions type for use +// with apply. +type MoveJobOptionsApplyConfiguration struct { + Ref *string `json:"ref,omitempty"` + Paths []string `json:"paths,omitempty"` + TargetPath *string `json:"targetPath,omitempty"` + Resources []ResourceRefApplyConfiguration `json:"resources,omitempty"` +} + +// MoveJobOptionsApplyConfiguration constructs a declarative configuration of the MoveJobOptions type for use with +// apply. +func MoveJobOptions() *MoveJobOptionsApplyConfiguration { + return &MoveJobOptionsApplyConfiguration{} +} + +// WithRef sets the Ref field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ref field is set to the value of the last call. +func (b *MoveJobOptionsApplyConfiguration) WithRef(value string) *MoveJobOptionsApplyConfiguration { + b.Ref = &value + return b +} + +// WithPaths adds the given value to the Paths field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Paths field. +func (b *MoveJobOptionsApplyConfiguration) WithPaths(values ...string) *MoveJobOptionsApplyConfiguration { + for i := range values { + b.Paths = append(b.Paths, values[i]) + } + return b +} + +// WithTargetPath sets the TargetPath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TargetPath field is set to the value of the last call. +func (b *MoveJobOptionsApplyConfiguration) WithTargetPath(value string) *MoveJobOptionsApplyConfiguration { + b.TargetPath = &value + return b +} + +// WithResources adds the given value to the Resources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Resources field. +func (b *MoveJobOptionsApplyConfiguration) WithResources(values ...*ResourceRefApplyConfiguration) *MoveJobOptionsApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithResources") + } + b.Resources = append(b.Resources, *values[i]) + } + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/pullrequestjoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/pullrequestjoboptions.go new file mode 100644 index 00000000000..82aa157c5aa --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/pullrequestjoboptions.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// PullRequestJobOptionsApplyConfiguration represents a declarative configuration of the PullRequestJobOptions type for use +// with apply. +type PullRequestJobOptionsApplyConfiguration struct { + Ref *string `json:"ref,omitempty"` + PR *int `json:"pr,omitempty"` + Hash *string `json:"hash,omitempty"` + URL *string `json:"url,omitempty"` +} + +// PullRequestJobOptionsApplyConfiguration constructs a declarative configuration of the PullRequestJobOptions type for use with +// apply. +func PullRequestJobOptions() *PullRequestJobOptionsApplyConfiguration { + return &PullRequestJobOptionsApplyConfiguration{} +} + +// WithRef sets the Ref field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ref field is set to the value of the last call. +func (b *PullRequestJobOptionsApplyConfiguration) WithRef(value string) *PullRequestJobOptionsApplyConfiguration { + b.Ref = &value + return b +} + +// WithPR sets the PR field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PR field is set to the value of the last call. +func (b *PullRequestJobOptionsApplyConfiguration) WithPR(value int) *PullRequestJobOptionsApplyConfiguration { + b.PR = &value + return b +} + +// WithHash sets the Hash field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Hash field is set to the value of the last call. +func (b *PullRequestJobOptionsApplyConfiguration) WithHash(value string) *PullRequestJobOptionsApplyConfiguration { + b.Hash = &value + return b +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *PullRequestJobOptionsApplyConfiguration) WithURL(value string) *PullRequestJobOptionsApplyConfiguration { + b.URL = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourceref.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourceref.go new file mode 100644 index 00000000000..10bbfe0aedc --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourceref.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// ResourceRefApplyConfiguration represents a declarative configuration of the ResourceRef type for use +// with apply. +type ResourceRefApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Kind *string `json:"kind,omitempty"` + Group *string `json:"group,omitempty"` +} + +// ResourceRefApplyConfiguration constructs a declarative configuration of the ResourceRef type for use with +// apply. +func ResourceRef() *ResourceRefApplyConfiguration { + return &ResourceRefApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ResourceRefApplyConfiguration) WithName(value string) *ResourceRefApplyConfiguration { + b.Name = &value + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ResourceRefApplyConfiguration) WithKind(value string) *ResourceRefApplyConfiguration { + b.Kind = &value + return b +} + +// WithGroup sets the Group field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Group field is set to the value of the last call. +func (b *ResourceRefApplyConfiguration) WithGroup(value string) *ResourceRefApplyConfiguration { + b.Group = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncjoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncjoboptions.go new file mode 100644 index 00000000000..9310ff5c548 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncjoboptions.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// SyncJobOptionsApplyConfiguration represents a declarative configuration of the SyncJobOptions type for use +// with apply. +type SyncJobOptionsApplyConfiguration struct { + Incremental *bool `json:"incremental,omitempty"` +} + +// SyncJobOptionsApplyConfiguration constructs a declarative configuration of the SyncJobOptions type for use with +// apply. +func SyncJobOptions() *SyncJobOptionsApplyConfiguration { + return &SyncJobOptionsApplyConfiguration{} +} + +// WithIncremental sets the Incremental field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Incremental field is set to the value of the last call. +func (b *SyncJobOptionsApplyConfiguration) WithIncremental(value bool) *SyncJobOptionsApplyConfiguration { + b.Incremental = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go index 29392ef2f2a..6fc85d35cda 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/utils.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go @@ -20,6 +20,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { // Group=provisioning.grafana.app, Version=v0alpha1 case v0alpha1.SchemeGroupVersion.WithKind("BitbucketRepositoryConfig"): return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("DeleteJobOptions"): + return &provisioningv0alpha1.DeleteJobOptionsApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ExportJobOptions"): + return &provisioningv0alpha1.ExportJobOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"): return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("GitLabRepositoryConfig"): @@ -28,8 +32,22 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &provisioningv0alpha1.GitRepositoryConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("HealthStatus"): return &provisioningv0alpha1.HealthStatusApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("Job"): + return &provisioningv0alpha1.JobApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("JobResourceSummary"): + return &provisioningv0alpha1.JobResourceSummaryApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("JobSpec"): + return &provisioningv0alpha1.JobSpecApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("JobStatus"): + return &provisioningv0alpha1.JobStatusApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("LocalRepositoryConfig"): return &provisioningv0alpha1.LocalRepositoryConfigApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("MigrateJobOptions"): + return &provisioningv0alpha1.MigrateJobOptionsApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("MoveJobOptions"): + return &provisioningv0alpha1.MoveJobOptionsApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("PullRequestJobOptions"): + return &provisioningv0alpha1.PullRequestJobOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("Repository"): return &provisioningv0alpha1.RepositoryApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("RepositorySpec"): @@ -38,6 +56,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &provisioningv0alpha1.RepositoryStatusApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ResourceCount"): return &provisioningv0alpha1.ResourceCountApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ResourceRef"): + return &provisioningv0alpha1.ResourceRefApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("SyncJobOptions"): + return &provisioningv0alpha1.SyncJobOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("SyncOptions"): return &provisioningv0alpha1.SyncOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("SyncStatus"): diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_job.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_job.go new file mode 100644 index 00000000000..e1d4dc1d5d9 --- /dev/null +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_job.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1" + typedprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeJobs implements JobInterface +type fakeJobs struct { + *gentype.FakeClientWithListAndApply[*v0alpha1.Job, *v0alpha1.JobList, *provisioningv0alpha1.JobApplyConfiguration] + Fake *FakeProvisioningV0alpha1 +} + +func newFakeJobs(fake *FakeProvisioningV0alpha1, namespace string) typedprovisioningv0alpha1.JobInterface { + return &fakeJobs{ + gentype.NewFakeClientWithListAndApply[*v0alpha1.Job, *v0alpha1.JobList, *provisioningv0alpha1.JobApplyConfiguration]( + fake.Fake, + namespace, + v0alpha1.SchemeGroupVersion.WithResource("jobs"), + v0alpha1.SchemeGroupVersion.WithKind("Job"), + func() *v0alpha1.Job { return &v0alpha1.Job{} }, + func() *v0alpha1.JobList { return &v0alpha1.JobList{} }, + func(dst, src *v0alpha1.JobList) { dst.ListMeta = src.ListMeta }, + func(list *v0alpha1.JobList) []*v0alpha1.Job { return gentype.ToPointerSlice(list.Items) }, + func(list *v0alpha1.JobList, items []*v0alpha1.Job) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, + } +} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go index 19a9c803579..4a6afceb6de 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go @@ -14,6 +14,10 @@ type FakeProvisioningV0alpha1 struct { *testing.Fake } +func (c *FakeProvisioningV0alpha1) Jobs(namespace string) v0alpha1.JobInterface { + return newFakeJobs(c, namespace) +} + func (c *FakeProvisioningV0alpha1) Repositories(namespace string) v0alpha1.RepositoryInterface { return newFakeRepositories(c, namespace) } diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go index 2e9e7e51d5b..9916a25266d 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go @@ -4,4 +4,6 @@ package v0alpha1 +type JobExpansion interface{} + type RepositoryExpansion interface{} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/job.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/job.go new file mode 100644 index 00000000000..071313110c3 --- /dev/null +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/job.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + context "context" + + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1" + scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// JobsGetter has a method to return a JobInterface. +// A group's client should implement this interface. +type JobsGetter interface { + Jobs(namespace string) JobInterface +} + +// JobInterface has methods to work with Job resources. +type JobInterface interface { + Create(ctx context.Context, job *provisioningv0alpha1.Job, opts v1.CreateOptions) (*provisioningv0alpha1.Job, error) + Update(ctx context.Context, job *provisioningv0alpha1.Job, opts v1.UpdateOptions) (*provisioningv0alpha1.Job, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, job *provisioningv0alpha1.Job, opts v1.UpdateOptions) (*provisioningv0alpha1.Job, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*provisioningv0alpha1.Job, error) + List(ctx context.Context, opts v1.ListOptions) (*provisioningv0alpha1.JobList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *provisioningv0alpha1.Job, err error) + Apply(ctx context.Context, job *applyconfigurationprovisioningv0alpha1.JobApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Job, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, job *applyconfigurationprovisioningv0alpha1.JobApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Job, err error) + JobExpansion +} + +// jobs implements JobInterface +type jobs struct { + *gentype.ClientWithListAndApply[*provisioningv0alpha1.Job, *provisioningv0alpha1.JobList, *applyconfigurationprovisioningv0alpha1.JobApplyConfiguration] +} + +// newJobs returns a Jobs +func newJobs(c *ProvisioningV0alpha1Client, namespace string) *jobs { + return &jobs{ + gentype.NewClientWithListAndApply[*provisioningv0alpha1.Job, *provisioningv0alpha1.JobList, *applyconfigurationprovisioningv0alpha1.JobApplyConfiguration]( + "jobs", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *provisioningv0alpha1.Job { return &provisioningv0alpha1.Job{} }, + func() *provisioningv0alpha1.JobList { return &provisioningv0alpha1.JobList{} }, + ), + } +} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go index 75998b83300..b260183135e 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go @@ -14,6 +14,7 @@ import ( type ProvisioningV0alpha1Interface interface { RESTClient() rest.Interface + JobsGetter RepositoriesGetter } @@ -22,6 +23,10 @@ type ProvisioningV0alpha1Client struct { restClient rest.Interface } +func (c *ProvisioningV0alpha1Client) Jobs(namespace string) JobInterface { + return newJobs(c, namespace) +} + func (c *ProvisioningV0alpha1Client) Repositories(namespace string) RepositoryInterface { return newRepositories(c, namespace) } diff --git a/apps/provisioning/pkg/generated/informers/externalversions/generic.go b/apps/provisioning/pkg/generated/informers/externalversions/generic.go index d4b7d049e54..cdbcc682e3d 100644 --- a/apps/provisioning/pkg/generated/informers/externalversions/generic.go +++ b/apps/provisioning/pkg/generated/informers/externalversions/generic.go @@ -39,6 +39,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=provisioning.grafana.app, Version=v0alpha1 + case v0alpha1.SchemeGroupVersion.WithResource("jobs"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Jobs().Informer()}, nil case v0alpha1.SchemeGroupVersion.WithResource("repositories"): return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Repositories().Informer()}, nil diff --git a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go index b358790c61e..0b7c499f035 100644 --- a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go +++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go @@ -10,6 +10,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Jobs returns a JobInformer. + Jobs() JobInformer // Repositories returns a RepositoryInformer. Repositories() RepositoryInformer } @@ -25,6 +27,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Jobs returns a JobInformer. +func (v *version) Jobs() JobInformer { + return &jobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Repositories returns a RepositoryInformer. func (v *version) Repositories() RepositoryInformer { return &repositoryInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/job.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/job.go new file mode 100644 index 00000000000..ab0c9122012 --- /dev/null +++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/job.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by informer-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + context "context" + time "time" + + apisprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned" + internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces" + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// JobInformer provides access to a shared informer and lister for +// Jobs. +type JobInformer interface { + Informer() cache.SharedIndexInformer + Lister() provisioningv0alpha1.JobLister +} + +type jobInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewJobInformer constructs a new informer for Job type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewJobInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredJobInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredJobInformer constructs a new informer for Job type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredJobInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Jobs(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Jobs(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Jobs(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Jobs(namespace).Watch(ctx, options) + }, + }, + &apisprovisioningv0alpha1.Job{}, + resyncPeriod, + indexers, + ) +} + +func (f *jobInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *jobInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisprovisioningv0alpha1.Job{}, f.defaultInformer) +} + +func (f *jobInformer) Lister() provisioningv0alpha1.JobLister { + return provisioningv0alpha1.NewJobLister(f.Informer().GetIndexer()) +} diff --git a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go index 118fae5b629..94ecc5121e1 100644 --- a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go +++ b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go @@ -4,6 +4,14 @@ package v0alpha1 +// JobListerExpansion allows custom methods to be added to +// JobLister. +type JobListerExpansion interface{} + +// JobNamespaceListerExpansion allows custom methods to be added to +// JobNamespaceLister. +type JobNamespaceListerExpansion interface{} + // RepositoryListerExpansion allows custom methods to be added to // RepositoryLister. type RepositoryListerExpansion interface{} diff --git a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/job.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/job.go new file mode 100644 index 00000000000..4616d9140eb --- /dev/null +++ b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/job.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by lister-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// JobLister helps list Jobs. +// All objects returned here must be treated as read-only. +type JobLister interface { + // List lists all Jobs in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*provisioningv0alpha1.Job, err error) + // Jobs returns an object that can list and get Jobs. + Jobs(namespace string) JobNamespaceLister + JobListerExpansion +} + +// jobLister implements the JobLister interface. +type jobLister struct { + listers.ResourceIndexer[*provisioningv0alpha1.Job] +} + +// NewJobLister returns a new JobLister. +func NewJobLister(indexer cache.Indexer) JobLister { + return &jobLister{listers.New[*provisioningv0alpha1.Job](indexer, provisioningv0alpha1.Resource("job"))} +} + +// Jobs returns an object that can list and get Jobs. +func (s *jobLister) Jobs(namespace string) JobNamespaceLister { + return jobNamespaceLister{listers.NewNamespaced[*provisioningv0alpha1.Job](s.ResourceIndexer, namespace)} +} + +// JobNamespaceLister helps list and get Jobs. +// All objects returned here must be treated as read-only. +type JobNamespaceLister interface { + // List lists all Jobs in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*provisioningv0alpha1.Job, err error) + // Get retrieves the Job from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*provisioningv0alpha1.Job, error) + JobNamespaceListerExpansion +} + +// jobNamespaceLister implements the JobNamespaceLister +// interface. +type jobNamespaceLister struct { + listers.ResourceIndexer[*provisioningv0alpha1.Job] +} diff --git a/apps/sdk.mk b/apps/sdk.mk index 07105fe3973..c6b057005d6 100644 --- a/apps/sdk.mk +++ b/apps/sdk.mk @@ -1,4 +1,4 @@ -APP_SDK_VERSION = v0.40.2 +APP_SDK_VERSION = v0.40.3 APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk diff --git a/apps/secret/go.mod b/apps/secret/go.mod index 45567c3943a..e9ee004cd8e 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/secret go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.74.2 @@ -28,7 +28,7 @@ require ( github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // 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 @@ -42,7 +42,7 @@ require ( github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // 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.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 3705f402c16..9482a6103bf 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -36,10 +36,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf/go.mod h1:eAlOam2uWhrsEZlOoAr7XZ9hbBP7SyYGYn31/aQAPs8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -83,8 +83,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= @@ -119,6 +119,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= diff --git a/apps/secret/inline/v1beta1/inline.pb.go b/apps/secret/inline/v1beta1/inline.pb.go index d759693f4e0..5f73010f039 100644 --- a/apps/secret/inline/v1beta1/inline.pb.go +++ b/apps/secret/inline/v1beta1/inline.pb.go @@ -296,8 +296,8 @@ type DeleteWhenOwnedByResourceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Owner reference. Owner *ObjectReference `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` - // Name of the secure value to delete. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Name of the secure values to delete. + Names []string `protobuf:"bytes,2,rep,name=names,proto3" json:"names,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -339,11 +339,11 @@ func (x *DeleteWhenOwnedByResourceRequest) GetOwner() *ObjectReference { return nil } -func (x *DeleteWhenOwnedByResourceRequest) GetName() string { +func (x *DeleteWhenOwnedByResourceRequest) GetNames() []string { if x != nil { - return x.Name + return x.Names } - return "" + return nil } type DeleteWhenOwnedByResourceResponse struct { @@ -413,42 +413,43 @@ var file_inline_v1beta1_inline_proto_rawDesc = string([]byte{ 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2a, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6c, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x20, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, 0x64, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x23, 0x0a, 0x21, + 0x65, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x23, + 0x0a, 0x21, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, + 0x64, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xcc, 0x02, 0x0a, 0x18, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x22, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x43, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x69, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, + 0x4f, 0x77, 0x6e, 0x65, 0x64, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x2f, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, 0x64, 0x42, - 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0xcc, 0x02, 0x0a, 0x18, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x57, - 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x22, - 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, - 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x7e, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, - 0x6e, 0x65, 0x64, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2f, 0x2e, - 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, 0x64, 0x42, 0x79, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, 0x64, 0x42, 0x79, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x61, - 0x70, 0x70, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, - 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x68, 0x65, 0x6e, 0x4f, 0x77, 0x6e, 0x65, 0x64, + 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, + 0x2f, 0x61, 0x70, 0x70, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x69, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, }) var ( diff --git a/apps/secret/inline/v1beta1/inline.proto b/apps/secret/inline/v1beta1/inline.proto index 493b855c517..d0edcfa7395 100644 --- a/apps/secret/inline/v1beta1/inline.proto +++ b/apps/secret/inline/v1beta1/inline.proto @@ -49,8 +49,8 @@ message DeleteWhenOwnedByResourceRequest { // Owner reference. ObjectReference owner = 1; - // Name of the secure value to delete. - string name = 2; + // Name of the secure values to delete. + repeated string names = 2; } message DeleteWhenOwnedByResourceResponse {} diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index deaba460063..954eff80397 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk v0.40.3 k8s.io/apimachinery v0.33.3 k8s.io/klog/v2 v2.130.1 ) @@ -31,7 +31,7 @@ require ( 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.40.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -47,7 +47,7 @@ require ( 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/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index 88512ac2ed8..c586ffa88d2 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -47,10 +47,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J 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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -98,8 +98,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= diff --git a/conf/defaults.ini b/conf/defaults.ini index be7e06d7223..65cec28cfbe 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -374,6 +374,9 @@ disable_brute_force_login_protection = false # max number of failed login attempts before user gets locked brute_force_login_protection_max_attempts = 5 +# disable protection against brute force login attempts by username +disable_username_login_protection = false + # disable protection against brute force login attempts by IP address disable_ip_address_login_protection = true diff --git a/conf/sample.ini b/conf/sample.ini index 6d3afd69292..fb6550c23f1 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -371,6 +371,9 @@ # max number of failed login attempts before user gets locked ;brute_force_login_protection_max_attempts = 5 +# disable protection against brute force login attempts by username +;disable_username_login_protection = false + # disable protection against brute force login attempts by IP address ; disable_ip_address_login_protection = true diff --git a/devenv/frontend-service/Tiltfile b/devenv/frontend-service/Tiltfile index b7e6f81eca3..885d8eddaf3 100644 --- a/devenv/frontend-service/Tiltfile +++ b/devenv/frontend-service/Tiltfile @@ -56,10 +56,10 @@ local_resource( # --- Docker Compose docker_compose("./docker-compose.yaml") dc_resource("proxy", - resource_deps=["backend", "frontend-service"], + resource_deps=["grafana-api", "frontend-service"], labels=["services"] ) -dc_resource("backend", +dc_resource("grafana-api", resource_deps=["yarn start", "backend-build"], labels=["services"] ) @@ -67,6 +67,8 @@ dc_resource("frontend-service", resource_deps=["yarn start", "backend-build"], labels=["services"], ) +dc_resource("alloy", labels=["observability"]) +dc_resource("loki", labels=["observability"]) # paths in tilt files are confusing.... # - if tilt is dealing the the path, it is relative to the Tiltfile diff --git a/devenv/frontend-service/configs/alloy/_databases.alloy b/devenv/frontend-service/configs/alloy/_databases.alloy new file mode 100644 index 00000000000..eb1766c1346 --- /dev/null +++ b/devenv/frontend-service/configs/alloy/_databases.alloy @@ -0,0 +1,15 @@ +loki.relabel "publish_logs" { + rule { + action = "replace" + target_label = "instance" + replacement = constants.hostname + } + + forward_to = [loki.write.tilt_loki.receiver] +} + +loki.write "tilt_loki" { + endpoint { + url = "http://loki:3100/loki/api/v1/push" + } +} diff --git a/devenv/frontend-service/configs/alloy/alloy.alloy b/devenv/frontend-service/configs/alloy/alloy.alloy new file mode 100644 index 00000000000..bd09daa1e87 --- /dev/null +++ b/devenv/frontend-service/configs/alloy/alloy.alloy @@ -0,0 +1,23 @@ +// +// Self-config +// + +livedebugging { + enabled = true +} + +// Alloy logs +logging { + level = "info" + write_to = [loki.relabel.alloy.receiver] +} + +loki.relabel "alloy" { + rule { + action = "replace" + target_label = "job" + replacement = "alloy" + } + + forward_to = [loki.relabel.publish_logs.receiver] +} diff --git a/devenv/frontend-service/configs/alloy/docker.alloy b/devenv/frontend-service/configs/alloy/docker.alloy new file mode 100644 index 00000000000..a081aa75d31 --- /dev/null +++ b/devenv/frontend-service/configs/alloy/docker.alloy @@ -0,0 +1,44 @@ +discovery.docker "tilt" { + host = "unix:///var/run/docker.sock" + + filter { + name = "label" + values = ["alloy.logs=true"] + } + + // Exposes output +} + +discovery.relabel "tilt" { + // Read from + targets = discovery.docker.tilt.targets + + rule { + action = "replace" + source_labels = ["__meta_docker_container_name"] + regex = "/(.*)" + replacement = "$1" + target_label = "docker_container_name" + } + + rule { + action = "replace" + source_labels = ["__meta_docker_container_label_com_docker_compose_project"] + target_label = "docker_compose_project" + } + + rule { + action = "replace" + source_labels = ["__meta_docker_container_label_com_docker_compose_service"] + target_label = "service_name" + } + + // Exposes output +} + +loki.source.docker "default" { + targets = discovery.relabel.tilt.output + host = "unix:///var/run/docker.sock" + labels = {"job" = "docker"} + forward_to = [loki.relabel.publish_logs.receiver] +} diff --git a/devenv/frontend-service/docker-compose.yaml b/devenv/frontend-service/docker-compose.yaml index 17a0ab3c7ef..9e6939d6e97 100644 --- a/devenv/frontend-service/docker-compose.yaml +++ b/devenv/frontend-service/docker-compose.yaml @@ -12,8 +12,13 @@ services: ports: - '3000:80' # Gateway - '3010:81' # CDN + depends_on: + - grafana-api + - frontend-service + labels: + - 'alloy.logs=true' - backend: + grafana-api: image: grafana-fs-dev build: context: ../.. @@ -25,6 +30,8 @@ services: - ./provisioning/dashboards:/grafana/conf/provisioning/dashboards - ../dev-dashboards:/grafana/conf/dev-dashboards environment: + GF_DEFAULT_APP_MODE: development + GF_PANELS_ENABLE_ALPHA: true GF_SERVER_CDN_URL: http://localhost:3010 GF_FEATURE_TOGGLES_ENABLE: multiTenantFrontend GF_DATABASE_TYPE: postgres @@ -34,6 +41,8 @@ services: GF_DATABASE_PASSWORD: grafana ports: - '3011:3000' + labels: + - 'alloy.logs=true' frontend-service: image: grafana-fs-dev @@ -43,6 +52,8 @@ services: entrypoint: ['bin/grafana', 'server', 'target'] ports: - '3012:3000' + labels: + - 'alloy.logs=true' environment: GF_DEFAULT_APP_MODE: development GF_DEFAULT_TARGET: frontend-server @@ -57,7 +68,35 @@ services: POSTGRES_DB: grafana volumes: - postgres-data:/var/lib/postgresql/data + labels: + - 'alloy.logs=true' + + alloy: + image: grafana/alloy:latest + volumes: + - ./configs/alloy:/alloy-config + - /var/run/docker.sock:/var/run/docker.sock # To scrape Docker container logs + - alloy-data:/var/lib/alloy/data + ports: + - '12346:12345' # Alloy UI + command: + - run + - --server.http.listen-addr=0.0.0.0:12345 + - --storage.path=/var/lib/alloy/data + - /alloy-config + depends_on: + - loki + + loki: + image: grafana/loki + volumes: + - loki-data:/loki + command: -config.file=/etc/loki/local-config.yaml + labels: + - 'alloy.logs=true' volumes: backend-data: postgres-data: + alloy-data: + loki-data: diff --git a/devenv/frontend-service/nginx.conf b/devenv/frontend-service/nginx.conf index 1cf21295a36..5e2ad228eb3 100644 --- a/devenv/frontend-service/nginx.conf +++ b/devenv/frontend-service/nginx.conf @@ -2,7 +2,7 @@ # Instance ### upstream backend { - server backend:3000; + server grafana-api:3000; } upstream frontend { diff --git a/devenv/frontend-service/provisioning/dashboards/dashboards/instance.json b/devenv/frontend-service/provisioning/dashboards/dashboards/instance.json new file mode 100644 index 00000000000..1669392b6b6 --- /dev/null +++ b/devenv/frontend-service/provisioning/dashboards/dashboards/instance.json @@ -0,0 +1,282 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 272, + "links": [], + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "info" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "warn" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "unknown" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 3, + "interval": "60000ms", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.2.0-pre", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "direction": "backward", + "editorMode": "code", + "expr": "sum by (level, detected_level) (count_over_time({service_name=\"grafana-api\"}[$__auto]))", + "legendFormat": "{{level}}", + "queryType": "range", + "refId": "A" + } + ], + "title": "Logs volume", + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 2, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": false, + "enableLogDetails": true, + "showControls": false, + "showTime": false, + "sortOrder": "Descending", + "syntaxHighlighting": true, + "wrapLogMessage": false + }, + "pluginVersion": "12.2.0-pre", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "direction": "backward", + "editorMode": "code", + "expr": "{service_name=\"grafana-api\"}\r\n| logfmt\r\n| logger!=\"context\"\r\n| msg!=\"Request Completed\"\r\n| level!=\"debug\"\r\n| level!=\"info\"", + "queryType": "range", + "refId": "A" + } + ], + "title": "Error logs", + "type": "logs-new" + }, + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 1, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": false, + "enableLogDetails": true, + "showControls": false, + "showTime": false, + "sortOrder": "Descending", + "syntaxHighlighting": true, + "wrapLogMessage": false + }, + "pluginVersion": "12.2.0-pre", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "fs-loki" + }, + "direction": "backward", + "editorMode": "code", + "expr": "{service_name=\"grafana-api\"}\r\n| logfmt\r\n| logger!=\"context\"\r\n| msg!=\"Request Completed\"", + "queryType": "range", + "refId": "A" + } + ], + "title": "All logs", + "type": "logs-new" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Grafana Instance", + "uid": "fd70c2ef-3841-4bcc-89af-b639e7f31800", + "version": 4 +} diff --git a/devenv/frontend-service/provisioning/dashboards/devenv.yaml b/devenv/frontend-service/provisioning/dashboards/devenv.yaml index c22f2d90141..bec2d040a21 100644 --- a/devenv/frontend-service/provisioning/dashboards/devenv.yaml +++ b/devenv/frontend-service/provisioning/dashboards/devenv.yaml @@ -11,3 +11,11 @@ providers: # This path is relative to the WORKDIR in the docker container. It is mounted # in the docker-compose.yml file. path: conf/dev-dashboards + - name: 'frontend-service dashboards' + folder: 'frontend-service dashboards' + folderUid: '' + type: file + allowUiUpdates: false + updateIntervalSeconds: 3600 + options: + path: conf/provisioning/dashboards/dashboards diff --git a/devenv/frontend-service/provisioning/datasources/default.yaml b/devenv/frontend-service/provisioning/datasources/default.yaml index a3e3c50f570..c99a93fd0b2 100644 --- a/devenv/frontend-service/provisioning/datasources/default.yaml +++ b/devenv/frontend-service/provisioning/datasources/default.yaml @@ -7,3 +7,8 @@ datasources: - name: Testdata uid: PD8C576611E62080A type: grafana-testdata-datasource + + - name: Loki Logs + uid: fs-loki + type: loki + url: http://loki:3100 diff --git a/docs/sources/administration/migration-guide/cloud-migration-assistant.md b/docs/sources/administration/migration-guide/cloud-migration-assistant.md index 2ca94628b32..439e04c282d 100644 --- a/docs/sources/administration/migration-guide/cloud-migration-assistant.md +++ b/docs/sources/administration/migration-guide/cloud-migration-assistant.md @@ -192,7 +192,7 @@ The migration assistant can migrate the majority of Grafana Alerting resources t - Notification templates {{< admonition type="note">}} -The `grafana-default-email` contact point that's provisioned with every new Grafana instance doesn't have a UID by default and won't be migrated unless you edit or update and save it. You do not need to change the contact point for a UID to be generated when saved. +The `grafana-default-email` contact point that's provisioned with every new Grafana instance doesn't have a UID by default and won't be migrated unless you edit or update and save it. You don't need to change the contact point for a UID to be generated when saved. {{< /admonition >}} This is sufficient to have your Alerting configuration up and running in Grafana Cloud with minimal effort. diff --git a/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md b/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md index ba16e57c14a..e8dd965ffff 100644 --- a/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md +++ b/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md @@ -47,7 +47,7 @@ Note that in data source-managed groups, the alert rules and recording rules wit To create a new data source-managed recording rule: 1. Click **Alerts & IRM** -> **Alerting** -> **Alert rules**. -1. Scroll to the **Data source-managed section** and click **+New recording rule**. +1. At the top of the Alert rules page, click **More** -> **New Grafana recording rule**. ## Enter recording rule name diff --git a/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md b/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md index 470bffe0817..224ec27ea82 100644 --- a/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md +++ b/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md @@ -81,7 +81,7 @@ To create a new Grafana-managed recording rule: 1. Click **Alerts & IRM** -> **Alerting** -> **Alert rules**. -1. Scroll to the **Grafana-managed section** and click **+New recording rule**. +1. At the top of the Alert rules page, click **More** -> **New Grafana recording rule**. 1. Enter the names to identify your recording rule and metric. diff --git a/docs/sources/alerting/alerting-rules/create-recording-rules/migrate-recorded-queries.md b/docs/sources/alerting/alerting-rules/create-recording-rules/migrate-recorded-queries.md index 871aa3e6ae2..02adb77819c 100644 --- a/docs/sources/alerting/alerting-rules/create-recording-rules/migrate-recorded-queries.md +++ b/docs/sources/alerting/alerting-rules/create-recording-rules/migrate-recorded-queries.md @@ -24,15 +24,15 @@ Users can transpose their [now-depreciated recorded queries](/docs/grafana/lates ## Migrate your recorded queries to Grafana-managed alert rules -1. Navigate to **Administration > Plugins and Data > Recorded queries.** +1. Navigate to **Administration** -> **Plugins and Data** -> **Recorded queries.** 1. Note the data source, query PromQL, interval, and time range, and copy them somewhere accessible. {{< figure alt="Example of relevant recorded query information" src="/media/docs/alerting/rec-query-example.png" max-width="800px" >}} -1. Now navigate to **Alerting > Alert rules.** +1. Now navigate to **Alerting** -> **Alert rules.** -1. In the Grafana-managed section of the Alert rules page, click **+ New recording**. +1. At the top of the Alert rules page, click **More** -> **New Grafana recording rule**. Add a name for your Recording Rule and a name for the new metric that the recording rule generates. diff --git a/docs/sources/alerting/monitor-status/declare-incident-from-alert.md b/docs/sources/alerting/monitor-status/declare-incident-from-alert.md index 8b9ab0f1339..a935d1c7335 100644 --- a/docs/sources/alerting/monitor-status/declare-incident-from-alert.md +++ b/docs/sources/alerting/monitor-status/declare-incident-from-alert.md @@ -29,7 +29,7 @@ Declare an incident from a firing alert to streamline your alert to incident wor To declare an incident from a firing alert, complete the following steps. 1. Navigate to **Alerts & IRM** -> **Alerting** -> **Alert rules**. -1. From the Alert rules page, find the firing alert that you want to declare an incident for. +1. From the Alert rules page, click the **Firing** filter to display firing alerts. Find the firing alert that you want to declare an incident for. 1. Click **More** -> **Declare Incident**. Alternatively, you can declare an incident from the Alert details page. diff --git a/docs/sources/alerting/monitor-status/view-alert-rules.md b/docs/sources/alerting/monitor-status/view-alert-rules.md index 34869a84364..34d60317d20 100644 --- a/docs/sources/alerting/monitor-status/view-alert-rules.md +++ b/docs/sources/alerting/monitor-status/view-alert-rules.md @@ -33,13 +33,15 @@ The Alert rules list view page lists all existing recording and alert rules, inc To access the Alert rules page, click **Alerts & IRM** -> **Alerting** -> **Alert rules**. -{{< figure src="/media/docs/alerting/alert-rules-page.png" max-width="750px" alt="Alert rule view page in Grafana Alerting" >}} +{{< figure src="/media/docs/alerting/alert-rules-page-2.png" max-width="750px" alt="Alert rule view page in Grafana Alerting" >}} -By default, alert rules are grouped by alert rule type: Grafana-managed or data source-managed. +By default, alert rules are grouped in separate sections—one for Grafana-managed alerts, and another for data source-managed alerts. +Inside the Grafana-managed alert rules section, the rules are organized in a hierarchical structure, from folder -> rule group -> rules. +Inside the data source-managed alert rules section, the rules are organized from namespace ->rule group -> rules. -In this view, you can find and edit rules created in Grafana. However, rules created in Prometheus-compatible data sources are displayed but cannot be edited. +Select a group to expand it and view the list of alert rules within that group. -This view includes filters to simplify managing large volumes of alerts. +The view includes filters to simplify managing large volumes of alerts. You can filter by data sources, dashboards, and alert rule properties such as state, type, health, and contact points. The **Search** input allows you to filter by additional parameters like folders, evaluation groups, labels, and more. @@ -47,15 +49,11 @@ You can filter by data sources, dashboards, and alert rule properties such as st You can also change how the rule list is displayed using the **View as** option. -- **Grouped**: Displays Grafana rules grouped by folder and evaluation group, and data-source rules by namespace and evaluation group. This is the default view. +- **Grouped**: Displays Grafana rules grouped in a hierarchical structure, from folder/namespace, to evaluation group, to the individual rules. This is the default view. -- **List**: Displays Grafana rules grouped only by folder. +- **List**: Displays all rules from all data sources in a flat, unpaginated list. -- **State**: Displays rules grouped by state, providing an overview for each state. - -Select a group to expand it and view the list of alert rules within that group. - -{{< figure src="/media/docs/alerting/view-alert-rule-list-with-actions.png" max-width="750px" alt="View alert rule state and alert rule health in Grafana Alerting" >}} +{{< figure src="/media/docs/alerting/view-alert-rule-list-with-actions-2.png" max-width="750px" alt="View alert rule state and alert rule health in Grafana Alerting" >}} For details on how rule states and alert instance states are displayed, refer to [View alert state](ref:view-alert-state). @@ -69,7 +67,7 @@ In Grafana OSS and Enterprise, the number of alert rule versions is limited. Fre To view or restore previous versions for an alert rule, complete the following steps. -1. Navigate to **Alerts & IRM -> Alerting -> Alert rules**. +1. Navigate to **Alerts & IRM** -> **Alerting** -> **Alert rules**. 1. Select an alert rule and click **View**. 1. Click the **Versions** tab. The page displays a list of the previous rule versions. @@ -89,7 +87,7 @@ Admin users can delete all of the alert rules within a folder. To delete all the Only users with an Admin role can restore deleted Grafana-managed alert rules. After an alert rule is restored, it is restored with a new, different UID from the one it had before. -1. Go to **Alerts & IRM > Alerting > Recently deleted**. +1. Go to **Alerts & IRM** -> **Alerting** -> **Recently deleted**. 1. Click the **Restore** button to restore the alert rule or click **Delete permanently** to delete the alert rule. {{< admonition type="note" >}} diff --git a/docs/sources/alerting/monitor-status/view-alert-state.md b/docs/sources/alerting/monitor-status/view-alert-state.md index ad12995c0b7..12e512a2325 100644 --- a/docs/sources/alerting/monitor-status/view-alert-state.md +++ b/docs/sources/alerting/monitor-status/view-alert-state.md @@ -66,26 +66,24 @@ There are three key components that helps us understand the behavior of our aler ## View alert rule and instance states -To view the state and health of your alert rules and the status of alert instances: +To view the details of your alert rules and the status of alert instances: 1. Click **Alerts & IRM** -> **Alerting**. 1. Click **Alert rules** to view the list of existing alert rules. - {{< figure src="/media/docs/alerting/view-alert-rule-list-with-actions.png" max-width="750px" alt="View alert rule state and alert rule health in Grafana Alerting" >}} + {{< figure src="/media/docs/alerting/view-alert-rule-list-with-actions2.png" max-width="750px" alt="View alert rule state and alert rule health in Grafana Alerting" >}} - Each alert rule shows its state, health, summary, next evaluation time, and available actions such as **Pause evaluation**, **Silence notifications**, **Export**, **Delete**, and more. + Each alert rule shows its state, summary, and available actions such as **Pause evaluation**, **Silence notifications**, **Export**, **Delete**, and more. 1. Click on an alert rule to view additional details and its resulting alert instances. - {{< figure src="/media/docs/alerting/view-alert-instance-state.png" max-width="750px" alt="View alert rule state and alert rule health in Grafana Alerting" >}} - ### View from the alert rule details page To view more alert rule details, complete the following steps. 1. Click **Alerts & IRM** -> **Alerting** -> **Alert rules**. 1. Click to expand an alert rule. -1. In **Actions**, click **View** (the eye icon). +1. Click the alert name to go to the alert details view. {{< figure src="/media/docs/alerting/alert-rule-view-page-with-breadcrumb.png" max-width="750px" alt="Alert rule view page in Grafana Alerting" >}} diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index a79ccc33e70..67cba858ef2 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -703,13 +703,17 @@ PostgreSQL, MySQL, and MSSQL data sources don't use the proxy and are not affect Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. -An existing user's account is unable to login for five minutes if all login attempts are spent within a 5 minute window. +Login is blocked for five minutes if all login attempts are spent within a 5 minute window. #### `brute_force_login_protection_max_attempts` -Configure how many login attempts a user can have within a five minute window before their account is locked. +Configure how many login attempts can be made within a five minute window before being blocked. Default is `5`. +#### `disable_username_login_protection` + +Set to `true` to disable [brute force login protection by username](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. User will be unable to login for 5 minutes if all login attempts are spent within a 5 minute window. + #### `disable_ip_address_login_protection` Set to `true` to disable [brute force login protection by IP address](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `true`. Anyone from the IP address will be unable to login for 5 minutes if all login attempts are spent within a 5 minute window. diff --git a/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md b/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md index 92004450844..0130d0f21d1 100644 --- a/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md +++ b/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md @@ -76,6 +76,17 @@ The configuration file in Grafana Enterprise contains the following options. For ;hide_edition = ``` +{{< admonition type="note" >}} +For the `login_logo` option, Grafana recommends using SVG files that are 48 pixels by 48 pixels or smaller. You also don't need to use the `url()` function for `login_logo`. + +Additionally, you can copy images to the local Grafana image directory, `/usr/share/grafana/public/img/`, and set `login_logo` to the stored image. For example: + +```ini +login_logo = /public/img/ +``` + +{{< /admonition >}} + You have the option of adding custom links in place of the default footer links (Documentation, Support, Community). Below is an example of how to replace the default footer and help links with custom links. ```ini diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index fd567340110..49062997533 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -29,7 +29,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `featureHighlights` | Highlight Grafana Enterprise features | | | `correlations` | Correlations page | Yes | | `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes | -| `nestedFolders` | Enable folder nesting | Yes | | `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes | | `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes | | `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes | diff --git a/e2e/dashboards-search-suite/mode0.ini b/e2e/dashboards-search-suite/mode0.ini index e8f0a768c77..5bb45c0fceb 100644 --- a/e2e/dashboards-search-suite/mode0.ini +++ b/e2e/dashboards-search-suite/mode0.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 0 diff --git a/e2e/dashboards-search-suite/mode1.ini b/e2e/dashboards-search-suite/mode1.ini index 5beecc31f65..0a3a6463c03 100644 --- a/e2e/dashboards-search-suite/mode1.ini +++ b/e2e/dashboards-search-suite/mode1.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 1 diff --git a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini index 45b16de4034..b034996f8cb 100644 --- a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini +++ b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = false grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 2 diff --git a/e2e/dashboards-search-suite/mode2.ini b/e2e/dashboards-search-suite/mode2.ini index 55538ee82e3..83c15652063 100644 --- a/e2e/dashboards-search-suite/mode2.ini +++ b/e2e/dashboards-search-suite/mode2.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 2 diff --git a/e2e/dashboards-search-suite/mode3.ini b/e2e/dashboards-search-suite/mode3.ini index 5955dfcd72a..b9c02681dd8 100644 --- a/e2e/dashboards-search-suite/mode3.ini +++ b/e2e/dashboards-search-suite/mode3.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 3 diff --git a/e2e/dashboards-search-suite/mode4.ini b/e2e/dashboards-search-suite/mode4.ini index e0f6f6d892d..db8796c572f 100644 --- a/e2e/dashboards-search-suite/mode4.ini +++ b/e2e/dashboards-search-suite/mode4.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 4 diff --git a/e2e/dashboards-search-suite/mode5.ini b/e2e/dashboards-search-suite/mode5.ini index 8722cb091eb..596fe7cb80c 100644 --- a/e2e/dashboards-search-suite/mode5.ini +++ b/e2e/dashboards-search-suite/mode5.ini @@ -5,7 +5,6 @@ unifiedStorageSearch = true unifiedStorageSearchUI = true grafanaAPIServerWithExperimentalAPIs = true unifiedStorageSearchSprinkles = true -unifiedStorageSearchPermissionFiltering = true [unified_storage.folders.folder.grafana.app] dualWriterMode = 5 diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 2123311d889..bc2f2db4004 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -1,8 +1,8 @@ { + "extends": "../scripts/tsconfig.base.json", "compilerOptions": { "types": ["cypress"], "resolveJsonModule": true }, - "extends": "@grafana/tsconfig/base.json", "include": ["**/*.ts", "cypress/support/e2e.js", "cypress/support/index.d.ts"] } diff --git a/eslint.config.js b/eslint.config.js index c31f2f77930..075aaaf17e7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -157,6 +157,14 @@ module.exports = [ '@typescript-eslint/no-redeclare': ['error'], 'unicorn/no-empty-file': 'error', 'no-constant-condition': 'error', + 'no-restricted-syntax': [ + 'error', + { + // value regex is to filter out whitespace-only text nodes (e.g. new lines and spaces in the JSX) + selector: "JSXElement[openingElement.name.name='a'] > JSXText[value!=/^\\s*$/]", + message: 'No bare anchor nodes containing only text. Use `TextLink` instead.', + }, + ], }, }, { diff --git a/go.mod b/go.mod index 03f4d527c89..8de5333353b 100644 --- a/go.mod +++ b/go.mod @@ -95,8 +95,8 @@ require ( github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend - github.com/grafana/grafana-app-sdk v0.40.2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-app-sdk/logging v0.40.1 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.40.3 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk/logging v0.40.2 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.1.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad @@ -153,7 +153,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend github.com/phpdave11/gofpdi v1.0.14 // @grafana/sharing-squad github.com/prometheus/alertmanager v0.28.0 // @grafana/alerting-backend - github.com/prometheus/client_golang v1.22.0 // @grafana/alerting-backend + github.com/prometheus/client_golang v1.23.0 // @grafana/alerting-backend github.com/prometheus/client_model v0.6.2 // @grafana/grafana-backend-group github.com/prometheus/common v0.65.0 // @grafana/alerting-backend github.com/prometheus/prometheus v0.303.1 // @grafana/alerting-backend @@ -230,19 +230,21 @@ require ( ) require ( - github.com/grafana/grafana/apps/advisor v0.0.0-20250804150913-990f1c69ecc2 // @grafana/plugins-platform-backend - github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250804150913-990f1c69ecc2 // @grafana/alerting-backend - github.com/grafana/grafana/apps/dashboard v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad - github.com/grafana/grafana/apps/folder v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-search-and-storage - github.com/grafana/grafana/apps/iam v0.0.0-20250804150913-990f1c69ecc2 // @grafana/identity-access-team - github.com/grafana/grafana/apps/investigations v0.0.0-20250804150913-990f1c69ecc2 // @fcjack @matryer - github.com/grafana/grafana/apps/playlist v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/apps/provisioning v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/apps/secret v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-operator-experience-squad - github.com/grafana/grafana/apps/shorturl v0.0.0-20250804150913-990f1c69ecc2 // @grafana/sharing-squad - github.com/grafana/grafana/pkg/aggregator v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/pkg/apiserver v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/apps/advisor v0.0.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend + github.com/grafana/grafana/apps/dashboard v0.0.0 // @grafana/grafana-app-platform-squad @grafana/dashboards-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/playlist v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/apps/plugins v0.0.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana/apps/preferences v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/apps/provisioning v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/apps/secret v0.0.0 // @grafana/grafana-operator-experience-squad + github.com/grafana/grafana/apps/shorturl v0.0.0 // @grafana/sharing-squad + github.com/grafana/grafana/pkg/aggregator v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/pkg/apimachinery v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/pkg/apiserver v0.0.0 // @grafana/grafana-app-platform-squad // This needs to be here for other projects that import grafana/grafana // For local development grafana/grafana will always use the local files @@ -251,6 +253,27 @@ require ( github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad ) +// Replace the workspace versions +replace ( + github.com/grafana/grafana/apps/advisor => ./apps/advisor + github.com/grafana/grafana/apps/alerting/notifications => ./apps/alerting/notifications + 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/playlist => ./apps/playlist + github.com/grafana/grafana/apps/plugins => ./apps/plugins + github.com/grafana/grafana/apps/preferences => ./apps/preferences + github.com/grafana/grafana/apps/provisioning => ./apps/provisioning + github.com/grafana/grafana/apps/secret => ./apps/secret + github.com/grafana/grafana/apps/shorturl => ./apps/shorturl + + // Packages + github.com/grafana/grafana/pkg/aggregator => ./pkg/aggregator + github.com/grafana/grafana/pkg/apimachinery => ./pkg/apimachinery + github.com/grafana/grafana/pkg/apiserver => ./pkg/apiserver +) + require ( cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.1 // indirect diff --git a/go.sum b/go.sum index 9e576825284..aa7c88a369c 100644 --- a/go.sum +++ b/go.sum @@ -1600,10 +1600,10 @@ github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= -github.com/grafana/grafana-app-sdk v0.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= -github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= -github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= -github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= +github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= +github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= +github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtMDoe0KhmE= github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= @@ -1616,32 +1616,6 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= -github.com/grafana/grafana/apps/advisor v0.0.0-20250804150913-990f1c69ecc2 h1:APintn9iX4pIRrDBGmxGf+d8PekMNtt36zb4rACGQgY= -github.com/grafana/grafana/apps/advisor v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:5qLtZ9W2PlDZa2Wxq3YaK1v35GIZmPNP0m8u8HhTYfI= -github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250804150913-990f1c69ecc2 h1:bZ61Ik/7FQaF5c97u5PSJ+SJLSjQ6Q9zBO3GRDuzajI= -github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:SR1zEHjyL30eZIuwxXLr9ubUKzidQrNEtFNn6wW4ZxE= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250804150913-990f1c69ecc2 h1:M5kMQVRjcjtnGuw0RM9/iab5VpmJyBwpEDbSfS6IQV4= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:Ik6PLuCNceqTeOesGeQLn4hVL5wCbfscuAEhBEdq8js= -github.com/grafana/grafana/apps/folder v0.0.0-20250804150913-990f1c69ecc2 h1:tubqrwcZaJzS3DM2bLJxK6OCUsmBK0Vs95HiBuhCQcU= -github.com/grafana/grafana/apps/folder v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:la6q+Qvy0JGzBu/yyjC+e7xPefPM7CEfEgD574R2QMU= -github.com/grafana/grafana/apps/iam v0.0.0-20250804150913-990f1c69ecc2 h1:T55BYKx/s2cWfGRtBqYAlqqgE+rQIEV56OxYEhtCXH8= -github.com/grafana/grafana/apps/iam v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:6dF/R8xe/zkVIlbKCRjXsfhxA3ZsNiQf46S9/ezEEKI= -github.com/grafana/grafana/apps/investigations v0.0.0-20250804150913-990f1c69ecc2 h1:2xEMvfEqgYc/gunmHmsw51ZMVCdEvsqJTg3qaaqh8nw= -github.com/grafana/grafana/apps/investigations v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:IOpGVuSwFWE7EMrooI8I6OIU+rOmJinOaxK3pVMl7No= -github.com/grafana/grafana/apps/playlist v0.0.0-20250804150913-990f1c69ecc2 h1:Bt9EQkYdYq+xumbEnNxbbz0in7JRJkK9lVFs49WEetE= -github.com/grafana/grafana/apps/playlist v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:M1ily25xZS7sZlQ5yK1HV0UjRme6lpOl6I3dcfyqqRo= -github.com/grafana/grafana/apps/provisioning v0.0.0-20250804150913-990f1c69ecc2 h1:83OGBndFNYijNjgQpSf4y1bSNlscVDq8ru97Q6PyU6o= -github.com/grafana/grafana/apps/provisioning v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:qzFUVwLI1b5UIbVFxFydUYAsnOK27AgtA5so3EW8jM0= -github.com/grafana/grafana/apps/secret v0.0.0-20250804150913-990f1c69ecc2 h1:r5oGyvRVxljzKHTQYHtpmK2Et+BVhWQVw7aXo+FUygk= -github.com/grafana/grafana/apps/secret v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:pS2M5ILsHx9VNTM96glLtCjCVXHWyfGcT34WHvbbMtM= -github.com/grafana/grafana/apps/shorturl v0.0.0-20250804150913-990f1c69ecc2 h1:n+PKetgDEzRYaenMmz5gXXrQMLScORlO+ZCnriZucQw= -github.com/grafana/grafana/apps/shorturl v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:6F043DPw1shYLpIAAlbww4BMDtRtVX5MetfUf0s2zoA= -github.com/grafana/grafana/pkg/aggregator v0.0.0-20250804150913-990f1c69ecc2 h1:1AdHE94bYfEG7cuFb92wFlBaVPeexF21l9zCFmN8im0= -github.com/grafana/grafana/pkg/aggregator v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:wU4CoDIHuOOL7ZnchyvfER5UAcEz94A+ZkY+Xt+Ks/8= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= -github.com/grafana/grafana/pkg/apiserver v0.0.0-20250804150913-990f1c69ecc2 h1:oUINBBiA25EwjOFXJVa4rmU8jHlOGn5EdPCmqpIqy4Q= -github.com/grafana/grafana/pkg/apiserver v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:BA9Rvm6co9tbrZ0lS8ScqLFvAnDQ4lZOyplJgB+yMQQ= github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s= @@ -2216,8 +2190,8 @@ github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+L github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/go.work b/go.work index 51f59db084a..50347ae04ba 100644 --- a/go.work +++ b/go.work @@ -12,6 +12,7 @@ use ( ./apps/iam ./apps/investigations ./apps/playlist + ./apps/plugins ./apps/provisioning ./apps/secret ./apps/shorturl diff --git a/go.work.sum b/go.work.sum index 9d0c865b9f1..34c0540f8ce 100644 --- a/go.work.sum +++ b/go.work.sum @@ -553,6 +553,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbO github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6 h1:UucmvNRPE75F3KzT68GHhKzOPwttxiFkh1d5LTTywW8= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6/go.mod h1:XGripOBEUAcge8IUWR/NMAB5qO9k82tkbpoewBpyjYQ= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= @@ -969,12 +970,12 @@ github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/ github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= -github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= @@ -987,30 +988,22 @@ github.com/grafana/grafana-plugin-sdk-go v0.275.0/go.mod h1:mO9LJqdXDh5JpO/xIdPA github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU= github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c= -github.com/grafana/grafana/apps/advisor v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:5qLtZ9W2PlDZa2Wxq3YaK1v35GIZmPNP0m8u8HhTYfI= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec17e0e4ce03/go.mod h1:oemrhKvFxxc5m32xKHPxInEHAObH0/hPPyHUiBUZ1Cc= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:VkX53kBiqIMHBoGgeEDJnzm5Nwcmv/726tuZuT5SvJY= -github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:SR1zEHjyL30eZIuwxXLr9ubUKzidQrNEtFNn6wW4ZxE= github.com/grafana/grafana/apps/dashboard v0.0.0-20250616135341-59c2f154336b/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:eR8wca74ADgxBrvX0uNpdB1qnPaGx/KhCm4Xj8oqHfQ= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:Ik6PLuCNceqTeOesGeQLn4hVL5wCbfscuAEhBEdq8js= -github.com/grafana/grafana/apps/folder v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:la6q+Qvy0JGzBu/yyjC+e7xPefPM7CEfEgD574R2QMU= -github.com/grafana/grafana/apps/iam v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:6dF/R8xe/zkVIlbKCRjXsfhxA3ZsNiQf46S9/ezEEKI= github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI= -github.com/grafana/grafana/apps/investigations v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:IOpGVuSwFWE7EMrooI8I6OIU+rOmJinOaxK3pVMl7No= github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA= -github.com/grafana/grafana/apps/playlist v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:M1ily25xZS7sZlQ5yK1HV0UjRme6lpOl6I3dcfyqqRo= -github.com/grafana/grafana/apps/provisioning v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:qzFUVwLI1b5UIbVFxFydUYAsnOK27AgtA5so3EW8jM0= -github.com/grafana/grafana/apps/secret v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:pS2M5ILsHx9VNTM96glLtCjCVXHWyfGcT34WHvbbMtM= -github.com/grafana/grafana/apps/shorturl v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:6F043DPw1shYLpIAAlbww4BMDtRtVX5MetfUf0s2zoA= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff h1:JDT0Mcfpi3c525xzeli+v5dR9pf5HhdFjr8djRdhs10= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924 h1:uGXX6gCF1q2ytIL0w1X3UAKgF/UZ7eDDAgOaSqLOeW8= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001 h1:y2AHkdji2I+zXv8rsSC8OjWEzJJjqW5OlmCsZR5+RuU= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA= -github.com/grafana/grafana/pkg/aggregator v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:wU4CoDIHuOOL7ZnchyvfER5UAcEz94A+ZkY+Xt+Ks/8= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= -github.com/grafana/grafana/pkg/apiserver v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:BA9Rvm6co9tbrZ0lS8ScqLFvAnDQ4lZOyplJgB+yMQQ= github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c= -github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:2HRzUK/xQEYc+8d5If/XSusMcaYq9IptnBSHACiQcOQ= github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250121113133-e747350fee2d/go.mod h1:CXpwZ3Mkw6xVlGKc0SqUxqXCP3Uv182q6qAQnLaLxRg= github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:xrKQcxQxz+IUF90ybtfENFeEXtlj9nAsX/3Fw0KEIeQ= github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed h1:59JF1WhHLT+lNX89Tm1OzOEySMVMASAhaPbsRjtp8Kc= @@ -1023,7 +1016,6 @@ github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975 github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975/go.mod h1:FGdGvhI40Dq+CTQaSzK9evuve774cgOUdGfVO04OXkw= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= -github.com/grafana/sqlds/v4 v4.2.4 h1:Xlxy1udWqDK0dlbuJ1qXL7K3EYaf+aKMl38zhd3VbQY= github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU= github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P2qggOAHTj/GCZfoLBle3OvNSYh1VkRBU= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -1670,7 +1662,6 @@ golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0Y golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -1730,14 +1721,12 @@ golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFK golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -1831,7 +1820,6 @@ google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= @@ -1859,7 +1847,6 @@ honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= k8s.io/code-generator v0.33.1 h1:ZLzIRdMsh3Myfnx9BaooX6iQry29UJjVfVG+BuS+UMw= k8s.io/code-generator v0.33.1/go.mod h1:HUKT7Ubp6bOgIbbaPIs9lpd2Q02uqkMCMx9/GjDrWpY= k8s.io/code-generator v0.33.2 h1:PCJ0Y6viTCxxJHMOyGqYwWEteM4q6y1Hqo2rNpl6jF4= @@ -1896,4 +1883,3 @@ sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2Lfw sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= diff --git a/jest.config.js b/jest.config.js index a576f4ec0c4..ef107c8cc24 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,7 +41,7 @@ module.exports = { verbose: false, testEnvironment: 'jsdom', transform: { - '^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest'), { isolatedModules: true }], + '^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest')], }, transformIgnorePatterns: [ `/node_modules/(?!${esModules})`, // exclude es modules to prevent TS complaining diff --git a/package.json b/package.json index 71c62b5c442..90c32b62b0e 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", "@grafana/plugin-e2e": "2.1.7", "@grafana/test-utils": "workspace:*", - "@grafana/tsconfig": "^2.0.0", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", "@playwright/test": "1.54.1", @@ -199,7 +198,7 @@ "eslint-plugin-testing-library": "^7.0.0", "eslint-plugin-unicorn": "^56.0.0", "eslint-scope": "^8.1.0", - "eslint-webpack-plugin": "4.2.0", + "eslint-webpack-plugin": "5.0.2", "expose-loader": "5.0.1", "fishery": "^2.2.2", "fork-ts-checker-webpack-plugin": "9.1.0", @@ -226,7 +225,7 @@ "node-notifier": "10.0.1", "nx": "20.7.1", "openapi-types": "^12.1.3", - "pa11y-ci": "^3.1.0", + "pa11y-ci": "^4.0.0", "pdf-parse": "^1.1.1", "plop": "^4.0.1", "postcss": "8.5.6", @@ -260,8 +259,8 @@ "webpack-subresource-integrity": "^5.2.0-rc.1", "webpackbar": "^7.0.0", "yaml": "^2.0.0", - "yargs": "^17.5.1", - "zod": "^3.25.55" + "yargs": "^18.0.0", + "zod": "^4.0.0" }, "dependencies": { "@bsull/augurs": "^0.10.0", @@ -272,7 +271,7 @@ "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", - "@grafana/assistant": "0.0.13", + "@grafana/assistant": "0.0.14", "@grafana/aws-sdk": "0.7.1", "@grafana/azure-sdk": "0.0.7", "@grafana/data": "workspace:*", @@ -287,11 +286,11 @@ "@grafana/llm": "0.22.1", "@grafana/monaco-logql": "^0.0.8", "@grafana/o11y-ds-frontend": "workspace:*", - "@grafana/plugin-ui": "0.10.7", + "@grafana/plugin-ui": "0.10.8", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.29.1", - "@grafana/scenes-react": "6.29.1", + "@grafana/scenes": "6.29.3", + "@grafana/scenes-react": "6.29.3", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index 27ba36f953a..e51a30bdcb3 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -62,7 +62,6 @@ }, "devDependencies": { "@grafana/test-utils": "workspace:*", - "@grafana/tsconfig": "^2.0.0", "@rtk-query/codegen-openapi": "^2.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/packages/grafana-alerting/tsconfig.json b/packages/grafana-alerting/tsconfig.json index e2fb19bd649..33a09e35d0b 100644 --- a/packages/grafana-alerting/tsconfig.json +++ b/packages/grafana-alerting/tsconfig.json @@ -1,5 +1,7 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "jsx": "react-jsx", "declarationDir": "./dist/types", "emitDeclarationOnly": true, @@ -8,7 +10,6 @@ "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", "include": ["typings/jest", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts", "src/**/*.ts*"], "ts-node": { "compilerOptions": { diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 77cbb8f36d6..9b0733b3441 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -84,7 +84,6 @@ "xss": "^1.0.14" }, "devDependencies": { - "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "16.0.1", "@types/history": "4.7.11", "@types/lodash": "4.17.20", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 86faf2d7f04..cb400b4e5fa 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -80,11 +80,6 @@ export interface FeatureToggles { */ mysqlAnsiQuotes?: boolean; /** - * Enable folder nesting - * @default true - */ - nestedFolders?: boolean; - /** * Rule backtesting API for alerting */ alertingBacktesting?: boolean; @@ -684,11 +679,6 @@ export interface FeatureToggles { */ unifiedStorageSearchSprinkles?: boolean; /** - * Enable permission filtering on unified storage search - * @default true - */ - unifiedStorageSearchPermissionFiltering?: boolean; - /** * Pick the dual write mode from database configs */ managedDualWriter?: boolean; @@ -1099,4 +1089,9 @@ export interface FeatureToggles { * New Log Context component */ newLogContext?: boolean; + /** + * Enables new design for the Clickhouse data source configuration page + * @default false + */ + newClickhouseConfigPageDesign?: boolean; } diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 488eced154a..3ed7de0c766 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -190,6 +190,7 @@ export enum PluginExtensionPoints { AlertingHomePage = 'grafana/alerting/home', AlertingAlertingRuleAction = 'grafana/alerting/alertingrule/action', AlertingRecordingRuleAction = 'grafana/alerting/recordingrule/action', + AlertingRuleQueryEditor = 'grafana/alerting/alertingrule/queryeditor', CommandPalette = 'grafana/commandpalette/action', DashboardPanelMenu = 'grafana/dashboard/panel/menu', DataSourceConfig = 'grafana/datasources/config', diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json index e625f825ef6..d13025d3115 100644 --- a/packages/grafana-data/tsconfig.json +++ b/packages/grafana-data/tsconfig.json @@ -1,5 +1,7 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "jsx": "react-jsx", "declarationDir": "./dist/types", "emitDeclarationOnly": true, @@ -7,7 +9,6 @@ "rootDirs": ["."] }, "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", "include": [ "src/**/*.ts*", "typings/jest", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index bc25b3d1bb3..b5bf9b4b27c 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -49,7 +49,6 @@ "rollup-plugin-node-externals": "^8.0.0" }, "dependencies": { - "@grafana/tsconfig": "^2.0.0", "semver": "^7.7.0", "tslib": "2.8.1", "typescript": "5.9.2" diff --git a/packages/grafana-e2e-selectors/tsconfig.json b/packages/grafana-e2e-selectors/tsconfig.json index d4afb7d5045..41f1fb8efb0 100644 --- a/packages/grafana-e2e-selectors/tsconfig.json +++ b/packages/grafana-e2e-selectors/tsconfig.json @@ -1,11 +1,12 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."] }, "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", "include": ["src/**/*.ts"] } diff --git a/packages/grafana-eslint-rules/tsconfig.json b/packages/grafana-eslint-rules/tsconfig.json index 8ab4da6efbe..56a7f641d1f 100644 --- a/packages/grafana-eslint-rules/tsconfig.json +++ b/packages/grafana-eslint-rules/tsconfig.json @@ -1,10 +1,10 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "module": "Node16", "moduleResolution": "Node16", - "declaration": false, "allowJs": true, "outDir": "./dist" - }, - "extends": "@grafana/tsconfig" + } } diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 7132e2cd4cb..06914eb8915 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -59,7 +59,6 @@ "@babel/core": "7.28.0", "@babel/preset-env": "7.28.0", "@babel/preset-react": "7.27.1", - "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "16.0.1", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "^6.1.2", diff --git a/packages/grafana-flamegraph/tsconfig.json b/packages/grafana-flamegraph/tsconfig.json index a3f1c194ab8..0a1330e2114 100644 --- a/packages/grafana-flamegraph/tsconfig.json +++ b/packages/grafana-flamegraph/tsconfig.json @@ -1,5 +1,7 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "jsx": "react-jsx", "baseUrl": "./", "declarationDir": "./dist/types", @@ -8,6 +10,5 @@ "rootDirs": ["."] }, "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", "include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"] } diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index 036a63834e4..12680665aea 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -63,7 +63,6 @@ "react-i18next": "^15.0.0" }, "devDependencies": { - "@grafana/tsconfig": "^2.0.0", "@types/react": "18.3.18", "rollup": "^4.22.4", "rollup-plugin-copy": "3.5.0", diff --git a/packages/grafana-i18n/tsconfig.json b/packages/grafana-i18n/tsconfig.json index 3fa337c4f19..2c83128f0f1 100644 --- a/packages/grafana-i18n/tsconfig.json +++ b/packages/grafana-i18n/tsconfig.json @@ -1,5 +1,7 @@ { + "extends": "../../scripts/tsconfig.base.json", "compilerOptions": { + "declaration": true, "jsx": "react-jsx", "declarationDir": "./dist/types", "emitDeclarationOnly": true, @@ -7,6 +9,5 @@ "rootDirs": ["."] }, "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", "include": ["src/**/*.ts*"] } diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index aee2372aa14..bf02da0ddd6 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -20,7 +20,7 @@ "@emotion/css": "11.13.5", "@grafana/data": "12.2.0-pre", "@grafana/e2e-selectors": "12.2.0-pre", - "@grafana/plugin-ui": "0.10.7", + "@grafana/plugin-ui": "0.10.8", "@grafana/runtime": "12.2.0-pre", "@grafana/schema": "12.2.0-pre", "@grafana/ui": "12.2.0-pre", @@ -30,7 +30,6 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/tsconfig": "^2.0.0", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "^6.1.2", "@testing-library/react": "16.3.0", diff --git a/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/TraceToMetricsSettings.tsx b/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/TraceToMetricsSettings.tsx index 966c1703b54..fbb68ab9a51 100644 --- a/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/TraceToMetricsSettings.tsx +++ b/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/TraceToMetricsSettings.tsx @@ -162,7 +162,7 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) { ); + expect(screen.queryByRole('button', { name: 'Tooltip text' })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Child text' })).toBeInTheDocument(); + }); + + it('should prioritise the aria-label if it is present', () => { + setup( + + ); + expect(screen.queryByRole('button', { name: 'Child text' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Tooltip text' })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Aria label' })).toBeInTheDocument(); + }); }); diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index f9500fc196d..83b1c5825c0 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -18,28 +18,43 @@ export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'dest export type ButtonFill = 'solid' | 'outline' | 'text'; export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text']; -type CommonProps = { +type BaseProps = { size?: ComponentSize; variant?: ButtonVariant; fill?: ButtonFill; icon?: IconName | React.ReactElement; className?: string; - children?: React.ReactNode; fullWidth?: boolean; type?: string; - /** Tooltip content to display on hover */ tooltip?: PopoverContent; - /** Position of the tooltip */ tooltipPlacement?: TooltipPlacement; /** Position of the icon */ iconPlacement?: 'left' | 'right'; }; +// either aria-label or tooltip is required for buttons without children +type NoChildrenAriaLabel = BaseProps & { + children?: never; + 'aria-label': string; +}; +type NoChildrenTooltip = BaseProps & { + children?: never; + tooltip: PopoverContent; + tooltipPlacement?: TooltipPlacement; +}; + +type BasePropsWithChildren = BaseProps & { + children: React.ReactNode; +}; + +type CommonProps = BasePropsWithChildren | NoChildrenTooltip | NoChildrenAriaLabel; + export type ButtonProps = CommonProps & ButtonHTMLAttributes; export const Button = React.forwardRef( ( { + 'aria-label': ariaLabel, variant = 'primary', size = 'md', fill = 'solid', @@ -92,6 +107,7 @@ export const Button = React.forwardRef( aria-disabled={hasTooltip && disabled} disabled={!hasTooltip && disabled} ref={tooltip ? undefined : ref} + aria-label={ariaLabel ?? (!children && typeof tooltip === 'string' ? tooltip : undefined)} > {iconPlacement === 'left' && iconComponent} {children && {children}} @@ -113,13 +129,12 @@ export const Button = React.forwardRef( Button.displayName = 'Button'; -export type ButtonLinkProps = CommonProps & - ButtonHTMLAttributes & - AnchorHTMLAttributes; +export type ButtonLinkProps = ButtonProps & Omit, 'aria-label'>; export const LinkButton = React.forwardRef( ( { + 'aria-label': ariaLabel, variant = 'primary', size = 'md', fill = 'solid', @@ -164,6 +179,7 @@ export const LinkButton = React.forwardRef( tabIndex={disabled ? -1 : 0} aria-disabled={disabled} ref={tooltip ? undefined : ref} + aria-label={ariaLabel ?? (!children && typeof tooltip === 'string' ? tooltip : undefined)} > {children && {children}} diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx index 6e6ba1fd725..43aaff2bee2 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx @@ -14,7 +14,7 @@ import { Icon } from '../Icon/Icon'; export interface ButtonCascaderProps { options: CascaderOption[]; - children?: string; + children: string; icon?: IconName; disabled?: boolean; value?: string[]; @@ -24,7 +24,7 @@ export interface ButtonCascaderProps { onPopupVisibleChange?: (visible: boolean) => void; className?: string; variant?: ButtonProps['variant']; - buttonProps?: ButtonProps; + buttonProps?: Omit; hideDownIcon?: boolean; } diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx index 2e2391da18a..d78ee23ecc7 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx @@ -1,24 +1,48 @@ import { render, screen } from '@testing-library/react'; +import { TextLink } from '../Link/TextLink'; + import { CallToActionCard } from './CallToActionCard'; describe('CallToActionCard', () => { describe('rendering', () => { it('should render callToActionElement', () => { - render(Click me} />); + render( + + Click me + + } + /> + ); expect(screen.getByRole('link', { name: 'Click me' })).toBeInTheDocument(); }); it('should render message when provided', () => { render( - Click me} /> + + Click me + + } + /> ); expect(screen.getByText('Click button below')).toBeInTheDocument(); }); it('should render footer when provided', () => { render( - Click me} /> + + Click me + + } + /> ); expect(screen.getByText('footer content')).toBeInTheDocument(); }); @@ -28,7 +52,11 @@ describe('CallToActionCard', () => { Click me} + callToActionElement={ + + Click me + + } /> ); expect(screen.getByText('Click button below')).toBeInTheDocument(); diff --git a/packages/grafana-ui/src/components/Card/Card.story.tsx b/packages/grafana-ui/src/components/Card/Card.story.tsx index 34b25d96a05..df9f978c531 100644 --- a/packages/grafana-ui/src/components/Card/Card.story.tsx +++ b/packages/grafana-ui/src/components/Card/Card.story.tsx @@ -2,6 +2,7 @@ import { Meta, StoryFn } from '@storybook/react'; import { Button } from '../Button/Button'; import { IconButton } from '../IconButton/IconButton'; +import { TextLink } from '../Link/TextLink'; import { TagList } from '../Tags/TagList'; import { Card } from './Card'; @@ -74,9 +75,9 @@ export const MultipleMetadataWithCustomSeparator: StoryFn = (args) Test dashboard Grafana - + https://ops-us-east4.grafana.net/api/prom - + ); @@ -171,9 +172,9 @@ export const WithMediaElements: StoryFn = (args) => { Grafana - + https://ops-us-east4.grafana.net/api/prom - + ); @@ -190,9 +191,9 @@ export const ActionCards: StoryFn = (args) => { 1-ops-tools1-fallback Prometheus - + https://ops-us-east4.grafana.net/api/prom - + Prometheus Logo @@ -223,9 +224,9 @@ export const DisabledState: StoryFn = (args) => { 1-ops-tools1-fallback Grafana - + https://ops-us-east4.grafana.net/api/prom - + Grafana Logo @@ -269,9 +270,9 @@ export const Full: StoryFn = (args) => { {['Subtitle', 'Meta info 1', 'Meta info 2']} - + https://ops-us-east4.grafana.net/api/prom - + Prometheus Logo diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.tsx index 64c0489590a..588eb0af231 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.tsx @@ -19,10 +19,10 @@ const meta: Meta = { }, }; -interface StoryProps extends Partial { +type StoryProps = Partial & { inputText: string; buttonText: string; -} +}; export const ClipboardButton: StoryFn = (args) => { const shareUrl = 'https://grafana.com/d/abcDEF-34t'; diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx index dc8bf238ed8..a42871dcde0 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx @@ -10,14 +10,14 @@ import { Button, ButtonProps } from '../Button/Button'; import { Icon } from '../Icon/Icon'; import { InlineToast } from '../InlineToast/InlineToast'; -export interface Props extends ButtonProps { +export type Props = ButtonProps & { /** A function that returns text to be copied */ getText(): string; /** Callback when the text has been successfully copied */ onClipboardCopy?(copiedText: string): void; /** Callback when there was an error copying the text */ onClipboardError?(copiedText: string, error: unknown): void; -} +}; const SHOW_SUCCESS_DURATION = 2 * 1000; @@ -73,7 +73,6 @@ export function ClipboardButton({ onClick={copyTextCallback} icon={icon} variant={showCopySuccess ? 'success' : variant} - aria-label={showCopySuccess ? copiedText : undefined} {...buttonProps} className={cx(styles.button, showCopySuccess && styles.successButton, buttonProps.className)} ref={buttonRef} diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx index 44ea08e36ac..4338129f4a9 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx @@ -4,7 +4,7 @@ import { ButtonProps, Button } from '../Button/Button'; type DataLinkButtonProps = { link: LinkModel; - buttonProps?: ButtonProps; + buttonProps?: Omit; }; /** diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx index 0bf13180e1a..65566666c99 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx @@ -9,6 +9,7 @@ import { useStyles2, useTheme2 } from '../../../themes/ThemeContext'; import { getFocusStyles } from '../../../themes/mixins'; import { FilterInput } from '../../FilterInput/FilterInput'; import { Icon } from '../../Icon/Icon'; +import { TextLink } from '../../Link/TextLink'; import { WeekStart } from '../WeekStartPicker'; import { TimePickerFooter } from './TimePickerFooter'; @@ -234,13 +235,9 @@ const EmptyRecentList = memo(() => {
- + Read the documentation - + to find out more about how to enter custom time ranges.
@@ -371,7 +368,4 @@ const getEmptyListStyles = (theme: GrafanaTheme2) => ({ fontSize: '13px', }, }), - link: css({ - color: theme.colors.text.link, - }), }); diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx index 7646f67735c..edcf354ef6d 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx @@ -109,4 +109,10 @@ describe('RefIDMultiPicker', () => { expect(mockOnChange).toHaveBeenLastCalledWith(['A', 'B']); /* eslint-enable testing-library/prefer-user-event */ }); + + // in the scenario where a refID filter was saved, but is no longer valid, it should still show. + it('Should display a refID that does not exist in the selection', async () => { + multiSetup({ value: '/^(?:merge-A-B-C)$/' }); + expect(screen.getByText('merge-A-B-C')).toBeInTheDocument(); + }); }); diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx index 87175cbb057..30899db375a 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx @@ -129,7 +129,6 @@ export function RefIDMultiPicker({ value, data, onChange, placeholder }: MultiPr const currentValue = useMemo(() => { let extractedRefIds = new Set(); - if (value) { if (value.startsWith('/^')) { try { @@ -152,8 +151,10 @@ export function RefIDMultiPicker({ value, data, onChange, placeholder }: MultiPr } const newRefIds = [...extractedRefIds].map(toOption); + const recoveredRefIDs = + recoverMultiRefIdMissing(newRefIds, priorSelectionState.refIds, priorSelectionState.value) ?? []; - return recoverMultiRefIdMissing(newRefIds, priorSelectionState.refIds, priorSelectionState.value); + return recoveredRefIDs.length > 0 ? recoveredRefIDs : newRefIds.length > 0 ? newRefIds : undefined; }, [value, listOfRefIds, priorSelectionState]); const onFilterChange = useCallback( diff --git a/packages/grafana-ui/src/components/Modal/getModalStyles.ts b/packages/grafana-ui/src/components/Modal/getModalStyles.ts index 9f0aa34f0ac..9847f8df418 100644 --- a/packages/grafana-ui/src/components/Modal/getModalStyles.ts +++ b/packages/grafana-ui/src/components/Modal/getModalStyles.ts @@ -25,6 +25,13 @@ export const getModalStyles = (theme: GrafanaTheme2) => { maxHeight: '80%', display: 'flex', flexDirection: 'column', + // Centre the modal vertically on smaller height screens + // this allows us to fill the full height for maximum usability + ['@media (max-height: 750px)']: { + maxHeight: '100%', + top: '50%', + transform: 'translateY(-50%)', + }, }), modalBackdrop: css({ position: 'fixed', diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx index 0bbe166ccff..e312691bc4c 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx @@ -11,6 +11,7 @@ import { Button } from '../Button/Button'; import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; import { Icon } from '../Icon/Icon'; import { Stack } from '../Layout/Stack/Stack'; +import { TextLink } from '../Link/TextLink'; import { Menu } from '../Menu/Menu'; import { PanelChromeProps } from './PanelChrome'; @@ -252,10 +253,9 @@ export const Examples = () => { {renderPanel('Panel with action link', { title: 'Panel with action link', actions: ( - + Error details - - + ), })} {renderPanel('Action and menu (should be rare)', { @@ -322,10 +322,9 @@ export const ExamplesHoverHeader = () => { title: 'With link in hover header', hoverHeader: true, actions: ( - + Error details - - + ), })} diff --git a/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterPopup.tsx b/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterPopup.tsx index 3d3103be741..d55da76386e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterPopup.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterPopup.tsx @@ -135,6 +135,7 @@ export const FilterPopup = ({ value={searchFilter} /> + + + + + ); +} diff --git a/public/app/features/alerting/unified/components/extensions/QuerylessAppExtensions.tsx b/public/app/features/alerting/unified/components/extensions/QuerylessAppExtensions.tsx new file mode 100644 index 00000000000..6aa929523d4 --- /dev/null +++ b/public/app/features/alerting/unified/components/extensions/QuerylessAppExtensions.tsx @@ -0,0 +1,45 @@ +import { first } from 'lodash'; + +import { PluginExtensionLink } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Dropdown, ToolbarButton } from '@grafana/ui'; + +import { AlertingRuleExtensionPointMenu } from './AlertingRuleExtensionPointMenu'; + +export type ExtensionDropdownProps = { + links: PluginExtensionLink[]; + setSelectedExtension: (extension: PluginExtensionLink) => void; + setIsModalOpen: (value: boolean) => void; + isModalOpen: boolean; +}; + +export function QuerylessAppsExtensions(props: ExtensionDropdownProps) { + const { links, setSelectedExtension, setIsModalOpen, isModalOpen } = props; + + if (links.length === 0) { + return undefined; + } + + const menu = ; + + if (links.length === 1) { + const link = first(links)!; + return ( + setSelectedExtension(link)}> + Go queryless + + ); + } + + return ( + + + Go queryless + + + ); +} diff --git a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/ReceiverMetadataBadge.tsx b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/ReceiverMetadataBadge.tsx index 2d24a310fc6..2599d1e6737 100644 --- a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/ReceiverMetadataBadge.tsx +++ b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/ReceiverMetadataBadge.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { Icon, LinkButton, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { ReceiverPluginMetadata } from './useReceiversMetadata'; @@ -25,7 +26,14 @@ export const ReceiverMetadataBadge = ({ metadata: { icon, title, externalUrl, wa {title} {externalUrl && ( - + )} ); diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx index 2e7e09e0a5e..65728cd922d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx @@ -27,6 +27,7 @@ import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { RuleFormValues } from '../../types/rule-form'; import { msToSingleUnitDuration } from '../../utils/time'; import { ExpressionStatusIndicator } from '../expressions/ExpressionStatusIndicator'; +import { AlertingRuleQueryExtensionPoint } from '../extensions/AlertingRuleQueryExtensionPoint'; import { QueryOptions } from './QueryOptions'; import { VizWrapper } from './VizWrapper'; @@ -169,6 +170,7 @@ export const QueryWrapper = ({ return ( + { setupMswServer(); setupDataSources(dataSources.default, dataSources.am); +// Setup plugin extensions hook to prevent setPluginLinksHook errors +setPluginLinksHook(() => ({ links: [], isLoading: false })); + describe('Can create a new grafana managed alert using simplified routing', () => { beforeEach(() => { window.localStorage.clear(); diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx index 7a3f89a2526..7c3dd139d8e 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx @@ -301,7 +301,14 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi return ( - {returnToHref && } + {returnToHref && ( + + )} {ruleOrigin && } {name} diff --git a/public/app/features/alerting/unified/components/rules/ActionButton.tsx b/public/app/features/alerting/unified/components/rules/ActionButton.tsx index 8ccf3338f00..e4ad6b9e0d5 100644 --- a/public/app/features/alerting/unified/components/rules/ActionButton.tsx +++ b/public/app/features/alerting/unified/components/rules/ActionButton.tsx @@ -5,9 +5,13 @@ import { Button, ButtonProps, useStyles2 } from '@grafana/ui'; type Props = Omit; -export const ActionButton = ({ className, ...restProps }: Props) => { +export const ActionButton = ({ children, className, ...restProps }: Props) => { const styles = useStyles2(getStyle); - return + ); }; export const getStyle = (theme: GrafanaTheme2) => ({ diff --git a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx index 5a3dbcc9870..0d0a67395ed 100644 --- a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx +++ b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx @@ -27,32 +27,32 @@ export const ActionIcon = ({ }: Props) => { const ariaLabel = typeof tooltip === 'string' ? tooltip : undefined; - return ( - - {to ? ( - - ) : ( - + ); @@ -136,13 +130,7 @@ export function BrowseActions({ folderDTO }: Props) { return ( <> - {moveIsInvalid ? ( - - {moveButton} - - ) : ( - moveButton - )} + {moveButton} {onRemove && ( ))} @@ -58,9 +68,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ border: 'none', padding: 0, cursor: 'pointer', - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', + display: 'flex', + gap: theme.spacing(1), + alignItems: 'center', }), expandButton: css({ display: 'flex', diff --git a/public/app/features/scopes/selector/ScopesSelector.tsx b/public/app/features/scopes/selector/ScopesSelector.tsx index 5b0fed1bfc1..5a40106070e 100644 --- a/public/app/features/scopes/selector/ScopesSelector.tsx +++ b/public/app/features/scopes/selector/ScopesSelector.tsx @@ -105,8 +105,8 @@ export const ScopesSelector = () => { scopeNodes={nodes} selectScope={selectScope} deselectScope={deselectScope} - onRecentScopesSelect={(scopeIds: string[]) => { - scopesSelectorService.changeScopes(scopeIds); + onRecentScopesSelect={(scopeIds: string[], parentNodeId?: string) => { + scopesSelectorService.changeScopes(scopeIds, parentNodeId); scopesSelectorService.closeAndReset(); }} /> diff --git a/public/app/features/scopes/selector/ScopesSelectorService.test.ts b/public/app/features/scopes/selector/ScopesSelectorService.test.ts index bb9ab1c40c4..f1e7265e735 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.test.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.test.ts @@ -4,6 +4,7 @@ import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesDashboardsService } from '../dashboards/ScopesDashboardsService'; import { RECENT_SCOPES_KEY, ScopesSelectorService } from './ScopesSelectorService'; +import { RecentScope } from './types'; describe('ScopesSelectorService', () => { let service: ScopesSelectorService; @@ -42,6 +43,7 @@ describe('ScopesSelectorService', () => { }; let storeValue: Record = {}; + let store: Store; beforeEach(() => { apiClient = { @@ -63,17 +65,23 @@ describe('ScopesSelectorService', () => { } as unknown as jest.Mocked; storeValue = {}; - const store = { + store = { get(key: string) { return storeValue[key]; }, - set(key: string, value: string) { storeValue[key] = value; }, - }; + subscribe: jest.fn(), + notifySubscribers: jest.fn(), + getBool: jest.fn(), + getObject: jest.fn(), + setObject: jest.fn(), + exists: jest.fn(), + delete: jest.fn(), + } as unknown as Store; - service = new ScopesSelectorService(apiClient, dashboardsService, store as Store); + service = new ScopesSelectorService(apiClient, dashboardsService, store); }); describe('updateNode', () => { @@ -125,6 +133,10 @@ describe('ScopesSelectorService', () => { await service.deselectScope('test-scope-node'); expect(service.state.selectedScopes).toEqual([]); }); + + it('should set recent scopes', async () => { + await service.selectScope('test-scope-node'); + }); }); describe('changeScopes', () => { @@ -154,6 +166,16 @@ describe('ScopesSelectorService', () => { sub.unsubscribe(); }); + + it('should set parent node for recent scopes', async () => { + // Load mock node + await service.updateNode('', true, ''); + + await service.changeScopes(['test-scope'], 'test-scope-node'); + expect(service.state.appliedScopes).toEqual([{ scopeId: 'test-scope', parentNodeId: 'test-scope-node' }]); + expect(service.state.nodes).toEqual({ 'test-scope-node': mockNode }); + expect(storeValue[RECENT_SCOPES_KEY]).toEqual(JSON.stringify([[{ ...mockScope, parentNode: mockNode }]])); + }); }); describe('open', () => { @@ -253,4 +275,166 @@ describe('ScopesSelectorService', () => { expect(recentScopes).toEqual([]); }); }); + + describe('nodes from local storage', () => { + it('should return parent nodes from recent scopes', async () => { + // Set mock scopes with parent node + const mockScopeWithParentNode: RecentScope = { + metadata: { name: 'test-scope' }, + spec: { + title: 'test-scope', + type: 'scope', + category: 'scope', + description: 'test scope', + filters: [], + }, + parentNode: { + metadata: { name: 'test-scope-node' }, + spec: { + linkId: 'test-scope', + linkType: 'scope', + parentName: '', + nodeType: 'container', + title: 'test-scope-node', + }, + }, + }; + + // Set store value BEFORE creating the service + storeValue[RECENT_SCOPES_KEY] = JSON.stringify([[mockScopeWithParentNode]]); + + // Create service with the existing store (which now has the data) + service = new ScopesSelectorService(apiClient, dashboardsService, store as Store); + expect(service.state.nodes).toEqual({ 'test-scope-node': mockScopeWithParentNode.parentNode }); + }); + + it('should remove parent node if it is not valid', async () => { + // Mock with valid parent node + const mockScopeWithValidParentNode: RecentScope = { + metadata: { name: 'test-scope' }, + spec: { + title: 'test-scope', + type: 'scope', + category: 'scope', + description: 'test scope', + filters: [], + }, + parentNode: { + metadata: { name: 'test-scope-node' }, + spec: { + linkId: 'test-scope', + linkType: 'scope', + parentName: '', + nodeType: 'container', + title: 'test-scope-node', + }, + }, + }; + + // lacks name and spec + const mockScopeWithInvalidParentNode: RecentScope = { + metadata: { name: 'test-scope' }, + spec: { + title: 'test-scope', + type: 'scope', + category: 'scope', + description: 'test scope', + filters: [], + }, + parentNode: { + //@ts-expect-error + metadata: {}, + //@ts-expect-error + spec: {}, + }, + }; + + // Set store value BEFORE creating the service + storeValue[RECENT_SCOPES_KEY] = JSON.stringify([ + [mockScopeWithInvalidParentNode], + [mockScopeWithValidParentNode], + ]); + + // Create service with the existing store (which now has the data) + service = new ScopesSelectorService(apiClient, dashboardsService, store as Store); + expect(service.state.nodes).toEqual({ 'test-scope-node': mockScopeWithValidParentNode.parentNode }); + }); + + it('should validate parent nodes across all recent scope sets', async () => { + // Create multiple scope sets with various parent node validity + const mockScopeWithValidParentNode: RecentScope = { + metadata: { name: 'valid-scope' }, + spec: { + title: 'valid-scope', + type: 'scope', + category: 'scope', + description: 'valid scope', + filters: [], + }, + parentNode: { + metadata: { name: 'valid-parent-node' }, + spec: { + linkId: 'valid-scope', + linkType: 'scope', + parentName: '', + nodeType: 'container', + title: 'valid-parent-node', + }, + }, + }; + + const mockScopeWithInvalidParentNode1: RecentScope = { + metadata: { name: 'invalid-scope-1' }, + spec: { + title: 'invalid-scope-1', + type: 'scope', + category: 'scope', + description: 'invalid scope 1', + filters: [], + }, + parentNode: { + //@ts-expect-error + metadata: {}, + //@ts-expect-error + spec: {}, + }, + }; + + const mockScopeWithInvalidParentNode2: RecentScope = { + metadata: { name: 'invalid-scope-2' }, + spec: { + title: 'invalid-scope-2', + type: 'scope', + category: 'scope', + description: 'invalid scope 2', + filters: [], + }, + parentNode: { + metadata: { name: 'invalid-parent-node-2' }, // missing spec + //@ts-expect-error - intentionally invalid spec for testing + spec: {}, + }, + }; + + // Set store value with multiple scope sets - some with invalid parent nodes + storeValue[RECENT_SCOPES_KEY] = JSON.stringify([ + [mockScopeWithInvalidParentNode1], + [mockScopeWithValidParentNode], + [mockScopeWithInvalidParentNode2], + ]); + + // Create service with the existing store + service = new ScopesSelectorService(apiClient, dashboardsService, store as Store); + + // Should only include the valid parent node + expect(service.state.nodes).toEqual({ 'valid-parent-node': mockScopeWithValidParentNode.parentNode }); + + // Verify that the invalid parent nodes were removed from the stored data + const recentScopes = service.getRecentScopes(); + expect(recentScopes).toHaveLength(3); + expect(recentScopes[0][0].parentNode).toBeUndefined(); // invalid parent node should be removed + expect(recentScopes[1][0].parentNode).toEqual(mockScopeWithValidParentNode.parentNode); // valid parent node should remain + expect(recentScopes[2][0].parentNode).toBeUndefined(); // invalid parent node should be removed + }); + }); }); diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index b8daece424c..6c6493ca16d 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -1,4 +1,4 @@ -import { Scope, store as storeImpl } from '@grafana/data'; +import { Scope, ScopeNode, store as storeImpl } from '@grafana/data'; import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesServiceBase } from '../ScopesServiceBase'; @@ -13,7 +13,7 @@ import { modifyTreeNodeAtPath, treeNodeAtPath, } from './scopesTreeUtils'; -import { NodesMap, ScopesMap, SelectedScope, TreeNode } from './types'; +import { NodesMap, RecentScope, RecentScopeSchema, ScopeSchema, ScopesMap, SelectedScope, TreeNode } from './types'; export const RECENT_SCOPES_KEY = 'grafana.scopes.recent'; @@ -69,6 +69,10 @@ export class ScopesSelectorService extends ScopesServiceBase { - return this.applyScopes(scopeNames.map((id) => ({ scopeId: id, parentNodeId: parentNodeId }))); + return this.applyScopes(scopeNames.map((id) => ({ scopeId: id, parentNodeId }))); }; /** @@ -266,22 +270,37 @@ export class ScopesSelectorService extends ScopesServiceBase this.applyScopes([]); - private addRecentScopes = (scopes: Scope[]) => { + private addRecentScopes = (scopes: Scope[], parentNode?: ScopeNode) => { if (scopes.length === 0) { return; } + const newScopes: RecentScope[] = structuredClone(scopes); + // Set parent node for the first scope. We don't currently support multiple parent nodes being displayed, hence we only add for the first one + if (parentNode) { + newScopes[0].parentNode = parentNode; + } + const RECENT_SCOPES_MAX_LENGTH = 5; const recentScopes = this.getRecentScopes(); - recentScopes.unshift(scopes); + recentScopes.unshift(newScopes); this.store.set(RECENT_SCOPES_KEY, JSON.stringify(recentScopes.slice(0, RECENT_SCOPES_MAX_LENGTH - 1))); }; @@ -289,12 +308,12 @@ export class ScopesSelectorService extends ScopesServiceBase { + public getRecentScopes = (): RecentScope[][] => { const content: string | undefined = this.store.get(RECENT_SCOPES_KEY); const recentScopes = parseScopesFromLocalStorage(content); // Filter out the current selection from recent scopes to avoid duplicates - return recentScopes.filter((scopes: Scope[]) => { + return recentScopes.filter((scopes: RecentScope[]) => { if (scopes.length !== this.state.appliedScopes.length) { return true; } @@ -303,6 +322,20 @@ export class ScopesSelectorService extends ScopesServiceBase => { + const content: string | undefined = this.store.get(RECENT_SCOPES_KEY); + const recentScopes = parseScopesFromLocalStorage(content); + + // Load parent nodes for recent scopes + const parentNodes = Object.fromEntries( + recentScopes + .map((scopes) => [scopes[0]?.parentNode?.metadata?.name, scopes[0]?.parentNode]) + .filter(([key, parentNode]) => parentNode !== undefined && key !== undefined) + ); + + return parentNodes; + }; + /** * Opens the scopes selector drawer and loads the root nodes if they are not loaded yet. */ @@ -361,18 +394,14 @@ function isScopeLocalStorageV1(obj: unknown): obj is { scope: Scope } { } function isScopeObj(obj: unknown): obj is Scope { - return ( - typeof obj === 'object' && - obj !== null && - 'metadata' in obj && - typeof obj['metadata'] === 'object' && - obj['metadata'] !== null && - 'name' in obj['metadata'] && - 'spec' in obj - ); + return ScopeSchema.safeParse(obj).success; } -function parseScopesFromLocalStorage(content: string | undefined): Scope[][] { +function hasValidScopeParentNode(obj: unknown): obj is RecentScope { + return RecentScopeSchema.safeParse(obj).success; +} + +function parseScopesFromLocalStorage(content: string | undefined): RecentScope[][] { let recentScopes; try { recentScopes = JSON.parse(content || '[]'); @@ -391,5 +420,14 @@ function parseScopesFromLocalStorage(content: string | undefined): Scope[][] { return []; } + // Verify the structure of the parent node for all recent scope sets, and remove it if it is not valid + for (const scopeSet of recentScopes) { + if (scopeSet[0]?.parentNode) { + if (!hasValidScopeParentNode(scopeSet[0])) { + scopeSet[0].parentNode = undefined; + } + } + } + return recentScopes; } diff --git a/public/app/features/scopes/selector/ScopesTree.tsx b/public/app/features/scopes/selector/ScopesTree.tsx index e3fbc06e203..e21ec1bc808 100644 --- a/public/app/features/scopes/selector/ScopesTree.tsx +++ b/public/app/features/scopes/selector/ScopesTree.tsx @@ -23,7 +23,7 @@ export interface ScopesTreeProps { // Recent scopes are only shown at the root node recentScopes?: Scope[][]; - onRecentScopesSelect?: (scopeIds: string[]) => void; + onRecentScopesSelect?: (scopeIds: string[], parentNodeId?: string) => void; } export function ScopesTree({ diff --git a/public/app/features/scopes/selector/types.ts b/public/app/features/scopes/selector/types.ts index f30b7e04868..b73b9e66bd0 100644 --- a/public/app/features/scopes/selector/types.ts +++ b/public/app/features/scopes/selector/types.ts @@ -1,3 +1,5 @@ +import { z } from 'zod'; + import { Scope, ScopeNode } from '@grafana/data'; export type NodesMap = Record; @@ -16,3 +18,51 @@ export interface TreeNode { query: string; children?: Record; } + +export interface RecentScope extends Scope { + parentNode?: ScopeNode; +} + +// Zod schemas for type validation +export const ScopeSpecFilterSchema = z.object({ + key: z.string(), + value: z.string(), + values: z.array(z.string()).optional(), + operator: z.enum(['equals', 'not-equals', 'regex-match', 'regex-not-match', 'one-of', 'not-one-of']), +}); + +export const ScopeSpecSchema = z.object({ + title: z.string(), + type: z.string(), + description: z.string(), + category: z.string(), + filters: z.array(ScopeSpecFilterSchema), +}); + +export const ScopeSchema = z.object({ + metadata: z.object({ + name: z.string(), + }), + spec: ScopeSpecSchema, +}); + +export const ScopeNodeSpecSchema = z.object({ + nodeType: z.enum(['container', 'leaf']), + title: z.string(), + description: z.string().optional(), + disableMultiSelect: z.boolean().optional(), + linkId: z.string().optional(), + linkType: z.enum(['scope']).optional(), + parentName: z.string().optional(), +}); + +export const ScopeNodeSchema = z.object({ + metadata: z.object({ + name: z.string(), + }), + spec: ScopeNodeSpecSchema, +}); + +export const RecentScopeSchema = ScopeSchema.extend({ + parentNode: ScopeNodeSchema.optional(), +}); diff --git a/public/app/features/scopes/tests/selector.test.ts b/public/app/features/scopes/tests/selector.test.ts index 9f9b5e55596..c996caeaded 100644 --- a/public/app/features/scopes/tests/selector.test.ts +++ b/public/app/features/scopes/tests/selector.test.ts @@ -102,18 +102,18 @@ describe('Selector', () => { await openSelector(); expectRecentScopesSection(); await expandRecentScopes(); - expectRecentScope('Grafana'); - expectRecentScope('Grafana, Mimir'); - await selectRecentScope('Grafana'); + expectRecentScope('Grafana Applications'); + expectRecentScope('Grafana, Mimir Applications'); + await selectRecentScope('Grafana Applications'); expectScopesSelectorValue('Grafana'); await openSelector(); await expandRecentScopes(); - expectRecentScope('Grafana, Mimir'); - expectRecentScopeNotPresent('Grafana'); - expectRecentScopeNotPresent('Mimir'); - await selectRecentScope('Grafana, Mimir'); + expectRecentScope('Grafana, Mimir Applications'); + expectRecentScopeNotPresent('Grafana Applications'); + expectRecentScopeNotPresent('Mimir Applications'); + await selectRecentScope('Grafana, Mimir Applications'); expectScopesSelectorValue('Grafana + Mimir'); }); @@ -148,7 +148,7 @@ describe('Selector', () => { await openSelector(); expectRecentScopesSection(); await expandRecentScopes(); - expectRecentScope('Grafana, Mimir'); + expectRecentScope('Grafana, Mimir Applications'); }); it('should update recent scopes when selecting a different combination', async () => { @@ -169,8 +169,8 @@ describe('Selector', () => { // Check recent scopes are updated await openSelector(); await expandRecentScopes(); - expectRecentScope('Grafana, Mimir'); - expectRecentScope('Grafana'); + expectRecentScope('Grafana, Mimir Applications'); + expectRecentScope('Grafana Applications'); }); }); }); diff --git a/public/app/features/search/service/folders.ts b/public/app/features/search/service/folders.ts index 90ea27fb616..78c2fec708e 100644 --- a/public/app/features/search/service/folders.ts +++ b/public/app/features/search/service/folders.ts @@ -1,4 +1,3 @@ -import config from 'app/core/config'; import { listFolders } from 'app/features/browse-dashboards/api/services'; import { DashboardViewItem } from '../types'; @@ -11,11 +10,6 @@ export async function getFolderChildren( parentTitle?: string, dashboardsAtRoot = false ): Promise { - if (!config.featureToggles.nestedFolders) { - console.error('getFolderChildren requires nestedFolders feature toggle'); - return []; - } - if (!dashboardsAtRoot && !parentUid) { // We don't show dashboards at root in folder view yet - they're shown under a dummy 'general' // folder that FolderView adds in diff --git a/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx index cc657efa403..f6d2aa046e1 100644 --- a/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx @@ -99,7 +99,12 @@ export const FilterByValueFilterEditor = (props: Props) => { -