From f2ffdd39cae445a0c374091484b23e64211e30a9 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 3 Jul 2025 21:46:03 -0500 Subject: [PATCH 01/67] TableNG: Reduce how often we re-measure row height (#107604) --- .../src/components/Table/TableNG/TableNG.tsx | 8 ++++---- .../src/components/Table/TableNG/hooks.ts | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 3867e04a943..5a0a2fdc036 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -64,7 +64,7 @@ export function TableNG(props: TableNGProps) { const { cellHeight, data, - enablePagination, + enablePagination = false, enableSharedCrosshair = false, enableVirtualization, footerOptions, @@ -265,6 +265,9 @@ export function TableNG(props: TableNGProps) { cellRootRenderers: {}, }; + let lastRowIdx = -1; + let _rowHeight = 0; + f.forEach((field, i) => { const justifyContent = getTextAlign(field); const footerStyles = getFooterStyles(justifyContent); @@ -292,9 +295,6 @@ export function TableNG(props: TableNGProps) { const shouldOverflow = shouldTextOverflow(field); const shouldWrap = shouldTextWrap(field); - let lastRowIdx = -1; - let _rowHeight = 0; - // this fires first const renderCellRoot = (key: Key, props: CellRendererProps): ReactNode => { const rowIdx = props.row.__index; diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts index 3e0672b0b0e..1540d1e0383 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts @@ -140,7 +140,7 @@ export interface PaginatedRowsOptions { headerHeight: number; footerHeight: number; paginationHeight?: number; - enabled?: boolean; + enabled: boolean; } export interface PaginatedRowsResult { @@ -167,11 +167,17 @@ export function usePaginatedRows( // calculate average row height if row height is variable. const avgRowHeight = useMemo(() => { + if (!enabled) { + return 0; + } + if (typeof rowHeight === 'number') { return rowHeight; } - return rows.reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0); - }, [rows, rowHeight]); + + // we'll just measure 100 rows to estimate + return rows.slice(0, 100).reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0); + }, [rows, rowHeight, enabled]); // using dimensions of the panel, calculate pagination parameters const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd, smallPagination } = useMemo((): { @@ -325,6 +331,7 @@ export function useTypographyCtx(): TypographyCtx { const txtWidth = ctx.measureText(txt).width; const avgCharWidth = txtWidth / txt.length + letterSpacing; const { count } = varPreLine(ctx); + const calcRowHeight = (text: string, cellWidth: number, defaultHeight: number) => { if (text === '') { return defaultHeight; From a1f119635210ca8d4cd297a4ef5d356dec5f893f Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Fri, 4 Jul 2025 08:33:33 +0200 Subject: [PATCH 02/67] Test Data Source: Allow inputs to render `0` values (#107578) --- .../grafana-testdata-datasource/components/RandomWalkEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx index 0bdcc29f0c1..0f745337a7d 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx @@ -47,7 +47,7 @@ export const RandomWalkEditor = ({ onChange, query }: EditorProps) => { id={`randomWalk-${id}-${query.refId}`} min={min} step={step} - value={(query as any)[id as keyof TestDataDataQuery] || placeholder} + value={(query as any)[id as keyof TestDataDataQuery] ?? placeholder} placeholder={placeholder} onChange={onChange} /> From ea4f783ebea640bd9ec7003335d011c4ee2f543b Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Fri, 4 Jul 2025 09:26:16 +0200 Subject: [PATCH 03/67] Dependencies: Add resolution for outdated transitive deps (#107559) * Add resolution for jsonlint * Update get-document and gitconfiglocal --- package.json | 5 ++++- yarn.lock | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 5f5bf7d2fe3..3fae3bc2626 100644 --- a/package.json +++ b/package.json @@ -444,7 +444,10 @@ "@grafana/scenes/@grafana/e2e-selectors": "workspace:*", "@grafana/scenes-react/@grafana/e2e-selectors": "workspace:*", "swagger-ui-react/dompurify": "3.2.6", - "refractor/prismjs": "^1.27.0" + "refractor/prismjs": "^1.27.0", + "@mapbox/jsonlint-lines-primitives": "github:mapbox/jsonlint#commit=e31b7289baedf3e1000d7ae7edd42268212c9954", + "get-document": "github:webmodules/get-document#commit=a04ccb499d6e0433a368c3bb150b4899b698461f", + "gitconfiglocal": "2.1.0" }, "workspaces": { "packages": [ diff --git a/yarn.lock b/yarn.lock index 8dea57626c7..c2f96eec6b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4986,10 +4986,10 @@ __metadata: languageName: node linkType: hard -"@mapbox/jsonlint-lines-primitives@npm:~2.0.2": +"@mapbox/jsonlint-lines-primitives@github:mapbox/jsonlint#commit=e31b7289baedf3e1000d7ae7edd42268212c9954": version: 2.0.2 - resolution: "@mapbox/jsonlint-lines-primitives@npm:2.0.2" - checksum: 10/6d8e64d34d912ebf29fead0d1917c8d8ad86e96f69b6100a9764af8cba391609474cdce7f7e4a2d579ccea58a142d1454257b795403179e9133a09af13101068 + resolution: "@mapbox/jsonlint-lines-primitives@https://github.com/mapbox/jsonlint.git#commit=e31b7289baedf3e1000d7ae7edd42268212c9954" + checksum: 10/660bd93beac97f0f4bdcfbbded3282efaf2ab4debb99da55f7fb7b27250e0dfa7dd572a9040802fdc008c71d0bb5ff7e565a7426177fda6c3d17e9bf1598bbaf languageName: node linkType: hard @@ -17665,10 +17665,10 @@ __metadata: languageName: node linkType: hard -"get-document@npm:1": +"get-document@github:webmodules/get-document#commit=a04ccb499d6e0433a368c3bb150b4899b698461f": version: 1.0.0 - resolution: "get-document@npm:1.0.0" - checksum: 10/94312db44ea9d070cca8cc14272582d04c7638494d6b5c9c0d6f1d949ef2605b4102ee52f0ded11e571357aba45e9fcc98a5b0467d61e4943a66002167eb6e7a + resolution: "get-document@https://github.com/webmodules/get-document.git#commit=a04ccb499d6e0433a368c3bb150b4899b698461f" + checksum: 10/5ec51321f3a5257a08c3ea9ac1984d64302a34c786f5d5691ae1ee00aba189eedac63f401e0cf746b0c77fb84091a4d2a9b66ac23dc0e872cbac860e5e0c5245 languageName: node linkType: hard @@ -17879,12 +17879,12 @@ __metadata: languageName: node linkType: hard -"gitconfiglocal@npm:^1.0.0": - version: 1.0.0 - resolution: "gitconfiglocal@npm:1.0.0" +"gitconfiglocal@npm:2.1.0": + version: 2.1.0 + resolution: "gitconfiglocal@npm:2.1.0" dependencies: ini: "npm:^1.3.2" - checksum: 10/e6d2764c15bbab6d1d1000d1181bb907f6b3796bb04f63614dba571b18369e0ecb1beaf27ce8da5b24307ef607e3a5f262a67cb9575510b9446aac697d421beb + checksum: 10/4b4b44d992a6abf2900eec8cfe960dc36e0d3c2467d20ec69e0a0f13b6b7645b926daa004df42f94c34ad28a58529cf2522fa0bf261e4e7b95958fb451dcedda languageName: node linkType: hard From 15e1aa88551d925a80796a966e52f2b7a178203b Mon Sep 17 00:00:00 2001 From: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Date: Fri, 4 Jul 2025 09:22:10 +0100 Subject: [PATCH 04/67] SecretsManager: Introduce decrypt store (#107586) Co-authored-by: Matheus Macabu Co-authored-by: Michael Mandrus Co-authored-by: PoorlyDefinedBehaviour --- pkg/server/wire.go | 3 +- pkg/server/wire_gen.go | 2 +- pkg/storage/secret/metadata/decrypt_store.go | 123 ++++++ .../secret/metadata/decrypt_store_test.go | 402 ++++++++++++++++++ 4 files changed, 528 insertions(+), 2 deletions(-) create mode 100644 pkg/storage/secret/metadata/decrypt_store.go create mode 100644 pkg/storage/secret/metadata/decrypt_store_test.go diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 406497bcf9f..42cf7f6f499 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -424,9 +424,10 @@ var wireBasicSet = wire.NewSet( // Secrets Manager secretmetadata.ProvideSecureValueMetadataStorage, secretmetadata.ProvideKeeperMetadataStorage, - secretmetadata.ProvideOutboxQueue, + secretmetadata.ProvideDecryptStorage, secretencryption.ProvideDataKeyStorage, secretencryption.ProvideEncryptedValueStorage, + secretmetadata.ProvideOutboxQueue, secretmigrator.NewWithEngine, secretdatabase.ProvideDatabase, wire.Bind(new(secretcontracts.Database), new(*secretdatabase.Database)), diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 9fba20aa8c6..d52743dc2da 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -1428,7 +1428,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, featuremgmt.ProvideStaticEvaluator, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideOutboxQueue, encryption2.ProvideDataKeyStorage, encryption2.ProvideEncryptedValueStorage, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, encryption3.ProvideThirdPartyProviderMap, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptAllowList, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, featuremgmt.ProvideStaticEvaluator, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, encryption2.ProvideDataKeyStorage, encryption2.ProvideEncryptedValueStorage, metadata.ProvideOutboxQueue, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, encryption3.ProvideThirdPartyProviderMap, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptAllowList, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/storage/secret/metadata/decrypt_store.go b/pkg/storage/secret/metadata/decrypt_store.go new file mode 100644 index 00000000000..e907374748a --- /dev/null +++ b/pkg/storage/secret/metadata/decrypt_store.go @@ -0,0 +1,123 @@ +package metadata + +import ( + "context" + "fmt" + "strconv" + "time" + + claims "github.com/grafana/authlib/types" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + + "github.com/grafana/grafana-app-sdk/logging" + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/storage/secret/metadata/metrics" +) + +// TODO: this should be a "decrypt" service rather, so that other services can wire and call it. +func ProvideDecryptStorage( + features featuremgmt.FeatureToggles, + tracer trace.Tracer, + keeperService contracts.KeeperService, + keeperMetadataStorage contracts.KeeperMetadataStorage, + secureValueMetadataStorage contracts.SecureValueMetadataStorage, + decryptAuthorizer contracts.DecryptAuthorizer, + reg prometheus.Registerer, +) (contracts.DecryptStorage, error) { + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || + !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { + return &decryptStorage{}, nil + } + + if decryptAuthorizer == nil { + return nil, fmt.Errorf("a decrypt authorizer is required") + } + + return &decryptStorage{ + tracer: tracer, + keeperMetadataStorage: keeperMetadataStorage, + keeperService: keeperService, + secureValueMetadataStorage: secureValueMetadataStorage, + decryptAuthorizer: decryptAuthorizer, + metrics: metrics.NewStorageMetrics(reg), + }, nil +} + +// decryptStorage is the actual implementation of the decrypt storage. +type decryptStorage struct { + tracer trace.Tracer + keeperMetadataStorage contracts.KeeperMetadataStorage + keeperService contracts.KeeperService + secureValueMetadataStorage contracts.SecureValueMetadataStorage + decryptAuthorizer contracts.DecryptAuthorizer + metrics *metrics.StorageMetrics +} + +// Decrypt decrypts a secure value from the keeper. +func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace, name string) (_ secretv0alpha1.ExposedSecureValue, decryptErr error) { + ctx, span := s.tracer.Start(ctx, "DecryptStorage.Decrypt", trace.WithAttributes( + attribute.String("namespace", namespace.String()), + attribute.String("name", name), + )) + defer span.End() + + var decrypterIdentity string + start := time.Now() + // TEMPORARY: While we evaluate all of our auditing needs, provide one for decrypt operations. + defer func() { + span.SetAttributes(attribute.String("decrypter.identity", decrypterIdentity)) + + if decryptErr == nil { + logging.FromContext(ctx).Info("Audit log:", "operation", "decrypt_secret_success", "namespace", namespace, "secret_name", name, "decrypter_identity", decrypterIdentity) + } else { + span.SetStatus(codes.Error, "Decrypt failed") + span.RecordError(decryptErr) + + logging.FromContext(ctx).Info("Audit log:", "operation", "decrypt_secret_error", "namespace", namespace, "secret_name", name, "decrypter_identity", decrypterIdentity, "error", decryptErr) + } + success := decryptErr == nil + s.metrics.DecryptDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.DecryptRequestCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + + // Basic authn check before reading a secure value metadata, it is here on purpose. + if _, ok := claims.AuthInfoFrom(ctx); !ok { + return "", contracts.ErrDecryptNotAuthorized + } + + // The auth token will not necessarily have the permission to read the secure value metadata, + // but we still need to do it to inspect the `decrypters` field, hence the actual `authorize` + // function call happens after this. + sv, err := s.secureValueMetadataStorage.ReadForDecrypt(ctx, namespace, name) + if err != nil { + return "", contracts.ErrDecryptNotFound + } + + decrypterIdentity, authorized := s.decryptAuthorizer.Authorize(ctx, name, sv.Decrypters) + if !authorized { + return "", contracts.ErrDecryptNotAuthorized + } + + keeperConfig, err := s.keeperMetadataStorage.GetKeeperConfig(ctx, namespace.String(), sv.Keeper, contracts.ReadOpts{}) + if err != nil { + return "", contracts.ErrDecryptFailed + } + + keeper, err := s.keeperService.KeeperForConfig(keeperConfig) + if err != nil { + return "", contracts.ErrDecryptFailed + } + + exposedValue, err := keeper.Expose(ctx, keeperConfig, namespace.String(), contracts.ExternalID(sv.ExternalID)) + if err != nil { + return "", contracts.ErrDecryptFailed + } + + return exposedValue, nil +} diff --git a/pkg/storage/secret/metadata/decrypt_store_test.go b/pkg/storage/secret/metadata/decrypt_store_test.go new file mode 100644 index 00000000000..69a178c01cf --- /dev/null +++ b/pkg/storage/secret/metadata/decrypt_store_test.go @@ -0,0 +1,402 @@ +package metadata + +import ( + "context" + "testing" + + "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/types" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt" + "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" + encryptionmanager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/secret/database" + encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption" + "github.com/grafana/grafana/pkg/storage/secret/migrator" +) + +func TestIntegrationDecrypt(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + t.Parallel() + + t.Run("when no auth info is present, it returns an error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + decryptSvc, _, _, _ := setupDecryptTestService(t, nil) + + exposed, err := decryptSvc.Decrypt(ctx, "default", "name") + require.Error(t, err) + require.Empty(t, exposed) + }) + + t.Run("when secure value cannot be found, it returns an error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // Create auth context with proper permissions + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/group1:decrypt"}, "svc", types.TypeUser) + + decryptSvc, _, _, _ := setupDecryptTestService(t, map[string]struct{}{"group1": {}}) + + exposed, err := decryptSvc.Decrypt(authCtx, "default", "non-existent-value") + require.ErrorIs(t, err, contracts.ErrDecryptNotFound) + require.Empty(t, exposed) + }) + + t.Run("when auth info is not in allowlist, it returns an unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svName := "sv-test" + svcIdentity := "svc" + + // Create auth context with identity that is not in allowlist + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/" + svName + ":decrypt"}, svcIdentity, types.TypeUser) + + // Create an allowlist that doesn't include the permission + allowList := map[string]struct{}{"allowed-group": {}} + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList) + + // Create a secure value that is not in the allowlist + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = svName + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", svName) + require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized) + require.Empty(t, exposed) + }) + + t.Run("when happy path with valid auth and permissions, it returns decrypted value", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svcIdentity := "svc" + + // Create auth context with proper permissions that match the decrypters + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues:decrypt"}, svcIdentity, types.TypeUser) + + // Include the group in allowlist + allowList := map[string]struct{}{svcIdentity: {}} + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList) + + // Create a secure value that is in the allowlist + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = "sv-test" + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test") + require.NoError(t, err) + require.NotEmpty(t, exposed) + require.Equal(t, "value", exposed.DangerouslyExposeAndConsumeValue()) + }) + + t.Run("with permissions for a specific secure value but trying to decrypt another one, it returns unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svName := "sv-test" + svcIdentity := "svc" + + // Create auth context with proper permissions that match the decrypters + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/sv-test2:decrypt"}, svcIdentity, types.TypeUser) + + // Include the group in allowlist + allowList := map[string]struct{}{svcIdentity: {}} + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList) + + // Create a secure value that is in the allowlist + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = svName + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", svName) + require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized) + require.Empty(t, exposed) + }) + + t.Run("when permission format is malformed (no verb), it returns unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svcIdentity := "svc" + + // Create auth context with malformed permission (no verb) + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues"}, svcIdentity, types.TypeUser) + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}}) + + // Create a secure value + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = "sv-test" + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test") + require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized) + require.Empty(t, exposed) + }) + + t.Run("when permission verb is not 'decrypt', it returns unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svName := "sv-test" + svcIdentity := "svc" + + // Create auth context with wrong verb + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/" + svName + ":read"}, svcIdentity, types.TypeUser) + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}}) + + // Create a secure value + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = svName + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", svName) + require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized) + require.Empty(t, exposed) + }) + + t.Run("when permission has incorrect number of parts, it returns unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svcIdentity := "svc" + + // Create auth context with incorrect number of parts + authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/:decrypt"}, svcIdentity, types.TypeUser) + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}}) + + // Create a secure value + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = "sv-test" + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test") + require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized) + require.Empty(t, exposed) + }) + + t.Run("when permission has incorrect group or resource, it returns unauthorized error", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + svName := "sv-test" + svcIdentity := "svc" + + // Create auth context with incorrect group + authCtx := createAuthContext(ctx, "default", []string{"wrong.group/securevalues/" + svName + ":decrypt"}, svcIdentity, types.TypeUser) + + // Setup service + decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}}) + + // Create a secure value + spec := secretv0alpha1.SecureValueSpec{ + Description: "description", + Decrypters: []string{svcIdentity}, + Value: secretv0alpha1.NewExposedSecureValue("value"), + } + sv := &secretv0alpha1.SecureValue{Spec: spec} + sv.Name = svName + sv.Namespace = "default" + + newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid") + + exposed, err := decryptSvc.Decrypt(authCtx, "default", svName) + require.Error(t, err) + require.Equal(t, err.Error(), "not authorized") + require.Empty(t, exposed) + }) + + // TODO: add more tests for keeper failure scenarios, lets see how the async work will change this though. +} + +func setupDecryptTestService(t *testing.T, allowList map[string]struct{}) (*decryptStorage, contracts.SecureValueMetadataStorage, *secretkeeper.OSSKeeperService, contracts.KeeperMetadataStorage) { + t.Helper() + + // Initialize infra dependencies + cfg := &setting.Cfg{ + SecretsManagement: setting.SecretsManagerSettings{ + SecretKey: "sdDkslslld", + EncryptionProvider: "secretKey.v1", + }, + } + + features := featuremgmt.WithFeatures( + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagSecretsManagementAppPlatform, + ) + + db := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) + tracer := noop.NewTracerProvider().Tracer("test") + database := database.ProvideDatabase(db, tracer) + + // Initialize encryption manager and storage + dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil) + require.NoError(t, err) + + encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features) + require.NoError(t, err) + + encryptionManager, err := encryptionmanager.ProvideEncryptionManager( + tracer, + dataKeyStore, + cfg, + &usagestats.UsageStatsMock{}, + encryption.ProviderMap{}, + ) + require.NoError(t, err) + + // Initialize the keeper service + keeperService, err := secretkeeper.ProvideService(tracer, encValueStore, encryptionManager, nil) + require.NoError(t, err) + + keeperMetadataStorage, err := ProvideKeeperMetadataStorage(database, tracer, features, nil) + require.NoError(t, err) + + // Initialize the secure value storage + secureValueMetadataStorage, err := ProvideSecureValueMetadataStorage(database, tracer, features, nil) + require.NoError(t, err) + + decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, allowList) + + // Initialize the decrypt storage + decryptSvc, err := ProvideDecryptStorage(features, tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil) + require.NoError(t, err) + + return decryptSvc.(*decryptStorage), secureValueMetadataStorage, keeperService, keeperMetadataStorage +} + +func createAuthContext(ctx context.Context, namespace string, permissions []string, svc string, identityType types.IdentityType) context.Context { + requester := &identity.StaticRequester{ + Type: identityType, + Namespace: namespace, + AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{ + Rest: authn.AccessTokenClaims{ + Permissions: permissions, + ServiceIdentity: svc, + }, + }, + } + + if identityType == types.TypeUser { + requester.UserID = 1 + } + + return types.WithAuthInfo(ctx, requester) +} + +// This helper will also delete the secureValue from the db when the test is done. +func newTestSecureValue(ctx context.Context, t *testing.T, db contracts.SecureValueMetadataStorage, keeperService *secretkeeper.OSSKeeperService, keeperMetadataStorage contracts.KeeperMetadataStorage, sv *secretv0alpha1.SecureValue, actorUID string) { + t.Helper() + + _, err := db.Create(ctx, sv, actorUID) + require.NoError(t, err) + + require.NoError(t, err) + + // Since creating secrets is async, store the secret in the keeper synchronously to make testing easier + cfg, err := keeperMetadataStorage.GetKeeperConfig(ctx, sv.Namespace, sv.Spec.Keeper, contracts.ReadOpts{}) + require.NoError(t, err) + + keeper, err := keeperService.KeeperForConfig(cfg) + require.NoError(t, err) + + externalID, err := keeper.Store(ctx, cfg, sv.Namespace, sv.Spec.Value.DangerouslyExposeAndConsumeValue()) + require.NoError(t, err) + + // Set external id for the secure value + err = db.SetExternalID(ctx, xkube.Namespace(sv.Namespace), sv.Name, externalID) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, keeper.Delete(ctx, cfg, sv.Namespace, externalID)) + require.NoError(t, db.Delete(ctx, xkube.Namespace(sv.Namespace), sv.Name)) + }) +} From c7374b691027402c68ae14cff30ab46d4f89dc3d Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro Date: Fri, 4 Jul 2025 10:52:43 +0200 Subject: [PATCH 05/67] Alerting: Add Synthetic and IRM link cards (#107538) --- pkg/services/user/model.go | 2 + .../core/components/Branding/CloudBadge.tsx | 7 + .../Branding/CloudEnterpriseBadge.tsx | 31 +--- .../core/components/Branding/OrangeBadge.tsx | 31 ++++ .../features/alerting/unified/home/AdCard.tsx | 146 ++++++++++++++++++ .../features/alerting/unified/home/Home.tsx | 8 + .../alerting/unified/home/IRMCard.tsx | 28 ++++ .../unified/home/SyntheticMonitoringCard.tsx | 38 +++++ public/img/irm_logo.svg | 45 ++++++ public/img/synthetic_monitoring_logo.svg | 10 ++ public/locales/en-US/grafana.json | 19 ++- 11 files changed, 336 insertions(+), 29 deletions(-) create mode 100644 public/app/core/components/Branding/CloudBadge.tsx create mode 100644 public/app/core/components/Branding/OrangeBadge.tsx create mode 100644 public/app/features/alerting/unified/home/AdCard.tsx create mode 100644 public/app/features/alerting/unified/home/IRMCard.tsx create mode 100644 public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx create mode 100644 public/img/irm_logo.svg create mode 100644 public/img/synthetic_monitoring_logo.svg diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index b60bd4ac306..2d88ee79fa8 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -16,6 +16,8 @@ const ( HelpFlagGettingStartedPanelDismissed HelpFlags1 = 1 << iota HelpFlagDashboardHelp1 HelpFlagEnterpriseAuth1 + HelpFlagSyntheticMonitoring1 + HelpFlagIRM1 ) type UpdateEmailActionType string diff --git a/public/app/core/components/Branding/CloudBadge.tsx b/public/app/core/components/Branding/CloudBadge.tsx new file mode 100644 index 00000000000..e98ac79b913 --- /dev/null +++ b/public/app/core/components/Branding/CloudBadge.tsx @@ -0,0 +1,7 @@ +import { t } from '@grafana/i18n'; + +import { OrangeBadge } from './OrangeBadge'; + +export function CloudBadge() { + return ; +} diff --git a/public/app/core/components/Branding/CloudEnterpriseBadge.tsx b/public/app/core/components/Branding/CloudEnterpriseBadge.tsx index a904417053c..ce95d147581 100644 --- a/public/app/core/components/Branding/CloudEnterpriseBadge.tsx +++ b/public/app/core/components/Branding/CloudEnterpriseBadge.tsx @@ -1,32 +1,7 @@ -import { css } from '@emotion/css'; +import { t } from '@grafana/i18n'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; -import { Icon, useStyles2 } from '@grafana/ui'; +import { OrangeBadge } from './OrangeBadge'; export function CloudEnterpriseBadge() { - const styles = useStyles2(getStyles); - return ( -
- - Cloud & Enterprise -
- ); + return ; } - -const getStyles = (theme: GrafanaTheme2) => { - return { - wrapper: css({ - display: 'inline-flex', - padding: theme.spacing(0.5, 1), - borderRadius: theme.shape.radius.pill, - background: theme.colors.gradients.brandHorizontal, - color: theme.colors.primary.contrastText, - fontWeight: theme.typography.fontWeightMedium, - gap: theme.spacing(0.5), - fontSize: theme.typography.bodySmall.fontSize, - lineHeight: theme.typography.bodySmall.lineHeight, - alignItems: 'center', - }), - }; -}; diff --git a/public/app/core/components/Branding/OrangeBadge.tsx b/public/app/core/components/Branding/OrangeBadge.tsx new file mode 100644 index 00000000000..be9d080df79 --- /dev/null +++ b/public/app/core/components/Branding/OrangeBadge.tsx @@ -0,0 +1,31 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + +export function OrangeBadge({ text }: { text: string }) { + const styles = useStyles2(getStyles); + return ( +
+ + {text} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + wrapper: css({ + display: 'inline-flex', + padding: theme.spacing(0.5, 1), + borderRadius: theme.shape.radius.pill, + background: theme.colors.gradients.brandHorizontal, + color: theme.colors.primary.contrastText, + fontWeight: theme.typography.fontWeightMedium, + gap: theme.spacing(0.5), + fontSize: theme.typography.bodySmall.fontSize, + lineHeight: theme.typography.bodySmall.lineHeight, + alignItems: 'center', + }), + }; +}; diff --git a/public/app/features/alerting/unified/home/AdCard.tsx b/public/app/features/alerting/unified/home/AdCard.tsx new file mode 100644 index 00000000000..01577b83150 --- /dev/null +++ b/public/app/features/alerting/unified/home/AdCard.tsx @@ -0,0 +1,146 @@ +import { css } from '@emotion/css'; +import { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Button, Divider, Icon, IconButton, useStyles2 } from '@grafana/ui'; +import { CloudBadge } from 'app/core/components/Branding/CloudBadge'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { contextSrv } from 'app/core/services/context_srv'; +import { isOpenSourceBuildOrUnlicenced } from 'app/features/admin/EnterpriseAuthFeaturesCard'; + +type AdCardProps = { + title: string; + description: string; + href: string; + logoUrl: string; + items: string[]; + helpFlag: number; +}; + +export default function AdCard({ title, description, href, logoUrl, items, helpFlag }: AdCardProps) { + const styles = useStyles2(getAddCardStyles); + + const helpFlags = contextSrv.user.helpFlags1; + const [isDismissed, setDismissed] = useState(Boolean(helpFlags & helpFlag)); + + const onDismiss = () => { + backendSrv.put(`/api/user/helpflags/${helpFlag}`, undefined, { showSuccessAlert: false }).then((res) => { + contextSrv.user.helpFlags1 = res.helpFlags1; + setDismissed(true); + }); + }; + + if (isDismissed || !isOpenSourceBuildOrUnlicenced()) { + return null; + } + + return ( +
+
+ + +
+
+ {title.concat(' +
+

{title}

+

{description}

+
+
+ +
+ {items.map((item) => ( +
+ + {item} +
+ ))} +
+ + +
+ ); +} + +const getAddCardStyles = (theme: GrafanaTheme2) => ({ + logo: css({ + objectFit: 'contain', + width: '47px', + height: '47px', + }), + + header: css({ + display: 'flex', + alignItems: 'flex-start', + gap: theme.spacing(2), + paddingTop: theme.spacing(2), + height: theme.spacing(8), + }), + + contentColumn: css({ + flex: 1, + }), + + title: css({ + marginBottom: theme.spacing(1), + fontSize: theme.typography.h4.fontSize, + fontWeight: theme.typography.h4.fontWeight, + color: theme.colors.text.primary, + }), + + description: css({ + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + lineHeight: theme.typography.bodySmall.lineHeight, + }), + + itemsList: css({ + display: 'grid', + gridTemplateColumns: '1fr', + gap: theme.spacing(0.5), + [theme.breakpoints.up('xl')]: { + gridTemplateColumns: '1fr 1fr', + gap: theme.spacing(1), + }, + }), + + listItem: css({ + display: 'flex', + alignItems: 'flex-start', + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + lineHeight: theme.typography.bodySmall.lineHeight, + marginBottom: theme.spacing(0.5), + }), + + icon: css({ + marginRight: theme.spacing(1), + color: theme.colors.success.main, + }), + + button: css({ + padding: `0 ${theme.spacing(2)}`, + }), + + buttonIcon: css({ + marginLeft: theme.spacing(1), + }), + + cardBody: css({ + padding: `${theme.spacing(3)} ${theme.spacing(4)} ${theme.spacing(2.25)} ${theme.spacing(4)}`, + backgroundColor: theme.colors.background.secondary, + borderRadius: theme.shape.radius.default, + border: `1px solid ${theme.colors.border.weak}`, + flex: 1, + }), + + preHeader: css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }), +}); diff --git a/public/app/features/alerting/unified/home/Home.tsx b/public/app/features/alerting/unified/home/Home.tsx index d62f6735531..449f956255c 100644 --- a/public/app/features/alerting/unified/home/Home.tsx +++ b/public/app/features/alerting/unified/home/Home.tsx @@ -8,8 +8,10 @@ import { isLocalDevEnv } from '../utils/misc'; import { withPageErrorBoundary } from '../withPageErrorBoundary'; import GettingStarted, { WelcomeHeader } from './GettingStarted'; +import IRMCard from './IRMCard'; import { getInsightsScenes, insightsIsAvailable } from './Insights'; import { PluginIntegrations } from './PluginIntegrations'; +import SyntheticMonitoringCard from './SyntheticMonitoringCard'; function Home() { const insightsEnabled = insightsIsAvailable() || isLocalDevEnv(); @@ -27,6 +29,12 @@ function Home() { + + + + + + {insightsEnabled && ( diff --git a/public/app/features/alerting/unified/home/IRMCard.tsx b/public/app/features/alerting/unified/home/IRMCard.tsx new file mode 100644 index 00000000000..ff22c94943d --- /dev/null +++ b/public/app/features/alerting/unified/home/IRMCard.tsx @@ -0,0 +1,28 @@ +import { t } from '@grafana/i18n'; +import irmSvg from 'img/irm_logo.svg'; + +import AdCard from './AdCard'; + +const LINK = 'https://grafana.com/auth/sign-up/create-user?redirectPath=irm&src=oss-grafana&cnt=alerting-irm'; +const HELP_FLAG_IRM = 0x0010; + +export default function IRMCard() { + return ( + + ); +} diff --git a/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx b/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx new file mode 100644 index 00000000000..c98b7066c46 --- /dev/null +++ b/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx @@ -0,0 +1,38 @@ +import { t } from '@grafana/i18n'; +import syntheticMonitoringSvg from 'img/synthetic_monitoring_logo.svg'; + +import AdCard from './AdCard'; + +const LINK = + 'https://grafana.com/auth/sign-up/create-user?redirectPath=synthetic-monitoring&src=oss-grafana&cnt=alerting-synthetic-monitoring'; +const HELP_FLAG_SYNTHETIC_MONITORING = 0x0008; + +export default function SyntheticMonitoringCard() { + return ( + + ); +} diff --git a/public/img/irm_logo.svg b/public/img/irm_logo.svg new file mode 100644 index 00000000000..7c25966a158 --- /dev/null +++ b/public/img/irm_logo.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/synthetic_monitoring_logo.svg b/public/img/synthetic_monitoring_logo.svg new file mode 100644 index 00000000000..3b5b9f49deb --- /dev/null +++ b/public/img/synthetic_monitoring_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8c65d4ed09f..629a553e9da 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Active timings" }, + "ad": { + "close": "Close", + "learn-more": "Learn more" + }, "add-button": { "add-more": "Add more" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "Unify on-call, alerting, and incident response with Grafana Cloud IRM.", + "irm-card-item-1": "Manage on-call schedules with your calendar or Terraform.", + "irm-card-item-2": "Respond to incidents via web, app, Slack, or other channels.", + "irm-card-item-3": "Pinpoint root causes with AI-powered Grafana SIFT.", + "irm-card-item-4": "Analyze past incidents to improve response and resilience.", + "irm-card-title": "Incident response and management", "label-get-started": "Get started", "label-insights": "Insights", + "synthetic-monitoring-card-description": "Monitor critical user flows, websites, and APIs externally, from global locations.", + "synthetic-monitoring-card-item-1": "Simulate end-to-end user journeys with browser checks.", + "synthetic-monitoring-card-item-2": "Run ping, DNS, HTTP/S, and TCP checks at every network layer.", + "synthetic-monitoring-card-item-3": "Use 20+ global probes or private probes behind your firewall.", + "synthetic-monitoring-card-item-4": "Track SLOs with built-in Prometheus-style alerts — right from the UI.", + "synthetic-monitoring-card-title": "Synthetic Monitoring", "title-alerting": "Alerting" }, "import-to-gma": { @@ -3894,7 +3910,8 @@ "close-button": { "tooltip": "Close" }, - "cloud-enterprise-feature-badge": "Cloud & Enterprise ", + "cloud-enterprise-feature-badge": "Cloud & Enterprise", + "cloud-feature-badge": "Cloud", "combobox": { "async": { "error": "An error occurred while loading options." From 713f6d1551dbbcfd7354eebc74178168052b01ed Mon Sep 17 00:00:00 2001 From: Misi Date: Fri, 4 Jul 2025 11:07:48 +0200 Subject: [PATCH 06/67] IAM: Move resource definitions to apps/iam step 1 (#107389) * wip * Use serviceaccount model from /apps/iam * revert version update * Add tembinding, userteam, other improvements * Change serviceaccounttoken spec * Revert the change of ServiceAccountToken * Revert the change of UserTeam * Clean up * Remove files that are not needed for now * Lint * Update sql query's integration tests * Fix tests * update openapi spec * Move LastSeenAt to the annotations * Updte openapi_snapshots * Change lastSeenAt annotation name --- apps/iam/kinds/manifest.cue | 13 +- apps/iam/kinds/serviceaccount.cue | 24 + apps/iam/kinds/team.cue | 24 + apps/iam/kinds/teambinding.cue | 24 + apps/iam/kinds/user.cue | 24 + .../iam/kinds/v0alpha1/resourcepermission.cue | 2 - apps/iam/kinds/v0alpha1/rolespec.cue | 1 - .../iam/kinds/v0alpha1/serviceaccountspec.cue | 6 + apps/iam/kinds/v0alpha1/teambindingspec.cue | 20 + apps/iam/kinds/v0alpha1/teamspec.cue | 6 + apps/iam/kinds/v0alpha1/userspec.cue | 13 + apps/iam/pkg/apis/iam/v0alpha1/extensions.go | 43 + .../iam/v0alpha1/serviceaccount_codec_gen.go | 28 + .../v0alpha1/serviceaccount_metadata_gen.go | 31 + .../iam/v0alpha1/serviceaccount_object_gen.go | 319 ++ .../iam/v0alpha1/serviceaccount_schema_gen.go | 34 + .../iam/v0alpha1/serviceaccount_spec_gen.go | 14 + .../iam/v0alpha1/serviceaccount_status_gen.go | 44 + .../pkg/apis/iam/v0alpha1/team_codec_gen.go | 28 + .../apis/iam/v0alpha1/team_metadata_gen.go | 31 + .../pkg/apis/iam/v0alpha1/team_object_gen.go | 319 ++ .../pkg/apis/iam/v0alpha1/team_schema_gen.go | 34 + .../pkg/apis/iam/v0alpha1/team_spec_gen.go | 14 + .../pkg/apis/iam/v0alpha1/team_status_gen.go | 44 + .../iam/v0alpha1/teambinding_codec_gen.go | 28 + .../iam/v0alpha1/teambinding_metadata_gen.go | 31 + .../iam/v0alpha1/teambinding_object_gen.go | 319 ++ .../iam/v0alpha1/teambinding_schema_gen.go | 34 + .../apis/iam/v0alpha1/teambinding_spec_gen.go | 49 + .../iam/v0alpha1/teambinding_status_gen.go | 44 + .../pkg/apis/iam/v0alpha1/user_codec_gen.go | 28 + .../apis/iam/v0alpha1/user_metadata_gen.go | 31 + .../pkg/apis/iam/v0alpha1/user_object_gen.go | 319 ++ .../pkg/apis/iam/v0alpha1/user_schema_gen.go | 34 + .../pkg/apis/iam/v0alpha1/user_spec_gen.go | 20 + .../pkg/apis/iam/v0alpha1/user_status_gen.go | 44 + .../pkg/apis/iam/v0alpha1/zz_openapi_gen.go | 987 ++++++ apps/iam/pkg/apis/iam_manifest.go | 50 + pkg/apimachinery/identity/context.go | 7 +- pkg/apis/iam/v0alpha1/register.go | 64 +- .../iam/v0alpha1/types_service_account.go | 19 + .../iam/v0alpha1/types_servier_account.go | 50 - pkg/apis/iam/v0alpha1/types_team.go | 51 - pkg/apis/iam/v0alpha1/types_user.go | 36 +- .../iam/v0alpha1/zz_generated.deepcopy.go | 310 -- pkg/apis/iam/v0alpha1/zz_generated.openapi.go | 487 --- ...enerated.openapi_violation_exceptions.list | 1 - pkg/registry/apis/iam/common/common.go | 17 +- .../apis/iam/legacy/display_query.sql | 3 +- .../mysql--display_query-display_ids.sql | 3 +- .../mysql--display_query-display_ids_uids.sql | 3 +- .../mysql--display_query-display_uids.sql | 3 +- .../mysql--users_query-users_page_1.sql | 3 +- .../mysql--users_query-users_page_2.sql | 3 +- .../testdata/mysql--users_query-users_uid.sql | 3 +- .../postgres--display_query-display_ids.sql | 3 +- ...stgres--display_query-display_ids_uids.sql | 3 +- .../postgres--display_query-display_uids.sql | 3 +- .../postgres--users_query-users_page_1.sql | 3 +- .../postgres--users_query-users_page_2.sql | 3 +- .../postgres--users_query-users_uid.sql | 3 +- .../sqlite--display_query-display_ids.sql | 3 +- ...sqlite--display_query-display_ids_uids.sql | 3 +- .../sqlite--display_query-display_uids.sql | 3 +- .../sqlite--users_query-users_page_1.sql | 3 +- .../sqlite--users_query-users_page_2.sql | 3 +- .../sqlite--users_query-users_uid.sql | 3 +- pkg/registry/apis/iam/legacy/user.go | 3 +- pkg/registry/apis/iam/legacy/users_query.sql | 3 +- pkg/registry/apis/iam/register.go | 17 +- .../apis/iam/serviceaccount/rest_token.go | 13 +- pkg/registry/apis/iam/serviceaccount/store.go | 15 +- pkg/registry/apis/iam/team/store.go | 20 +- pkg/registry/apis/iam/team/store_binding.go | 24 +- pkg/registry/apis/iam/user/rest_user_team.go | 14 +- pkg/registry/apis/iam/user/store.go | 30 +- pkg/tests/apis/iam/iam_test.go | 59 +- .../iam.grafana.app-v0alpha1.json | 2672 ++++++++++++++--- 78 files changed, 5655 insertions(+), 1472 deletions(-) create mode 100644 apps/iam/kinds/serviceaccount.cue create mode 100644 apps/iam/kinds/team.cue create mode 100644 apps/iam/kinds/teambinding.cue create mode 100644 apps/iam/kinds/user.cue create mode 100644 apps/iam/kinds/v0alpha1/serviceaccountspec.cue create mode 100644 apps/iam/kinds/v0alpha1/teambindingspec.cue create mode 100644 apps/iam/kinds/v0alpha1/teamspec.cue create mode 100644 apps/iam/kinds/v0alpha1/userspec.cue create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/extensions.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_codec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_metadata_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_schema_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_spec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_status_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_codec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_metadata_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_schema_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_spec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/team_status_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_codec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_metadata_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_schema_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/teambinding_status_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_codec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_metadata_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_schema_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_spec_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go create mode 100644 pkg/apis/iam/v0alpha1/types_service_account.go delete mode 100644 pkg/apis/iam/v0alpha1/types_servier_account.go diff --git a/apps/iam/kinds/manifest.cue b/apps/iam/kinds/manifest.cue index f2f4b519d7b..032261e9030 100644 --- a/apps/iam/kinds/manifest.cue +++ b/apps/iam/kinds/manifest.cue @@ -3,5 +3,16 @@ package kinds manifest: { appName: "iam" groupOverride: "iam.grafana.app" - kinds: [ globalrole, globalrolebinding, corerole, role, rolebinding, resourcepermission ] + kinds: [ + globalrole, + globalrolebinding, + corerole, + role, + rolebinding, + resourcepermission, + user, + team, + teambinding, + serviceaccount, + ] } diff --git a/apps/iam/kinds/serviceaccount.cue b/apps/iam/kinds/serviceaccount.cue new file mode 100644 index 00000000000..22fb1e9e9cf --- /dev/null +++ b/apps/iam/kinds/serviceaccount.cue @@ -0,0 +1,24 @@ +package kinds + +import ( + "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" +) + +serviceaccount: { + kind: "ServiceAccount" + pluralName: "ServiceAccounts" + current: "v0alpha1" + + codegen: { + ts: { enabled: false } + go: { enabled: true } + } + + versions: { + "v0alpha1": { + schema: { + spec: v0alpha1.ServiceAccountSpec + } + } + } +} diff --git a/apps/iam/kinds/team.cue b/apps/iam/kinds/team.cue new file mode 100644 index 00000000000..284d9e601ea --- /dev/null +++ b/apps/iam/kinds/team.cue @@ -0,0 +1,24 @@ +package kinds + +import ( + "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" +) + +team: { + kind: "Team" + pluralName: "Teams" + current: "v0alpha1" + + codegen: { + ts: { enabled: false } + go: { enabled: true } + } + + versions: { + "v0alpha1": { + schema: { + spec: v0alpha1.TeamSpec + } + } + } +} diff --git a/apps/iam/kinds/teambinding.cue b/apps/iam/kinds/teambinding.cue new file mode 100644 index 00000000000..3f0805b0307 --- /dev/null +++ b/apps/iam/kinds/teambinding.cue @@ -0,0 +1,24 @@ +package kinds + +import ( + "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" +) + +teambinding: { + kind: "TeamBinding" + pluralName: "TeamBindings" + current: "v0alpha1" + + codegen: { + ts: { enabled: false } + go: { enabled: true } + } + + versions: { + "v0alpha1": { + schema: { + spec: v0alpha1.TeamBindingSpec + } + } + } +} diff --git a/apps/iam/kinds/user.cue b/apps/iam/kinds/user.cue new file mode 100644 index 00000000000..f5e0f04377b --- /dev/null +++ b/apps/iam/kinds/user.cue @@ -0,0 +1,24 @@ +package kinds + +import ( + "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" +) + +user: { + kind: "User" + pluralName: "Users" + current: "v0alpha1" + + codegen: { + ts: { enabled: false } + go: { enabled: true } + } + + versions: { + "v0alpha1": { + schema: { + spec: v0alpha1.UserSpec + } + } + } +} diff --git a/apps/iam/kinds/v0alpha1/resourcepermission.cue b/apps/iam/kinds/v0alpha1/resourcepermission.cue index 4cb02c3c400..5cb8c4de5d0 100644 --- a/apps/iam/kinds/v0alpha1/resourcepermission.cue +++ b/apps/iam/kinds/v0alpha1/resourcepermission.cue @@ -21,5 +21,3 @@ ResourcePermission: { resource: #Resource permissions: [...#Permission] } - - diff --git a/apps/iam/kinds/v0alpha1/rolespec.cue b/apps/iam/kinds/v0alpha1/rolespec.cue index 46d6ce1f2c4..845ae207c28 100644 --- a/apps/iam/kinds/v0alpha1/rolespec.cue +++ b/apps/iam/kinds/v0alpha1/rolespec.cue @@ -20,4 +20,3 @@ RoleSpec: { // created? // updated? } - diff --git a/apps/iam/kinds/v0alpha1/serviceaccountspec.cue b/apps/iam/kinds/v0alpha1/serviceaccountspec.cue new file mode 100644 index 00000000000..9474fc1a06f --- /dev/null +++ b/apps/iam/kinds/v0alpha1/serviceaccountspec.cue @@ -0,0 +1,6 @@ +package v0alpha1 + +ServiceAccountSpec: { + title: string + disabled: bool +} diff --git a/apps/iam/kinds/v0alpha1/teambindingspec.cue b/apps/iam/kinds/v0alpha1/teambindingspec.cue new file mode 100644 index 00000000000..3bd130c902d --- /dev/null +++ b/apps/iam/kinds/v0alpha1/teambindingspec.cue @@ -0,0 +1,20 @@ +package v0alpha1 + +TeamBindingSpec: { + #Subject: { + // uid of the identity + name: string + // permission of the identity in the team + permission: TeamPermission + } + + subjects: [...#Subject] + teamRef: TeamRef +} + +TeamRef:{ + // Name is the unique identifier for a team. + name: string +} + +TeamPermission: "admin" | "member" diff --git a/apps/iam/kinds/v0alpha1/teamspec.cue b/apps/iam/kinds/v0alpha1/teamspec.cue new file mode 100644 index 00000000000..392c5fedb4a --- /dev/null +++ b/apps/iam/kinds/v0alpha1/teamspec.cue @@ -0,0 +1,6 @@ +package v0alpha1 + +TeamSpec: { + title: string + email: string +} diff --git a/apps/iam/kinds/v0alpha1/userspec.cue b/apps/iam/kinds/v0alpha1/userspec.cue new file mode 100644 index 00000000000..8ddcf57151d --- /dev/null +++ b/apps/iam/kinds/v0alpha1/userspec.cue @@ -0,0 +1,13 @@ +package v0alpha1 + +UserSpec: { + disabled: bool + email: string + emailVerified: bool + grafanaAdmin: bool + login: string + name: string + provisioned: bool + + // What to do with salt, rands and password? +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/extensions.go b/apps/iam/pkg/apis/iam/v0alpha1/extensions.go new file mode 100644 index 00000000000..c0c5a11f7e3 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/extensions.go @@ -0,0 +1,43 @@ +package v0alpha1 + +import ( + "fmt" + + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +func (u User) AuthID() string { + meta, err := utils.MetaAccessor(&u) + if err != nil { + return "" + } + // TODO: Workaround until we move all definitions + // After having all resource definitions here in the app, we can remove this + // and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method + //nolint:staticcheck + return fmt.Sprintf("%d", meta.GetDeprecatedInternalID()) +} + +func (s ServiceAccount) AuthID() string { + meta, err := utils.MetaAccessor(&s) + if err != nil { + return "" + } + // TODO: Workaround until we move all definitions + // After having all resource definitions here in the app, we can remove this + // and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method + //nolint:staticcheck + return fmt.Sprintf("%d", meta.GetDeprecatedInternalID()) +} + +func (t Team) AuthID() string { + meta, err := utils.MetaAccessor(&t) + if err != nil { + return "" + } + // TODO: Workaround until we move all definitions + // After having all resource definitions here in the app, we can remove this + // and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method + //nolint:staticcheck + return fmt.Sprintf("%d", meta.GetDeprecatedInternalID()) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_codec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_codec_gen.go new file mode 100644 index 00000000000..0f5b3e7783a --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_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" +) + +// ServiceAccountJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type ServiceAccountJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*ServiceAccountJSONCodec) 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 (*ServiceAccountJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &ServiceAccountJSONCodec{} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_metadata_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_metadata_gen.go new file mode 100644 index 00000000000..3a846141f87 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_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 ServiceAccountMetadata 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"` +} + +// NewServiceAccountMetadata creates a new ServiceAccountMetadata object. +func NewServiceAccountMetadata() *ServiceAccountMetadata { + return &ServiceAccountMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go new file mode 100644 index 00000000000..081e244dd6e --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_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 ServiceAccount struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the ServiceAccount + Spec ServiceAccountSpec `json:"spec" yaml:"spec"` + + Status ServiceAccountStatus `json:"status" yaml:"status"` +} + +func (o *ServiceAccount) GetSpec() any { + return o.Spec +} + +func (o *ServiceAccount) SetSpec(spec any) error { + cast, ok := spec.(ServiceAccountSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *ServiceAccount) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *ServiceAccount) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *ServiceAccount) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(ServiceAccountStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type ServiceAccountStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *ServiceAccount) 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 *ServiceAccount) 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 *ServiceAccount) 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 *ServiceAccount) 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 *ServiceAccount) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *ServiceAccount) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *ServiceAccount) 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 *ServiceAccount) 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 *ServiceAccount) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *ServiceAccount) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *ServiceAccount) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *ServiceAccount) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *ServiceAccount) DeepCopy() *ServiceAccount { + cpy := &ServiceAccount{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *ServiceAccount) DeepCopyInto(dst *ServiceAccount) { + 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 = &ServiceAccount{} + +// +k8s:openapi-gen=true +type ServiceAccountList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []ServiceAccount `json:"items" yaml:"items"` +} + +func (o *ServiceAccountList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *ServiceAccountList) Copy() resource.ListObject { + cpy := &ServiceAccountList{ + TypeMeta: o.TypeMeta, + Items: make([]ServiceAccount, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*ServiceAccount); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *ServiceAccountList) 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 *ServiceAccountList) SetItems(items []resource.Object) { + o.Items = make([]ServiceAccount, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*ServiceAccount) + } +} + +func (o *ServiceAccountList) DeepCopy() *ServiceAccountList { + cpy := &ServiceAccountList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *ServiceAccountList) DeepCopyInto(dst *ServiceAccountList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &ServiceAccountList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *ServiceAccountSpec) DeepCopy() *ServiceAccountSpec { + cpy := &ServiceAccountSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *ServiceAccountSpec) DeepCopyInto(dst *ServiceAccountSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of ServiceAccountStatus +func (s *ServiceAccountStatus) DeepCopy() *ServiceAccountStatus { + cpy := &ServiceAccountStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies ServiceAccountStatus into another ServiceAccountStatus object +func (s *ServiceAccountStatus) DeepCopyInto(dst *ServiceAccountStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_schema_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_schema_gen.go new file mode 100644 index 00000000000..ea48c864739 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_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 ( + schemaServiceAccount = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &ServiceAccount{}, &ServiceAccountList{}, resource.WithKind("ServiceAccount"), + resource.WithPlural("serviceaccounts"), resource.WithScope(resource.NamespacedScope)) + kindServiceAccount = resource.Kind{ + Schema: schemaServiceAccount, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &ServiceAccountJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func ServiceAccountKind() resource.Kind { + return kindServiceAccount +} + +// Schema returns a resource.SimpleSchema representation of ServiceAccount +func ServiceAccountSchema() *resource.SimpleSchema { + return schemaServiceAccount +} + +// Interface compliance checks +var _ resource.Schema = kindServiceAccount diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_spec_gen.go new file mode 100644 index 00000000000..a6bfffd53f9 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_spec_gen.go @@ -0,0 +1,14 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type ServiceAccountSpec struct { + Title string `json:"title"` + Disabled bool `json:"disabled"` +} + +// NewServiceAccountSpec creates a new ServiceAccountSpec object. +func NewServiceAccountSpec() *ServiceAccountSpec { + return &ServiceAccountSpec{} +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_status_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_status_gen.go new file mode 100644 index 00000000000..8174df97c23 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type ServiceAccountstatusOperatorState 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 ServiceAccountStatusOperatorStateState `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"` +} + +// NewServiceAccountstatusOperatorState creates a new ServiceAccountstatusOperatorState object. +func NewServiceAccountstatusOperatorState() *ServiceAccountstatusOperatorState { + return &ServiceAccountstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type ServiceAccountStatus 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]ServiceAccountstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewServiceAccountStatus creates a new ServiceAccountStatus object. +func NewServiceAccountStatus() *ServiceAccountStatus { + return &ServiceAccountStatus{} +} + +// +k8s:openapi-gen=true +type ServiceAccountStatusOperatorStateState string + +const ( + ServiceAccountStatusOperatorStateStateSuccess ServiceAccountStatusOperatorStateState = "success" + ServiceAccountStatusOperatorStateStateInProgress ServiceAccountStatusOperatorStateState = "in_progress" + ServiceAccountStatusOperatorStateStateFailed ServiceAccountStatusOperatorStateState = "failed" +) diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_codec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_codec_gen.go new file mode 100644 index 00000000000..fb0ede4ef1e --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_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" +) + +// TeamJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type TeamJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*TeamJSONCodec) 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 (*TeamJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &TeamJSONCodec{} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_metadata_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_metadata_gen.go new file mode 100644 index 00000000000..5e833327309 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_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 TeamMetadata 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"` +} + +// NewTeamMetadata creates a new TeamMetadata object. +func NewTeamMetadata() *TeamMetadata { + return &TeamMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go new file mode 100644 index 00000000000..bfc949acfd1 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_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 Team struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the Team + Spec TeamSpec `json:"spec" yaml:"spec"` + + Status TeamStatus `json:"status" yaml:"status"` +} + +func (o *Team) GetSpec() any { + return o.Spec +} + +func (o *Team) SetSpec(spec any) error { + cast, ok := spec.(TeamSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *Team) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *Team) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *Team) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(TeamStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type TeamStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *Team) 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 *Team) 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 *Team) 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 *Team) 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 *Team) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *Team) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *Team) 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 *Team) 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 *Team) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *Team) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *Team) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *Team) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *Team) DeepCopy() *Team { + cpy := &Team{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *Team) DeepCopyInto(dst *Team) { + 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 = &Team{} + +// +k8s:openapi-gen=true +type TeamList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []Team `json:"items" yaml:"items"` +} + +func (o *TeamList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *TeamList) Copy() resource.ListObject { + cpy := &TeamList{ + TypeMeta: o.TypeMeta, + Items: make([]Team, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*Team); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *TeamList) 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 *TeamList) SetItems(items []resource.Object) { + o.Items = make([]Team, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*Team) + } +} + +func (o *TeamList) DeepCopy() *TeamList { + cpy := &TeamList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *TeamList) DeepCopyInto(dst *TeamList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &TeamList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *TeamSpec) DeepCopy() *TeamSpec { + cpy := &TeamSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *TeamSpec) DeepCopyInto(dst *TeamSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of TeamStatus +func (s *TeamStatus) DeepCopy() *TeamStatus { + cpy := &TeamStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies TeamStatus into another TeamStatus object +func (s *TeamStatus) DeepCopyInto(dst *TeamStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_schema_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_schema_gen.go new file mode 100644 index 00000000000..7a4875cf2d5 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_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 ( + schemaTeam = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &Team{}, &TeamList{}, resource.WithKind("Team"), + resource.WithPlural("teams"), resource.WithScope(resource.NamespacedScope)) + kindTeam = resource.Kind{ + Schema: schemaTeam, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &TeamJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func TeamKind() resource.Kind { + return kindTeam +} + +// Schema returns a resource.SimpleSchema representation of Team +func TeamSchema() *resource.SimpleSchema { + return schemaTeam +} + +// Interface compliance checks +var _ resource.Schema = kindTeam diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_spec_gen.go new file mode 100644 index 00000000000..994bfdd59c5 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_spec_gen.go @@ -0,0 +1,14 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type TeamSpec struct { + Title string `json:"title"` + Email string `json:"email"` +} + +// NewTeamSpec creates a new TeamSpec object. +func NewTeamSpec() *TeamSpec { + return &TeamSpec{} +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_status_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_status_gen.go new file mode 100644 index 00000000000..0690e938633 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type TeamstatusOperatorState 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 TeamStatusOperatorStateState `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"` +} + +// NewTeamstatusOperatorState creates a new TeamstatusOperatorState object. +func NewTeamstatusOperatorState() *TeamstatusOperatorState { + return &TeamstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type TeamStatus 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]TeamstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewTeamStatus creates a new TeamStatus object. +func NewTeamStatus() *TeamStatus { + return &TeamStatus{} +} + +// +k8s:openapi-gen=true +type TeamStatusOperatorStateState string + +const ( + TeamStatusOperatorStateStateSuccess TeamStatusOperatorStateState = "success" + TeamStatusOperatorStateStateInProgress TeamStatusOperatorStateState = "in_progress" + TeamStatusOperatorStateStateFailed TeamStatusOperatorStateState = "failed" +) diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_codec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_codec_gen.go new file mode 100644 index 00000000000..358f530ca7b --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_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" +) + +// TeamBindingJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type TeamBindingJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*TeamBindingJSONCodec) 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 (*TeamBindingJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &TeamBindingJSONCodec{} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_metadata_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_metadata_gen.go new file mode 100644 index 00000000000..b1e7ab5f8ab --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_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 TeamBindingMetadata 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"` +} + +// NewTeamBindingMetadata creates a new TeamBindingMetadata object. +func NewTeamBindingMetadata() *TeamBindingMetadata { + return &TeamBindingMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go new file mode 100644 index 00000000000..6c9f6f407e3 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_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 TeamBinding struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the TeamBinding + Spec TeamBindingSpec `json:"spec" yaml:"spec"` + + Status TeamBindingStatus `json:"status" yaml:"status"` +} + +func (o *TeamBinding) GetSpec() any { + return o.Spec +} + +func (o *TeamBinding) SetSpec(spec any) error { + cast, ok := spec.(TeamBindingSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *TeamBinding) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *TeamBinding) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *TeamBinding) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(TeamBindingStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type TeamBindingStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *TeamBinding) 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 *TeamBinding) 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 *TeamBinding) 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 *TeamBinding) 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 *TeamBinding) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *TeamBinding) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *TeamBinding) 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 *TeamBinding) 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 *TeamBinding) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *TeamBinding) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *TeamBinding) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *TeamBinding) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *TeamBinding) DeepCopy() *TeamBinding { + cpy := &TeamBinding{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *TeamBinding) DeepCopyInto(dst *TeamBinding) { + 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 = &TeamBinding{} + +// +k8s:openapi-gen=true +type TeamBindingList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []TeamBinding `json:"items" yaml:"items"` +} + +func (o *TeamBindingList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *TeamBindingList) Copy() resource.ListObject { + cpy := &TeamBindingList{ + TypeMeta: o.TypeMeta, + Items: make([]TeamBinding, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*TeamBinding); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *TeamBindingList) 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 *TeamBindingList) SetItems(items []resource.Object) { + o.Items = make([]TeamBinding, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*TeamBinding) + } +} + +func (o *TeamBindingList) DeepCopy() *TeamBindingList { + cpy := &TeamBindingList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *TeamBindingList) DeepCopyInto(dst *TeamBindingList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &TeamBindingList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *TeamBindingSpec) DeepCopy() *TeamBindingSpec { + cpy := &TeamBindingSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *TeamBindingSpec) DeepCopyInto(dst *TeamBindingSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of TeamBindingStatus +func (s *TeamBindingStatus) DeepCopy() *TeamBindingStatus { + cpy := &TeamBindingStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies TeamBindingStatus into another TeamBindingStatus object +func (s *TeamBindingStatus) DeepCopyInto(dst *TeamBindingStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_schema_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_schema_gen.go new file mode 100644 index 00000000000..e3c2c11a8b5 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_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 ( + schemaTeamBinding = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &TeamBinding{}, &TeamBindingList{}, resource.WithKind("TeamBinding"), + resource.WithPlural("teambindings"), resource.WithScope(resource.NamespacedScope)) + kindTeamBinding = resource.Kind{ + Schema: schemaTeamBinding, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &TeamBindingJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func TeamBindingKind() resource.Kind { + return kindTeamBinding +} + +// Schema returns a resource.SimpleSchema representation of TeamBinding +func TeamBindingSchema() *resource.SimpleSchema { + return schemaTeamBinding +} + +// Interface compliance checks +var _ resource.Schema = kindTeamBinding diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go new file mode 100644 index 00000000000..0e58323533e --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go @@ -0,0 +1,49 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type TeamBindingspecSubject struct { + // uid of the identity + Name string `json:"name"` + // permission of the identity in the team + Permission TeamBindingTeamPermission `json:"permission"` +} + +// NewTeamBindingspecSubject creates a new TeamBindingspecSubject object. +func NewTeamBindingspecSubject() *TeamBindingspecSubject { + return &TeamBindingspecSubject{} +} + +// +k8s:openapi-gen=true +type TeamBindingTeamPermission string + +const ( + TeamBindingTeamPermissionAdmin TeamBindingTeamPermission = "admin" + TeamBindingTeamPermissionMember TeamBindingTeamPermission = "member" +) + +// +k8s:openapi-gen=true +type TeamBindingTeamRef struct { + // Name is the unique identifier for a team. + Name string `json:"name"` +} + +// NewTeamBindingTeamRef creates a new TeamBindingTeamRef object. +func NewTeamBindingTeamRef() *TeamBindingTeamRef { + return &TeamBindingTeamRef{} +} + +// +k8s:openapi-gen=true +type TeamBindingSpec struct { + Subjects []TeamBindingspecSubject `json:"subjects"` + TeamRef TeamBindingTeamRef `json:"teamRef"` +} + +// NewTeamBindingSpec creates a new TeamBindingSpec object. +func NewTeamBindingSpec() *TeamBindingSpec { + return &TeamBindingSpec{ + Subjects: []TeamBindingspecSubject{}, + TeamRef: *NewTeamBindingTeamRef(), + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_status_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_status_gen.go new file mode 100644 index 00000000000..221eb320ae7 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type TeamBindingstatusOperatorState 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 TeamBindingStatusOperatorStateState `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"` +} + +// NewTeamBindingstatusOperatorState creates a new TeamBindingstatusOperatorState object. +func NewTeamBindingstatusOperatorState() *TeamBindingstatusOperatorState { + return &TeamBindingstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type TeamBindingStatus 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]TeamBindingstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewTeamBindingStatus creates a new TeamBindingStatus object. +func NewTeamBindingStatus() *TeamBindingStatus { + return &TeamBindingStatus{} +} + +// +k8s:openapi-gen=true +type TeamBindingStatusOperatorStateState string + +const ( + TeamBindingStatusOperatorStateStateSuccess TeamBindingStatusOperatorStateState = "success" + TeamBindingStatusOperatorStateStateInProgress TeamBindingStatusOperatorStateState = "in_progress" + TeamBindingStatusOperatorStateStateFailed TeamBindingStatusOperatorStateState = "failed" +) diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_codec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_codec_gen.go new file mode 100644 index 00000000000..b37a806a30d --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_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" +) + +// UserJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type UserJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*UserJSONCodec) 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 (*UserJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &UserJSONCodec{} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_metadata_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_metadata_gen.go new file mode 100644 index 00000000000..8b1e19a94ab --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_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 UserMetadata 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"` +} + +// NewUserMetadata creates a new UserMetadata object. +func NewUserMetadata() *UserMetadata { + return &UserMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go new file mode 100644 index 00000000000..ce2defa33a8 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_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 User struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the User + Spec UserSpec `json:"spec" yaml:"spec"` + + Status UserStatus `json:"status" yaml:"status"` +} + +func (o *User) GetSpec() any { + return o.Spec +} + +func (o *User) SetSpec(spec any) error { + cast, ok := spec.(UserSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *User) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *User) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *User) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(UserStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type UserStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *User) 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 *User) 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 *User) 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 *User) 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 *User) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *User) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *User) 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 *User) 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 *User) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *User) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *User) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *User) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *User) DeepCopy() *User { + cpy := &User{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *User) DeepCopyInto(dst *User) { + 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 = &User{} + +// +k8s:openapi-gen=true +type UserList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []User `json:"items" yaml:"items"` +} + +func (o *UserList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *UserList) Copy() resource.ListObject { + cpy := &UserList{ + TypeMeta: o.TypeMeta, + Items: make([]User, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*User); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *UserList) 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 *UserList) SetItems(items []resource.Object) { + o.Items = make([]User, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*User) + } +} + +func (o *UserList) DeepCopy() *UserList { + cpy := &UserList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *UserList) DeepCopyInto(dst *UserList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &UserList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *UserSpec) DeepCopy() *UserSpec { + cpy := &UserSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *UserSpec) DeepCopyInto(dst *UserSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of UserStatus +func (s *UserStatus) DeepCopy() *UserStatus { + cpy := &UserStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies UserStatus into another UserStatus object +func (s *UserStatus) DeepCopyInto(dst *UserStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_schema_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_schema_gen.go new file mode 100644 index 00000000000..a44d9dfcf2d --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_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 ( + schemaUser = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &User{}, &UserList{}, resource.WithKind("User"), + resource.WithPlural("users"), resource.WithScope(resource.NamespacedScope)) + kindUser = resource.Kind{ + Schema: schemaUser, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &UserJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func UserKind() resource.Kind { + return kindUser +} + +// Schema returns a resource.SimpleSchema representation of User +func UserSchema() *resource.SimpleSchema { + return schemaUser +} + +// Interface compliance checks +var _ resource.Schema = kindUser diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_spec_gen.go new file mode 100644 index 00000000000..8e83f1d8b5d --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_spec_gen.go @@ -0,0 +1,20 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type UserSpec struct { + Disabled bool `json:"disabled"` + Email string `json:"email"` + EmailVerified bool `json:"emailVerified"` + GrafanaAdmin bool `json:"grafanaAdmin"` + Login string `json:"login"` + Name string `json:"name"` + // What to do with salt, rands and password? + Provisioned bool `json:"provisioned"` +} + +// NewUserSpec creates a new UserSpec object. +func NewUserSpec() *UserSpec { + return &UserSpec{} +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go new file mode 100644 index 00000000000..34a138f59ce --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type UserstatusOperatorState 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 UserStatusOperatorStateState `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"` +} + +// NewUserstatusOperatorState creates a new UserstatusOperatorState object. +func NewUserstatusOperatorState() *UserstatusOperatorState { + return &UserstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type UserStatus 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]UserstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewUserStatus creates a new UserStatus object. +func NewUserStatus() *UserStatus { + return &UserStatus{} +} + +// +k8s:openapi-gen=true +type UserStatusOperatorStateState string + +const ( + UserStatusOperatorStateStateSuccess UserStatusOperatorStateState = "success" + UserStatusOperatorStateStateInProgress UserStatusOperatorStateState = "in_progress" + UserStatusOperatorStateStateFailed UserStatusOperatorStateState = "failed" +) diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index df62f68072a..406834348e3 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -51,6 +51,28 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus": schema_pkg_apis_iam_v0alpha1_RoleStatus(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolespecPermission": schema_pkg_apis_iam_v0alpha1_RolespecPermission(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_RolestatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount": schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountList": schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec": schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus": schema_pkg_apis_iam_v0alpha1_ServiceAccountStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState": schema_pkg_apis_iam_v0alpha1_ServiceAccountstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team": schema_pkg_apis_iam_v0alpha1_Team(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding": schema_pkg_apis_iam_v0alpha1_TeamBinding(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingList": schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec": schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus": schema_pkg_apis_iam_v0alpha1_TeamBindingStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef": schema_pkg_apis_iam_v0alpha1_TeamBindingTeamRef(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject": schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamBindingstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamList": schema_pkg_apis_iam_v0alpha1_TeamList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec": schema_pkg_apis_iam_v0alpha1_TeamSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus": schema_pkg_apis_iam_v0alpha1_TeamStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User": schema_pkg_apis_iam_v0alpha1_User(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserList": schema_pkg_apis_iam_v0alpha1_UserList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec": schema_pkg_apis_iam_v0alpha1_UserSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": schema_pkg_apis_iam_v0alpha1_UserStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref), } } @@ -1738,3 +1760,968 @@ func schema_pkg_apis_iam_v0alpha1_RolestatusOperatorState(ref common.ReferenceCa }, } } + +func schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec is the spec of the ServiceAccount", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus"), + }, + }, + }, + Required: []string{"metadata", "spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "title": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "disabled": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"title", "disabled"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_ServiceAccountStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operatorStates": { + SchemaProps: spec.SchemaProps{ + Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState"), + }, + }, + }, + }, + }, + "additionalFields": { + SchemaProps: spec.SchemaProps{ + Description: "additionalFields is reserved for future use", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ServiceAccountstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastEvaluation": { + SchemaProps: spec.SchemaProps{ + Description: "lastEvaluation is the ResourceVersion last evaluated", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "descriptiveState": { + SchemaProps: spec.SchemaProps{ + Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + SchemaProps: spec.SchemaProps{ + Description: "details contains any extra information that is operator-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"lastEvaluation", "state"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec is the spec of the Team", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus"), + }, + }, + }, + Required: []string{"metadata", "spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec is the spec of the TeamBinding", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus"), + }, + }, + }, + Required: []string{"metadata", "spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "subjects": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"), + }, + }, + }, + }, + }, + "teamRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef"), + }, + }, + }, + Required: []string{"subjects", "teamRef"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operatorStates": { + SchemaProps: spec.SchemaProps{ + Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState"), + }, + }, + }, + }, + }, + "additionalFields": { + SchemaProps: spec.SchemaProps{ + Description: "additionalFields is reserved for future use", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingTeamRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the unique identifier for a team.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "uid of the identity", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "permission": { + SchemaProps: spec.SchemaProps{ + Description: "permission of the identity in the team", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "permission"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamBindingstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastEvaluation": { + SchemaProps: spec.SchemaProps{ + Description: "lastEvaluation is the ResourceVersion last evaluated", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "descriptiveState": { + SchemaProps: spec.SchemaProps{ + Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + SchemaProps: spec.SchemaProps{ + Description: "details contains any extra information that is operator-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"lastEvaluation", "state"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "title": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "email": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"title", "email"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operatorStates": { + SchemaProps: spec.SchemaProps{ + Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState"), + }, + }, + }, + }, + }, + "additionalFields": { + SchemaProps: spec.SchemaProps{ + Description: "additionalFields is reserved for future use", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_TeamstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastEvaluation": { + SchemaProps: spec.SchemaProps{ + Description: "lastEvaluation is the ResourceVersion last evaluated", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "descriptiveState": { + SchemaProps: spec.SchemaProps{ + Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + SchemaProps: spec.SchemaProps{ + Description: "details contains any extra information that is operator-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"lastEvaluation", "state"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec is the spec of the User", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus"), + }, + }, + }, + Required: []string{"metadata", "spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "disabled": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "email": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "emailVerified": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "grafanaAdmin": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "login": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "provisioned": { + SchemaProps: spec.SchemaProps{ + Description: "What to do with salt, rands and password?", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"disabled", "email", "emailVerified", "grafanaAdmin", "login", "name", "provisioned"}, + }, + }, + } +} + +func schema_pkg_apis_iam_v0alpha1_UserStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operatorStates": { + SchemaProps: spec.SchemaProps{ + Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState"), + }, + }, + }, + }, + }, + "additionalFields": { + SchemaProps: spec.SchemaProps{ + Description: "additionalFields is reserved for future use", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastEvaluation": { + SchemaProps: spec.SchemaProps{ + Description: "lastEvaluation is the ResourceVersion last evaluated", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "descriptiveState": { + SchemaProps: spec.SchemaProps{ + Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + SchemaProps: spec.SchemaProps{ + Description: "details contains any extra information that is operator-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"lastEvaluation", "state"}, + }, + }, + } +} diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index b80f5e5eee3..8f144c3cd27 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -14,6 +14,8 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" ) +var () + var appManifestData = app.ManifestData{ AppName: "iam", Group: "iam.grafana.app", @@ -83,6 +85,50 @@ var appManifestData = app.ManifestData{ }, }, }, + + { + Kind: "User", + Scope: "Namespaced", + Conversion: false, + Versions: []app.ManifestKindVersion{ + { + Name: "v0alpha1", + }, + }, + }, + + { + Kind: "Team", + Scope: "Namespaced", + Conversion: false, + Versions: []app.ManifestKindVersion{ + { + Name: "v0alpha1", + }, + }, + }, + + { + Kind: "TeamBinding", + Scope: "Namespaced", + Conversion: false, + Versions: []app.ManifestKindVersion{ + { + Name: "v0alpha1", + }, + }, + }, + + { + Kind: "ServiceAccount", + Scope: "Namespaced", + Conversion: false, + Versions: []app.ManifestKindVersion{ + { + Name: "v0alpha1", + }, + }, + }, }, } @@ -101,6 +147,10 @@ var kindVersionToGoType = map[string]resource.Kind{ "Role/v0alpha1": v0alpha1.RoleKind(), "RoleBinding/v0alpha1": v0alpha1.RoleBindingKind(), "ResourcePermission/v0alpha1": v0alpha1.ResourcePermissionKind(), + "User/v0alpha1": v0alpha1.UserKind(), + "Team/v0alpha1": v0alpha1.TeamKind(), + "TeamBinding/v0alpha1": v0alpha1.TeamBindingKind(), + "ServiceAccount/v0alpha1": v0alpha1.ServiceAccountKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index be5d7fe83fa..4e849dbba3c 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -115,9 +115,10 @@ var serviceIdentityPermissions = getWildcardPermissions( "datasources:delete", "alert.provisioning:write", "alert.provisioning.secrets:read", - "users:read", // accesscontrol.ActionUsersRead, - "org.users:read", // accesscontrol.ActionOrgUsersRead, - "teams:read", // accesscontrol.ActionTeamsRead, + "users:read", // accesscontrol.ActionUsersRead, + "org.users:read", // accesscontrol.ActionOrgUsersRead, + "teams:read", // accesscontrol.ActionTeamsRead, + "serviceaccounts:read", // serviceaccounts.ActionRead, ) var serviceIdentityTokenPermissions = getTokenPermissions( diff --git a/pkg/apis/iam/v0alpha1/register.go b/pkg/apis/iam/v0alpha1/register.go index 7cec76a3b77..879cb3837cc 100644 --- a/pkg/apis/iam/v0alpha1/register.go +++ b/pkg/apis/iam/v0alpha1/register.go @@ -2,8 +2,10 @@ package v0alpha1 import ( "fmt" + "strings" "time" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -16,10 +18,11 @@ const ( APIVERSION = GROUP + "/" + VERSION ) -var UserResourceInfo = utils.NewResourceInfo(GROUP, VERSION, - "users", "user", "User", - func() runtime.Object { return &User{} }, - func() runtime.Object { return &UserList{} }, +var userKind = iamv0alpha1.UserKind() +var UserResourceInfo = utils.NewResourceInfo(userKind.Group(), userKind.Version(), + userKind.GroupVersionResource().Resource, strings.ToLower(userKind.Kind()), userKind.Kind(), + func() runtime.Object { return userKind.ZeroValue() }, + func() runtime.Object { return userKind.ZeroListValue() }, utils.TableColumns{ Definition: []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -28,7 +31,7 @@ var UserResourceInfo = utils.NewResourceInfo(GROUP, VERSION, {Name: "Created At", Type: "date"}, }, Reader: func(obj any) ([]interface{}, error) { - u, ok := obj.(*User) + u, ok := obj.(*iamv0alpha1.User) if ok { return []interface{}{ u.Name, @@ -42,10 +45,11 @@ var UserResourceInfo = utils.NewResourceInfo(GROUP, VERSION, }, ) -var TeamResourceInfo = utils.NewResourceInfo(GROUP, VERSION, - "teams", "team", "Team", - func() runtime.Object { return &Team{} }, - func() runtime.Object { return &TeamList{} }, +var teamKind = iamv0alpha1.TeamKind() +var TeamResourceInfo = utils.NewResourceInfo(teamKind.Group(), teamKind.Version(), + teamKind.GroupVersionResource().Resource, strings.ToLower(teamKind.Kind()), teamKind.Kind(), + func() runtime.Object { return teamKind.ZeroValue() }, + func() runtime.Object { return teamKind.ZeroListValue() }, utils.TableColumns{ Definition: []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -54,7 +58,7 @@ var TeamResourceInfo = utils.NewResourceInfo(GROUP, VERSION, {Name: "Created At", Type: "date"}, }, Reader: func(obj any) ([]interface{}, error) { - m, ok := obj.(*Team) + m, ok := obj.(*iamv0alpha1.Team) if !ok { return nil, fmt.Errorf("expected team") } @@ -68,10 +72,11 @@ var TeamResourceInfo = utils.NewResourceInfo(GROUP, VERSION, }, ) -var ServiceAccountResourceInfo = utils.NewResourceInfo(GROUP, VERSION, - "serviceaccounts", "serviceaccount", "ServiceAccount", - func() runtime.Object { return &ServiceAccount{} }, - func() runtime.Object { return &ServiceAccountList{} }, +var serviceAccountKind = iamv0alpha1.ServiceAccountKind() +var ServiceAccountResourceInfo = utils.NewResourceInfo(serviceAccountKind.Group(), serviceAccountKind.Version(), + serviceAccountKind.GroupVersionResource().Resource, strings.ToLower(serviceAccountKind.Kind()), serviceAccountKind.Kind(), + func() runtime.Object { return serviceAccountKind.ZeroValue() }, + func() runtime.Object { return serviceAccountKind.ZeroListValue() }, utils.TableColumns{ Definition: []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -80,7 +85,7 @@ var ServiceAccountResourceInfo = utils.NewResourceInfo(GROUP, VERSION, {Name: "Created At", Type: "date"}, }, Reader: func(obj any) ([]interface{}, error) { - sa, ok := obj.(*ServiceAccount) + sa, ok := obj.(*iamv0alpha1.ServiceAccount) if ok { return []interface{}{ sa.Name, @@ -120,10 +125,13 @@ var SSOSettingResourceInfo = utils.NewResourceInfo( }, ) +var teamBindingKind = iamv0alpha1.TeamBindingKind() var TeamBindingResourceInfo = utils.NewResourceInfo( - GROUP, VERSION, "teambindings", "teambinding", "TeamBinding", - func() runtime.Object { return &TeamBinding{} }, - func() runtime.Object { return &TeamBindingList{} }, + teamBindingKind.Group(), teamBindingKind.Version(), + teamBindingKind.GroupVersionResource().Resource, + strings.ToLower(teamBindingKind.Kind()), teamBindingKind.Kind(), + func() runtime.Object { return teamBindingKind.ZeroValue() }, + func() runtime.Object { return teamBindingKind.ZeroListValue() }, utils.TableColumns{ Definition: []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -131,13 +139,13 @@ var TeamBindingResourceInfo = utils.NewResourceInfo( {Name: "Created At", Type: "string", Format: "date"}, }, Reader: func(obj any) ([]interface{}, error) { - m, ok := obj.(*TeamBinding) + m, ok := obj.(*iamv0alpha1.TeamBinding) if !ok { return nil, fmt.Errorf("expected team binding") } return []interface{}{ m.Name, - m.Spec.Team.Name, + m.Spec.TeamRef.Name, m.CreationTimestamp.UTC().Format(time.RFC3339), }, nil }, @@ -158,19 +166,19 @@ var ( func AddKnownTypes(scheme *runtime.Scheme, version string) { scheme.AddKnownTypes( schema.GroupVersion{Group: GROUP, Version: version}, - &User{}, - &UserList{}, + &iamv0alpha1.User{}, + &iamv0alpha1.UserList{}, &UserTeamList{}, - &ServiceAccount{}, - &ServiceAccountList{}, + &iamv0alpha1.ServiceAccount{}, + &iamv0alpha1.ServiceAccountList{}, &ServiceAccountTokenList{}, - &Team{}, - &TeamList{}, + &iamv0alpha1.Team{}, + &iamv0alpha1.TeamList{}, &DisplayList{}, &SSOSetting{}, &SSOSettingList{}, - &TeamBinding{}, - &TeamBindingList{}, + &iamv0alpha1.TeamBinding{}, + &iamv0alpha1.TeamBindingList{}, &TeamMemberList{}, ) } diff --git a/pkg/apis/iam/v0alpha1/types_service_account.go b/pkg/apis/iam/v0alpha1/types_service_account.go new file mode 100644 index 00000000000..25688929ebf --- /dev/null +++ b/pkg/apis/iam/v0alpha1/types_service_account.go @@ -0,0 +1,19 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ServiceAccountTokenList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []ServiceAccountToken `json:"items"` +} + +type ServiceAccountToken struct { + Name string `json:"name,omitempty"` + Revoked bool `json:"revoked,omitempty"` + Expires *metav1.Time `json:"expires,omitempty"` + LastUsed *metav1.Time `json:"lastUsed,omitempty"` + Created metav1.Time `json:"created"` +} diff --git a/pkg/apis/iam/v0alpha1/types_servier_account.go b/pkg/apis/iam/v0alpha1/types_servier_account.go deleted file mode 100644 index 3f07ca76b41..00000000000 --- a/pkg/apis/iam/v0alpha1/types_servier_account.go +++ /dev/null @@ -1,50 +0,0 @@ -package v0alpha1 - -import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccount struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ServiceAccountSpec `json:"spec,omitempty"` -} - -func (s ServiceAccount) AuthID() string { - return fmt.Sprintf("%d", s.Spec.InternalID) -} - -type ServiceAccountSpec struct { - Title string `json:"title,omitempty"` - Disabled bool `json:"disabled,omitempty"` - // This is currently used for authorization checks but we don't want to expose it - InternalID int64 `json:"-"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccountList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []ServiceAccount `json:"items"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccountTokenList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []ServiceAccountToken `json:"items"` -} - -type ServiceAccountToken struct { - Name string `json:"name,omitempty"` - Revoked bool `json:"revoked,omitempty"` - Expires *metav1.Time `json:"expires,omitempty"` - LastUsed *metav1.Time `json:"lastUsed,omitempty"` - Created metav1.Time `json:"created"` -} diff --git a/pkg/apis/iam/v0alpha1/types_team.go b/pkg/apis/iam/v0alpha1/types_team.go index f7a2cfe1762..de8d81543f2 100644 --- a/pkg/apis/iam/v0alpha1/types_team.go +++ b/pkg/apis/iam/v0alpha1/types_team.go @@ -1,60 +1,9 @@ package v0alpha1 import ( - "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type Team struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TeamSpec `json:"spec,omitempty"` -} - -type TeamSpec struct { - Title string `json:"title,omitempty"` - Email string `json:"email,omitempty"` - - // This is currently used for authorization checks but we don't want to expose it - InternalID int64 `json:"-"` -} - -func (t Team) AuthID() string { - return fmt.Sprintf("%d", t.Spec.InternalID) -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TeamList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []Team `json:"items"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TeamBinding struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TeamBindingSpec `json:"spec,omitempty"` -} - -type TeamBindingSpec struct { - Subjects []TeamSubject `json:"subjects,omitempty"` - Team TeamRef `json:"team,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TeamBindingList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []TeamBinding `json:"items"` -} - type TeamSubject struct { // Identity is a reference to the identity of this subject. Identity IdentityRef `json:"identity"` diff --git a/pkg/apis/iam/v0alpha1/types_user.go b/pkg/apis/iam/v0alpha1/types_user.go index f9d97095a3e..b62bbf6fc9d 100644 --- a/pkg/apis/iam/v0alpha1/types_user.go +++ b/pkg/apis/iam/v0alpha1/types_user.go @@ -1,40 +1,6 @@ package v0alpha1 -import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type User struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec UserSpec `json:"spec,omitempty"` -} - -func (u User) AuthID() string { - return fmt.Sprintf("%d", u.Spec.InternalID) -} - -type UserSpec struct { - Name string `json:"name,omitempty"` - Login string `json:"login,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"emailVerified,omitempty"` - Disabled bool `json:"disabled,omitempty"` - // This is currently used for authorization checks but we don't want to expose it - InternalID int64 `json:"-"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type UserList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []User `json:"items"` -} +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type UserTeamList struct { diff --git a/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go index 20556acfa96..64d2f72c358 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go @@ -162,82 +162,6 @@ func (in *SSOSettingSpec) DeepCopy() *SSOSettingSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount. -func (in *ServiceAccount) DeepCopy() *ServiceAccount { - if in == nil { - return nil - } - out := new(ServiceAccount) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceAccount) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccountList) DeepCopyInto(out *ServiceAccountList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ServiceAccount, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountList. -func (in *ServiceAccountList) DeepCopy() *ServiceAccountList { - if in == nil { - return nil - } - out := new(ServiceAccountList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceAccountList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccountSpec) DeepCopyInto(out *ServiceAccountSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountSpec. -func (in *ServiceAccountSpec) DeepCopy() *ServiceAccountSpec { - if in == nil { - return nil - } - out := new(ServiceAccountSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAccountToken) DeepCopyInto(out *ServiceAccountToken) { *out = *in @@ -296,148 +220,6 @@ func (in *ServiceAccountTokenList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Team) DeepCopyInto(out *Team) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Team. -func (in *Team) DeepCopy() *Team { - if in == nil { - return nil - } - out := new(Team) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Team) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeamBinding) DeepCopyInto(out *TeamBinding) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamBinding. -func (in *TeamBinding) DeepCopy() *TeamBinding { - if in == nil { - return nil - } - out := new(TeamBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TeamBinding) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeamBindingList) DeepCopyInto(out *TeamBindingList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]TeamBinding, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamBindingList. -func (in *TeamBindingList) DeepCopy() *TeamBindingList { - if in == nil { - return nil - } - out := new(TeamBindingList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TeamBindingList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeamBindingSpec) DeepCopyInto(out *TeamBindingSpec) { - *out = *in - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]TeamSubject, len(*in)) - copy(*out, *in) - } - out.Team = in.Team - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamBindingSpec. -func (in *TeamBindingSpec) DeepCopy() *TeamBindingSpec { - if in == nil { - return nil - } - out := new(TeamBindingSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeamList) DeepCopyInto(out *TeamList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Team, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamList. -func (in *TeamList) DeepCopy() *TeamList { - if in == nil { - return nil - } - out := new(TeamList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TeamList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TeamMember) DeepCopyInto(out *TeamMember) { *out = *in @@ -502,22 +284,6 @@ func (in *TeamRef) DeepCopy() *TeamRef { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeamSpec) DeepCopyInto(out *TeamSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamSpec. -func (in *TeamSpec) DeepCopy() *TeamSpec { - if in == nil { - return nil - } - out := new(TeamSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TeamSubject) DeepCopyInto(out *TeamSubject) { *out = *in @@ -535,82 +301,6 @@ func (in *TeamSubject) DeepCopy() *TeamSubject { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *User) DeepCopyInto(out *User) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User. -func (in *User) DeepCopy() *User { - if in == nil { - return nil - } - out := new(User) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *User) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *UserList) DeepCopyInto(out *UserList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]User, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. -func (in *UserList) DeepCopy() *UserList { - if in == nil { - return nil - } - out := new(UserList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *UserList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *UserSpec) DeepCopyInto(out *UserSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. -func (in *UserSpec) DeepCopy() *UserSpec { - if in == nil { - return nil - } - out := new(UserSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UserTeam) DeepCopyInto(out *UserTeam) { *out = *in diff --git a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go index 826b6dfb60d..c345dd23444 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go @@ -20,24 +20,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSetting": schema_pkg_apis_iam_v0alpha1_SSOSetting(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSettingList": schema_pkg_apis_iam_v0alpha1_SSOSettingList(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSettingSpec": schema_pkg_apis_iam_v0alpha1_SSOSettingSpec(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccount": schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountList": schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountSpec": schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountToken": schema_pkg_apis_iam_v0alpha1_ServiceAccountToken(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountTokenList": schema_pkg_apis_iam_v0alpha1_ServiceAccountTokenList(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.Team": schema_pkg_apis_iam_v0alpha1_Team(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBinding": schema_pkg_apis_iam_v0alpha1_TeamBinding(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBindingList": schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBindingSpec": schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamList": schema_pkg_apis_iam_v0alpha1_TeamList(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamMember": schema_pkg_apis_iam_v0alpha1_TeamMember(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamMemberList": schema_pkg_apis_iam_v0alpha1_TeamMemberList(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamRef": schema_pkg_apis_iam_v0alpha1_TeamRef(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSpec": schema_pkg_apis_iam_v0alpha1_TeamSpec(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSubject": schema_pkg_apis_iam_v0alpha1_TeamSubject(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.User": schema_pkg_apis_iam_v0alpha1_User(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserList": schema_pkg_apis_iam_v0alpha1_UserList(ref), - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserSpec": schema_pkg_apis_iam_v0alpha1_UserSpec(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserTeam": schema_pkg_apis_iam_v0alpha1_UserTeam(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserTeamList": schema_pkg_apis_iam_v0alpha1_UserTeamList(ref), } @@ -328,118 +316,6 @@ func schema_pkg_apis_iam_v0alpha1_SSOSettingSpec(ref common.ReferenceCallback) c } } -func schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountSpec"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccount"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "disabled": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Format: "", - }, - }, - }, - }, - }, - } -} - func schema_pkg_apis_iam_v0alpha1_ServiceAccountToken(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -530,215 +406,6 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccountTokenList(ref common.ReferenceCa } } -func schema_pkg_apis_iam_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSpec"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_TeamBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBindingSpec"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBindingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBinding"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "subjects": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSubject"), - }, - }, - }, - }, - }, - "team": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamRef"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamRef", "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamSubject"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.Team"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - func schema_pkg_apis_iam_v0alpha1_TeamMember(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -864,30 +531,6 @@ func schema_pkg_apis_iam_v0alpha1_TeamRef(ref common.ReferenceCallback) common.O } } -func schema_pkg_apis_iam_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "email": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - } -} - func schema_pkg_apis_iam_v0alpha1_TeamSubject(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -918,136 +561,6 @@ func schema_pkg_apis_iam_v0alpha1_TeamSubject(ref common.ReferenceCallback) comm } } -func schema_pkg_apis_iam_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserSpec"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.User"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "login": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "email": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "emailVerified": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Format: "", - }, - }, - "disabled": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Format: "", - }, - }, - }, - }, - }, - } -} - func schema_pkg_apis_iam_v0alpha1_UserTeam(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list index 4d214e16a1a..b2e716970f2 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,4 +1,3 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,DisplayList,Items -API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,TeamBindingSpec,Subjects API rule violation: names_match,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,Display,InternalID API rule violation: names_match,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,DisplayList,Items diff --git a/pkg/registry/apis/iam/common/common.go b/pkg/registry/apis/iam/common/common.go index a93c90a7d36..ff11de13799 100644 --- a/pkg/registry/apis/iam/common/common.go +++ b/pkg/registry/apis/iam/common/common.go @@ -5,8 +5,9 @@ import ( "strconv" authlib "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" + legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/team" ) @@ -20,11 +21,19 @@ func OptionalFormatInt(num int64) string { return "" } -func MapTeamPermission(p team.PermissionType) iamv0.TeamPermission { +func MapTeamPermission(p team.PermissionType) iamv0alpha1.TeamBindingTeamPermission { if p == team.PermissionTypeAdmin { - return iamv0.TeamPermissionAdmin + return iamv0alpha1.TeamBindingTeamPermissionAdmin } else { - return iamv0.TeamPermissionMember + return iamv0alpha1.TeamBindingTeamPermissionMember + } +} + +func MapUserTeamPermission(p team.PermissionType) legacyiamv0.TeamPermission { + if p == team.PermissionTypeAdmin { + return legacyiamv0.TeamPermissionAdmin + } else { + return legacyiamv0.TeamPermissionMember } } diff --git a/pkg/registry/apis/iam/legacy/display_query.sql b/pkg/registry/apis/iam/legacy/display_query.sql index c6364f0afa5..15dfff35d09 100644 --- a/pkg/registry/apis/iam/legacy/display_query.sql +++ b/pkg/registry/apis/iam/legacy/display_query.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM {{ .Ident .UserTable }} as u JOIN {{ .Ident .OrgUserTable }} as o ON u.id = o.user_id WHERE o.org_id = {{ .Arg .Query.OrgID }} AND ( 1=2 {{ if .Query.UIDs }} diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids.sql index 8346f0a8a7d..66f977fe3db 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR u.id IN (1, 2) diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids_uids.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids_uids.sql index a93f7b2e662..933d45adbee 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_ids_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_uids.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_uids.sql index d9727e67feb..a1c7c11dcad 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--display_query-display_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_1.sql index 4ac1c74d87f..399aee41f46 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_1.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_2.sql index 5bcc865f1d9..63b9fc610ab 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_page_2.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_uid.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_uid.sql index 2c36c9f9fbb..bf2a9b7ccb5 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_uid.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--users_query-users_uid.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids.sql index d02afa8cf0a..d3cb1fb1e8c 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR u.id IN (1, 2) diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids_uids.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids_uids.sql index e8a519e465b..dfb4ed5afe2 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_ids_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_uids.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_uids.sql index 2af78d4dfe6..a6989bb94e0 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--display_query-display_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_1.sql index 7de4914aa8d..4e771e18f26 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_1.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_2.sql index e938a14392b..f6e2d6dab11 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_page_2.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_uid.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_uid.sql index 5c8f1f2ec66..a43df88f8a8 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_uid.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--users_query-users_uid.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids.sql index d02afa8cf0a..d3cb1fb1e8c 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR u.id IN (1, 2) diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids_uids.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids_uids.sql index e8a519e465b..dfb4ed5afe2 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_ids_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_uids.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_uids.sql index 2af78d4dfe6..a6989bb94e0 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_uids.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--display_query-display_uids.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 2 AND ( 1=2 OR uid IN ('a', 'b') diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_1.sql index 7de4914aa8d..4e771e18f26 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_1.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_2.sql index e938a14392b..f6e2d6dab11 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_page_2.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_uid.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_uid.sql index 5c8f1f2ec66..a43df88f8a8 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_uid.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--users_query-users_uid.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id WHERE o.org_id = 0 AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/legacy/user.go b/pkg/registry/apis/iam/legacy/user.go index 0319abc1520..24d3ab71b5e 100644 --- a/pkg/registry/apis/iam/legacy/user.go +++ b/pkg/registry/apis/iam/legacy/user.go @@ -165,7 +165,8 @@ func (s *legacySQLStore) queryUsers(ctx context.Context, sql *legacysql.LegacyDa for rows.Next() { u := user.User{} err = rows.Scan(&u.OrgID, &u.ID, &u.UID, &u.Login, &u.Email, &u.Name, - &u.Created, &u.Updated, &u.IsServiceAccount, &u.IsDisabled, &u.IsAdmin, + &u.Created, &u.Updated, &u.IsServiceAccount, &u.IsDisabled, &u.IsAdmin, &u.EmailVerified, + &u.IsProvisioned, &u.LastSeenAt, ) if err != nil { return res, err diff --git a/pkg/registry/apis/iam/legacy/users_query.sql b/pkg/registry/apis/iam/legacy/users_query.sql index 59d46d39400..5c66dbd9c54 100644 --- a/pkg/registry/apis/iam/legacy/users_query.sql +++ b/pkg/registry/apis/iam/legacy/users_query.sql @@ -1,5 +1,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name, - u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin + u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin, + u.email_verified, u.is_provisioned, u.last_seen_at FROM {{ .Ident .UserTable }} as u JOIN {{ .Ident .OrgUserTable }} as o ON u.id = o.user_id WHERE o.org_id = {{ .Arg .Query.OrgID }} AND NOT u.is_service_account diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 7e3a54f9ee1..7434577e69f 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -2,6 +2,7 @@ package iam import ( "context" + "maps" "strings" "github.com/prometheus/client_golang/prometheus" @@ -149,18 +150,12 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } func (b *IdentityAccessManagementAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - defs := legacyiamv0.GetOpenAPIDefinitions - if b.enableAuthZApis { - defs = func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - def1 := legacyiamv0.GetOpenAPIDefinitions(ref) - def2 := iamv0.GetOpenAPIDefinitions(ref) - for k, v := range def2 { - def1[k] = v - } - return def1 - } + return func(rc common.ReferenceCallback) map[string]common.OpenAPIDefinition { + dst := legacyiamv0.GetOpenAPIDefinitions(rc) + maps.Copy(dst, iamv0.GetOpenAPIDefinitions(rc)) + + return dst } - return defs } func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { diff --git a/pkg/registry/apis/iam/serviceaccount/rest_token.go b/pkg/registry/apis/iam/serviceaccount/rest_token.go index 49b5a1383c2..1cffc74f74c 100644 --- a/pkg/registry/apis/iam/serviceaccount/rest_token.go +++ b/pkg/registry/apis/iam/serviceaccount/rest_token.go @@ -9,7 +9,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" + claims "github.com/grafana/authlib/types" + legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -32,7 +33,7 @@ type LegacyTokenRest struct { // New implements rest.Storage. func (s *LegacyTokenRest) New() runtime.Object { - return &iamv0.UserTeamList{} + return &legacyiamv0.ServiceAccountTokenList{} } // Destroy implements rest.Storage. @@ -70,10 +71,10 @@ func (s *LegacyTokenRest) Connect(ctx context.Context, name string, options runt return } - list := &iamv0.ServiceAccountTokenList{Items: make([]iamv0.ServiceAccountToken, 0, len(res.Items))} + list := &legacyiamv0.ServiceAccountTokenList{Items: make([]legacyiamv0.ServiceAccountToken, 0, len(res.Items))} for _, t := range res.Items { - list.Items = append(list.Items, mapToToken(t)) + list.Items = append(list.Items, mapToToken(t, ns)) } list.Continue = common.OptionalFormatInt(res.Continue) @@ -92,7 +93,7 @@ func (s *LegacyTokenRest) ConnectMethods() []string { return []string{http.MethodGet} } -func mapToToken(t legacy.ServiceAccountToken) iamv0.ServiceAccountToken { +func mapToToken(t legacy.ServiceAccountToken, ns claims.NamespaceInfo) legacyiamv0.ServiceAccountToken { var expires, lastUsed *metav1.Time if t.Expires != nil { @@ -105,7 +106,7 @@ func mapToToken(t legacy.ServiceAccountToken) iamv0.ServiceAccountToken { lastUsed = &ts } - return iamv0.ServiceAccountToken{ + return legacyiamv0.ServiceAccountToken{ Name: t.Name, Expires: expires, LastUsed: lastUsed, diff --git a/pkg/registry/apis/iam/serviceaccount/store.go b/pkg/registry/apis/iam/serviceaccount/store.go index 7e8d6b18040..02e797cec95 100644 --- a/pkg/registry/apis/iam/serviceaccount/store.go +++ b/pkg/registry/apis/iam/serviceaccount/store.go @@ -10,6 +10,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" claims "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" @@ -61,7 +62,7 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { res, err := common.List( ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options), - func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.ServiceAccount], error) { + func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.ServiceAccount], error) { found, err := s.store.ListServiceAccounts(ctx, ns, legacy.ListServiceAccountsQuery{ Pagination: p, }) @@ -70,12 +71,12 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - items := make([]iamv0.ServiceAccount, 0, len(found.Items)) + items := make([]iamv0alpha1.ServiceAccount, 0, len(found.Items)) for _, sa := range found.Items { items = append(items, toSAItem(sa, ns.Value)) } - return &common.ListResponse[iamv0.ServiceAccount]{ + return &common.ListResponse[iamv0alpha1.ServiceAccount]{ Items: items, RV: found.RV, Continue: found.Continue, @@ -87,21 +88,21 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - obj := &iamv0.ServiceAccountList{Items: res.Items} + obj := &iamv0alpha1.ServiceAccountList{Items: res.Items} obj.Continue = common.OptionalFormatInt(res.Continue) obj.ResourceVersion = common.OptionalFormatInt(res.RV) return obj, nil } -func toSAItem(sa legacy.ServiceAccount, ns string) iamv0.ServiceAccount { - item := iamv0.ServiceAccount{ +func toSAItem(sa legacy.ServiceAccount, ns string) iamv0alpha1.ServiceAccount { + item := iamv0alpha1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: sa.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", sa.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(sa.Created), }, - Spec: iamv0.ServiceAccountSpec{ + Spec: iamv0alpha1.ServiceAccountSpec{ Title: sa.Name, Disabled: sa.Disabled, }, diff --git a/pkg/registry/apis/iam/team/store.go b/pkg/registry/apis/iam/team/store.go index 0db2b96b981..5da6b0c8099 100644 --- a/pkg/registry/apis/iam/team/store.go +++ b/pkg/registry/apis/iam/team/store.go @@ -11,6 +11,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" claims "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" @@ -64,7 +65,7 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { res, err := common.List( ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options), - func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.Team], error) { + func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.Team], error) { found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{ Pagination: p, }) @@ -73,12 +74,12 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - teams := make([]iamv0.Team, 0, len(found.Teams)) + teams := make([]iamv0alpha1.Team, 0, len(found.Teams)) for _, t := range found.Teams { teams = append(teams, toTeamObject(t, ns)) } - return &common.ListResponse[iamv0.Team]{ + return &common.ListResponse[iamv0alpha1.Team]{ Items: teams, RV: found.RV, Continue: found.Continue, @@ -90,7 +91,7 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, fmt.Errorf("failed to list teams: %w", err) } - list := &iamv0.TeamList{Items: res.Items} + list := &iamv0alpha1.TeamList{Items: res.Items} list.Continue = common.OptionalFormatInt(res.Continue) list.ResourceVersion = common.OptionalFormatInt(res.RV) @@ -119,18 +120,17 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO return &obj, nil } -func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0.Team { - obj := iamv0.Team{ +func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0alpha1.Team { + obj := iamv0alpha1.Team{ ObjectMeta: metav1.ObjectMeta{ Name: t.UID, Namespace: ns.Value, CreationTimestamp: metav1.NewTime(t.Created), ResourceVersion: strconv.FormatInt(t.Updated.UnixMilli(), 10), }, - Spec: iamv0.TeamSpec{ - Title: t.Name, - Email: t.Email, - InternalID: t.ID, + Spec: iamv0alpha1.TeamSpec{ + Title: t.Name, + Email: t.Email, }, } meta, _ := utils.MetaAccessor(&obj) diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/team/store_binding.go index 03e25a393fe..e11bbec15c0 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/team/store_binding.go @@ -11,6 +11,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" claims "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" @@ -102,8 +103,8 @@ func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion. return nil, err } - list := iamv0.TeamBindingList{ - Items: make([]iamv0.TeamBinding, 0, len(res.Bindings)), + list := iamv0alpha1.TeamBindingList{ + Items: make([]iamv0alpha1.TeamBinding, 0, len(res.Bindings)), } for _, b := range res.Bindings { @@ -116,7 +117,7 @@ func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion. return &list, nil } -func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0.TeamBinding { +func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0alpha1.TeamBinding { rv := time.Time{} ct := time.Now() @@ -129,15 +130,15 @@ func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0.Tea } } - return iamv0.TeamBinding{ + return iamv0alpha1.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: b.TeamUID, Namespace: ns.Value, ResourceVersion: strconv.FormatInt(rv.UnixMilli(), 10), CreationTimestamp: metav1.NewTime(ct), }, - Spec: iamv0.TeamBindingSpec{ - Team: iamv0.TeamRef{ + Spec: iamv0alpha1.TeamBindingSpec{ + TeamRef: iamv0alpha1.TeamBindingTeamRef{ Name: b.TeamUID, }, Subjects: mapToSubjects(b.Members), @@ -145,14 +146,11 @@ func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0.Tea } } -func mapToSubjects(members []legacy.TeamMember) []iamv0.TeamSubject { - out := make([]iamv0.TeamSubject, 0, len(members)) +func mapToSubjects(members []legacy.TeamMember) []iamv0alpha1.TeamBindingspecSubject { + out := make([]iamv0alpha1.TeamBindingspecSubject, 0, len(members)) for _, m := range members { - out = append(out, iamv0.TeamSubject{ - Identity: iamv0.IdentityRef{ - Type: claims.TypeUser, - Name: m.UserUID, - }, + out = append(out, iamv0alpha1.TeamBindingspecSubject{ + Name: m.UserUID, Permission: common.MapTeamPermission(m.Permission), }) } diff --git a/pkg/registry/apis/iam/user/rest_user_team.go b/pkg/registry/apis/iam/user/rest_user_team.go index f1e56bc177f..cc30081d0bb 100644 --- a/pkg/registry/apis/iam/user/rest_user_team.go +++ b/pkg/registry/apis/iam/user/rest_user_team.go @@ -7,7 +7,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" + legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -29,7 +29,7 @@ type LegacyUserTeamREST struct { // New implements rest.Storage. func (s *LegacyUserTeamREST) New() runtime.Object { - return &iamv0.UserTeamList{} + return &legacyiamv0.UserTeamList{} } // Destroy implements rest.Storage. @@ -62,7 +62,7 @@ func (s *LegacyUserTeamREST) Connect(ctx context.Context, name string, options r return } - list := &iamv0.UserTeamList{Items: make([]iamv0.UserTeam, 0, len(res.Items))} + list := &legacyiamv0.UserTeamList{Items: make([]legacyiamv0.UserTeam, 0, len(res.Items))} for _, m := range res.Items { list.Items = append(list.Items, mapToUserTeam(m)) @@ -84,12 +84,12 @@ func (s *LegacyUserTeamREST) ConnectMethods() []string { return []string{http.MethodGet} } -func mapToUserTeam(t legacy.UserTeam) iamv0.UserTeam { - return iamv0.UserTeam{ +func mapToUserTeam(t legacy.UserTeam) legacyiamv0.UserTeam { + return legacyiamv0.UserTeam{ Title: t.Name, - TeamRef: iamv0.TeamRef{ + TeamRef: legacyiamv0.TeamRef{ Name: t.UID, }, - Permission: common.MapTeamPermission(t.Permission), + Permission: common.MapUserTeamPermission(t.Permission), } } diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index 115bbb8f8d2..e9c45178845 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -3,6 +3,7 @@ package user import ( "context" "fmt" + "time" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -10,6 +11,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" claims "github.com/grafana/authlib/types" + iamv0alpha "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" @@ -18,6 +20,8 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) +const AnnoKeyLastSeenAt = "iam.grafana.app/lastSeenAt" + var ( _ rest.Scoper = (*LegacyStore)(nil) _ rest.SingularNameProvider = (*LegacyStore)(nil) @@ -62,7 +66,7 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { res, err := common.List( ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options), - func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.User], error) { + func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha.User], error) { found, err := s.store.ListUsers(ctx, ns, legacy.ListUserQuery{ Pagination: p, }) @@ -71,12 +75,12 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - users := make([]iamv0.User, 0, len(found.Users)) + users := make([]iamv0alpha.User, 0, len(found.Users)) for _, u := range found.Users { users = append(users, toUserItem(&u, ns.Value)) } - return &common.ListResponse[iamv0.User]{ + return &common.ListResponse[iamv0alpha.User]{ Items: users, RV: found.RV, Continue: found.Continue, @@ -88,7 +92,7 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - obj := &iamv0.UserList{Items: res.Items} + obj := &iamv0alpha.UserList{Items: res.Items} obj.Continue = common.OptionalFormatInt(res.Continue) obj.ResourceVersion = common.OptionalFormatInt(res.RV) return obj, nil @@ -116,25 +120,35 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO return &obj, nil } -func toUserItem(u *user.User, ns string) iamv0.User { - item := &iamv0.User{ +func toUserItem(u *user.User, ns string) iamv0alpha.User { + item := &iamv0alpha.User{ ObjectMeta: metav1.ObjectMeta{ Name: u.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(u.Created), }, - Spec: iamv0.UserSpec{ + Spec: iamv0alpha.UserSpec{ Name: u.Name, Login: u.Login, Email: u.Email, EmailVerified: u.EmailVerified, Disabled: u.IsDisabled, - InternalID: u.ID, + GrafanaAdmin: u.IsAdmin, + Provisioned: u.IsProvisioned, }, } obj, _ := utils.MetaAccessor(item) obj.SetUpdatedTimestamp(&u.Updated) + obj.SetAnnotation(AnnoKeyLastSeenAt, formatTime(&u.LastSeenAt)) obj.SetDeprecatedInternalID(u.ID) // nolint:staticcheck return *item } + +func formatTime(v *time.Time) string { + txt := "" + if v != nil && v.Unix() != 0 { + txt = v.UTC().Format(time.RFC3339) + } + return txt +} diff --git a/pkg/tests/apis/iam/iam_test.go b/pkg/tests/apis/iam/iam_test.go index 8bcd7d3742a..19d16d341c9 100644 --- a/pkg/tests/apis/iam/iam_test.go +++ b/pkg/tests/apis/iam/iam_test.go @@ -67,7 +67,8 @@ func TestIntegrationIdentity(t *testing.T) { "spec": { "email": "staff@Org1", "title": "staff" - } + }, + "status": {} } ] }`, found) @@ -84,20 +85,40 @@ func TestIntegrationIdentity(t *testing.T) { found = teamClient.SpecJSON(rsp) require.JSONEq(t, `[ { + "disabled": false, "email": "admin@localhost", - "login": "admin" + "emailVerified": false, + "grafanaAdmin": true, + "login": "admin", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "admin2-1", - "login": "admin2-1" + "emailVerified": false, + "grafanaAdmin": true, + "login": "admin2-1", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "editor-1", - "login": "editor-1" + "emailVerified": false, + "grafanaAdmin": false, + "login": "editor-1", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "viewer-1", - "login": "viewer-1" + "emailVerified": false, + "grafanaAdmin": false, + "login": "viewer-1", + "name": "", + "provisioned": false } ]`, found) @@ -114,20 +135,40 @@ func TestIntegrationIdentity(t *testing.T) { found = teamClient.SpecJSON(rsp) require.JSONEq(t, `[ { + "disabled": false, "email": "admin2-1", - "login": "admin2-1" + "emailVerified": false, + "grafanaAdmin": true, + "login": "admin2-1", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "admin2-2", - "login": "admin2-2" + "emailVerified": false, + "grafanaAdmin": false, + "login": "admin2-2", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "editor-2", - "login": "editor-2" + "emailVerified": false, + "grafanaAdmin": false, + "login": "editor-2", + "name": "", + "provisioned": false }, { + "disabled": false, "email": "viewer-2", - "login": "viewer-2" + "emailVerified": false, + "grafanaAdmin": false, + "login": "viewer-2", + "name": "", + "provisioned": false } ]`, found) }) diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index 3d70923422a..b1b2dc77570 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -95,27 +95,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList" } } } @@ -253,17 +253,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccount" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccount" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccount" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount" } } } @@ -321,7 +321,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserTeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountTokenList" } } } @@ -331,14 +331,14 @@ "x-kubernetes-group-version-kind": { "group": "iam.grafana.app", "version": "v0alpha1", - "kind": "UserTeamList" + "kind": "ServiceAccountTokenList" } }, "parameters": [ { "name": "name", "in": "path", - "description": "name of the UserTeamList", + "description": "name of the ServiceAccountTokenList", "required": true, "schema": { "type": "string", @@ -920,27 +920,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList" } } } @@ -1078,17 +1078,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBinding" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBinding" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBinding" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" } } } @@ -1146,27 +1146,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList" } } } @@ -1304,17 +1304,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Team" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.Team" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Team" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.Team" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Team" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.Team" } } } @@ -1421,27 +1421,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList" } } } @@ -1579,17 +1579,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.User" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.User" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.User" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.User" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.User" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.User" } } } @@ -1686,6 +1686,731 @@ }, "components": { "schemas": { + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "description": "Spec is the spec of the ServiceAccount", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "ServiceAccount", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "ServiceAccountList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountSpec": { + "type": "object", + "required": [ + "title", + "disabled" + ], + "properties": { + "disabled": { + "type": "boolean", + "default": false + }, + "title": { + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountstatusOperatorState" + } + ] + } + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccountstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.Team": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "description": "Spec is the spec of the Team", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "Team", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "description": "Spec is the spec of the TeamBinding", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "TeamBinding", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "TeamBindingList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingSpec": { + "type": "object", + "required": [ + "subjects", + "teamRef" + ], + "properties": { + "subjects": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingspecSubject" + } + ] + } + }, + "teamRef": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingTeamRef" + } + ] + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingstatusOperatorState" + } + ] + } + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingTeamRef": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Name is the unique identifier for a team.", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingspecSubject": { + "type": "object", + "required": [ + "name", + "permission" + ], + "properties": { + "name": { + "description": "uid of the identity", + "type": "string", + "default": "" + }, + "permission": { + "description": "permission of the identity in the team", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.Team" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "TeamList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamSpec": { + "type": "object", + "required": [ + "title", + "email" + ], + "properties": { + "email": { + "type": "string", + "default": "" + }, + "title": { + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamstatusOperatorState" + } + ] + } + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.User": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "description": "Spec is the spec of the User", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "User", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.User" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "UserList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserSpec": { + "type": "object", + "required": [ + "disabled", + "email", + "emailVerified", + "grafanaAdmin", + "login", + "name", + "provisioned" + ], + "properties": { + "disabled": { + "type": "boolean", + "default": false + }, + "email": { + "type": "string", + "default": "" + }, + "emailVerified": { + "type": "boolean", + "default": false + }, + "grafanaAdmin": { + "type": "boolean", + "default": false + }, + "login": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + }, + "provisioned": { + "description": "What to do with salt, rands and password?", + "type": "boolean", + "default": false + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserstatusOperatorState" + } + ] + } + } + } + }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured": { "type": "object", "additionalProperties": true, @@ -1895,104 +2620,21 @@ } } }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccount": { - "type": "object", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountSpec" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "ServiceAccount", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountList": { - "type": "object", - "required": [ - "items" - ], - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "items": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccount" - } - ] - } - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "ServiceAccountList", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountSpec": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "title": { - "type": "string" - } - } - }, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountToken": { "type": "object", "required": [ "created" ], "properties": { - "created": {}, - "expires": {}, - "lastUsed": {}, + "created": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "expires": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "lastUsed": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, "name": { "type": "string" }, @@ -2002,103 +2644,6 @@ } }, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountTokenList": { - "type": "object", - "required": [ - "items" - ], - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "items": { - "type": "array", - "items": { - "default": {} - } - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {} - } - } - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Team": { - "type": "object", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamSpec" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "Team", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBinding": { - "type": "object", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingSpec" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "TeamBinding", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingList": { "type": "object", "required": [ "items" @@ -2114,7 +2659,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBinding" + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.ServiceAccountToken" } ] } @@ -2135,73 +2680,7 @@ "x-kubernetes-group-version-kind": [ { "group": "iam.grafana.app", - "kind": "TeamBindingList", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamBindingSpec": { - "type": "object", - "properties": { - "subjects": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamSubject" - } - ] - } - }, - "team": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamRef" - } - ] - } - } - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamList": { - "type": "object", - "required": [ - "items" - ], - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "items": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Team" - } - ] - } - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "TeamList", + "kind": "ServiceAccountTokenList", "version": "v0alpha1" } ] @@ -2300,17 +2779,6 @@ } } }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamSpec": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "title": { - "type": "string" - } - } - }, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.TeamSubject": { "type": "object", "required": [ @@ -2319,12 +2787,7 @@ "properties": { "identity": { "description": "Identity is a reference to the identity of this subject.", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.IdentityRef" - } - ] + "default": {} }, "permission": { "description": "Permission subject has in team.\n\nPossible enum values:\n - `\"admin\"`\n - `\"member\"`", @@ -2336,104 +2799,6 @@ } } }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.User": { - "type": "object", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ] - }, - "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserSpec" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "User", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserList": { - "type": "object", - "required": [ - "items" - ], - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "items": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.User" - } - ] - } - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "UserList", - "version": "v0alpha1" - } - ] - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserSpec": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "email": { - "type": "string" - }, - "emailVerified": { - "type": "boolean" - }, - "login": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.UserTeam": { "type": "object", "properties": { @@ -2500,6 +2865,1499 @@ } ] }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRole": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the CoreRole", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec": { + "type": "object", + "required": [ + "title", + "version", + "group", + "permissions" + ], + "properties": { + "group": { + "type": "string", + "default": "" + }, + "permissions": { + "description": "delegatable?: bool created? updated?", + "type": "array", + "items": { + "default": {} + } + }, + "title": { + "description": "Display name of the role", + "type": "string", + "default": "" + }, + "version": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolespecPermission": { + "type": "object", + "required": [ + "action", + "scope" + ], + "properties": { + "action": { + "description": "RBAC action (e.g: \"dashbaords:read\")", + "type": "string", + "default": "" + }, + "scope": { + "description": "RBAC scope (e.g: \"dashboards:uid:dash1\")", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolestatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the GlobalRole", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the GlobalRoleBinding", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec": { + "type": "object", + "required": [ + "subjects", + "roleRef" + ], + "properties": { + "roleRef": { + "default": {} + }, + "subjects": { + "type": "array", + "items": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "description": "kind of role", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the role", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "description": "kind of the identity getting the permission", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the identity", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec": { + "type": "object", + "required": [ + "title", + "version", + "group", + "permissions" + ], + "properties": { + "group": { + "type": "string", + "default": "" + }, + "permissions": { + "description": "delegatable?: bool created? updated?", + "type": "array", + "items": { + "default": {} + } + }, + "title": { + "description": "Display name of the role", + "type": "string", + "default": "" + }, + "version": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolespecPermission": { + "type": "object", + "required": [ + "action", + "scope" + ], + "properties": { + "action": { + "description": "RBAC action (e.g: \"dashbaords:read\")", + "type": "string", + "default": "" + }, + "scope": { + "description": "RBAC scope (e.g: \"dashboards:uid:dash1\")", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolestatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermission": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the ResourcePermission", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec": { + "type": "object", + "required": [ + "resource", + "permissions" + ], + "properties": { + "permissions": { + "type": "array", + "items": { + "default": {} + } + }, + "resource": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecPermission": { + "type": "object", + "required": [ + "kind", + "name", + "verbs" + ], + "properties": { + "kind": { + "description": "kind of the identity getting the permission", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the identity getting the permission", + "type": "string", + "default": "" + }, + "verbs": { + "description": "list of actions granted to the user (e.g. \"admin\" or \"get\", \"update\")", + "type": "array", + "items": { + "type": "string", + "default": "" + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecResource": { + "type": "object", + "required": [ + "apiGroup", + "resource", + "name" + ], + "properties": { + "apiGroup": { + "description": "api group of the resource (e.g: \"folder.grafana.app\")", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the resource (e.g: \"fold1\")", + "type": "string", + "default": "" + }, + "resource": { + "description": "kind of the resource (e.g: \"folders\")", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Role": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the Role", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBinding": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the RoleBinding", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec": { + "type": "object", + "required": [ + "subjects", + "roleRef" + ], + "properties": { + "roleRef": { + "default": {} + }, + "subjects": { + "type": "array", + "items": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "description": "kind of role", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the role", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "description": "kind of the identity getting the permission", + "type": "string", + "default": "" + }, + "name": { + "description": "uid of the identity", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec": { + "type": "object", + "required": [ + "title", + "version", + "group", + "permissions" + ], + "properties": { + "group": { + "type": "string", + "default": "" + }, + "permissions": { + "description": "delegatable?: bool created? updated?", + "type": "array", + "items": { + "default": {} + } + }, + "title": { + "description": "Display name of the role", + "type": "string", + "default": "" + }, + "version": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolespecPermission": { + "type": "object", + "required": [ + "action", + "scope" + ], + "properties": { + "action": { + "description": "RBAC action (e.g: \"dashbaords:read\")", + "type": "string", + "default": "" + }, + "scope": { + "description": "RBAC scope (e.g: \"dashboards:uid:dash1\")", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolestatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the ServiceAccount", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec": { + "type": "object", + "required": [ + "title", + "disabled" + ], + "properties": { + "disabled": { + "type": "boolean", + "default": false + }, + "title": { + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the Team", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the TeamBinding", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec": { + "type": "object", + "required": [ + "subjects", + "teamRef" + ], + "properties": { + "subjects": { + "type": "array", + "items": { + "default": {} + } + }, + "teamRef": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Name is the unique identifier for a team.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject": { + "type": "object", + "required": [ + "name", + "permission" + ], + "properties": { + "name": { + "description": "uid of the identity", + "type": "string", + "default": "" + }, + "permission": { + "description": "permission of the identity in the team", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec": { + "type": "object", + "required": [ + "title", + "email" + ], + "properties": { + "email": { + "type": "string", + "default": "" + }, + "title": { + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User": { + "type": "object", + "required": [ + "metadata", + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "description": "Spec is the spec of the User", + "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec": { + "type": "object", + "required": [ + "disabled", + "email", + "emailVerified", + "grafanaAdmin", + "login", + "name", + "provisioned" + ], + "properties": { + "disabled": { + "type": "boolean", + "default": false + }, + "email": { + "type": "string", + "default": "" + }, + "emailVerified": { + "type": "boolean", + "default": false + }, + "grafanaAdmin": { + "type": "boolean", + "default": false + }, + "login": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + }, + "provisioned": { + "description": "What to do with salt, rands and password?", + "type": "boolean", + "default": false + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": { + "type": "object", + "properties": { + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "default": {} + } + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string", + "default": "" + }, + "state": { + "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + "type": "string", + "default": "" + } + } + }, "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { "description": "APIResource specifies the name of a resource and whether it is namespaced.", "type": "object", From c02f76d89af763d0abf7f1afa036f516ec00315c Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 09:08:21 +0000 Subject: [PATCH 07/67] I18n: Download translations from Crowdin (#107601) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/de-DE/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/es-ES/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/fr-FR/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/hu-HU/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/id-ID/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/it-IT/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/ja-JP/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/ko-KR/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/nl-NL/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/pl-PL/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/pt-BR/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/pt-PT/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/ru-RU/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/sv-SE/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/tr-TR/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/zh-Hans/grafana.json | 45 ++++++++++++++++++++++++++++- public/locales/zh-Hant/grafana.json | 45 ++++++++++++++++++++++++++++- 18 files changed, 792 insertions(+), 18 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 6a1e7dbaab0..fbef2015518 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -8408,6 +8408,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8431,7 +8443,6 @@ "label-wrap-lines": "Zalomit řádky" }, "log-details": { - "close": "Zavřít podrobnosti protokolu", "fields": "Pole", "links": "Odkazy", "log-line": "Řádek protokolu", @@ -8457,6 +8468,35 @@ "show-more": "zobrazit více", "tooltip-error": "Chyba: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Kopírovat odkaz na řádek protokolu", "copy-log": "Kopírovat řádek protokolu", @@ -8583,6 +8623,7 @@ "un-themed-log-details": { "aria-label-data-links": "Propojení dat", "aria-label-fields": "Pole", + "aria-label-line": "", "aria-label-log-level": "Úroveň protokolu", "aria-label-no-details": "Žádné podrobnosti" }, @@ -11458,6 +11499,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternativní text, který se zobrazí, pokud nelze zobrazit obrázek, nebo pro uživatele, kteří používají čtečku obrazovky", "description-title-text": "Text, který se zobrazí po najetí kurzoru na obrázek", @@ -11478,6 +11520,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 5ce02f8a0d8..27fd33084fa 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Zeilen umbrechen" }, "log-details": { - "close": "Logdetails schließen", "fields": "Felder", "links": "Links", "log-line": "Protokollzeile", @@ -8405,6 +8416,35 @@ "show-more": "mehr anzeigen", "tooltip-error": "Fehler: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Link zur Protokollzeile kopieren", "copy-log": "Protokollzeile kopieren", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Datenlinks", "aria-label-fields": "Felder", + "aria-label-line": "", "aria-label-log-level": "Log-Stufe", "aria-label-no-details": "Keine Angaben" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternativer Text, der angezeigt wird, wenn ein Bild nicht angezeigt werden kann, oder für Nutzer, die einen Screenreader verwenden", "description-title-text": "Text, der angezeigt wird, wenn über dem Bild der Mauszeiger bewegt wird", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index fbdb3450d6e..d70b63ee4e2 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Ajustar líneas" }, "log-details": { - "close": "Cerrar detalles del log", "fields": "Campos", "links": "Enlaces", "log-line": "Línea de registro", @@ -8405,6 +8416,35 @@ "show-more": "mostrar más", "tooltip-error": "Error: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Copiar enlace a la línea de registro", "copy-log": "Copiar línea de registro", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Enlaces de datos", "aria-label-fields": "Campos", + "aria-label-line": "", "aria-label-log-level": "Nivel de log", "aria-label-no-details": "Sin detalles" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Texto alternativo que se mostrará si no se puede visualizar una imagen o para los usuarios que utilizan un lector de pantalla", "description-title-text": "Texto que se mostrará cuando se pase el cursor por encima de la imagen", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index a1254bb741a..e82300c3932 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Enrouler les lignes" }, "log-details": { - "close": "Fermer les détails du journal", "fields": "Champs", "links": "Liens", "log-line": "Ligne du journal", @@ -8405,6 +8416,35 @@ "show-more": "afficher plus", "tooltip-error": "Erreur : {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Copier le lien vers la ligne de journal", "copy-log": "Copier la ligne de journal", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Données de liaison", "aria-label-fields": "Champs", + "aria-label-line": "", "aria-label-log-level": "Niveau de journal", "aria-label-no-details": "Aucune information" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Texte alternatif qui sera affiché si une image ne peut pas être affichée ou pour les utilisateurs qui utilisent un lecteur d’écran", "description-title-text": "Texte qui sera affiché lorsque l’image est survolée par un curseur", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index cc9c3a2967b..4dc3348eb55 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Sortörés" }, "log-details": { - "close": "Napló részleteinek bezárása", "fields": "Mezők", "links": "Hivatkozások", "log-line": "Naplósor", @@ -8405,6 +8416,35 @@ "show-more": "több megjelenítése", "tooltip-error": "Hiba: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Hivatkozás másolása a naplósorba", "copy-log": "Naplósor másolása", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Adathivatkozások", "aria-label-fields": "Mezők", + "aria-label-line": "", "aria-label-log-level": "Naplózási szint", "aria-label-no-details": "Nincsenek részletek" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Helyettesítő szöveg, amely akkor jelenik meg, ha egy kép nem jeleníthető meg, és amely a képernyőolvasót használó felhasználóknak hasznos", "description-title-text": "Szöveg, amely akkor jelenik meg, amikor a kép fölé viszik a kurzort", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index f63f0f6b315..e61bb28b431 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -8330,6 +8330,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8353,7 +8365,6 @@ "label-wrap-lines": "Terapkan wrap pada baris" }, "log-details": { - "close": "Tutup detail log", "fields": "Bidang", "links": "Tautan", "log-line": "Baris log", @@ -8379,6 +8390,35 @@ "show-more": "tampilkan lebih banyak", "tooltip-error": "Kesalahan: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Salin tautan ke baris log", "copy-log": "Salin baris log", @@ -8505,6 +8545,7 @@ "un-themed-log-details": { "aria-label-data-links": "Tautan data", "aria-label-fields": "Bidang", + "aria-label-line": "", "aria-label-log-level": "Level log", "aria-label-no-details": "Tidak ada detail" }, @@ -11353,6 +11394,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Teks alternatif yang akan ditampilkan jika gambar tidak dapat ditampilkan atau untuk pengguna yang menggunakan pembaca layar", "description-title-text": "Teks yang akan ditampilkan saat gambar diarahkan oleh kursor", @@ -11373,6 +11415,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 03f35d3bbc5..df92b4c98c3 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "A capo" }, "log-details": { - "close": "Chiudi i dettagli del registro", "fields": "Campi", "links": "Link", "log-line": "Riga registro", @@ -8405,6 +8416,35 @@ "show-more": "mostra altro", "tooltip-error": "Errore: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Copia il link alla riga del registro", "copy-log": "Copia la riga del registro", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Collegamenti dati", "aria-label-fields": "Campi", + "aria-label-line": "", "aria-label-log-level": "Livello registro", "aria-label-no-details": "Nessun dettaglio" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "", "description-title-text": "", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 47a76879ee1..091d62623b3 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -8330,6 +8330,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8353,7 +8365,6 @@ "label-wrap-lines": "行を折り返す" }, "log-details": { - "close": "ログの詳細を閉じる", "fields": "フィールド", "links": "リンク", "log-line": "ログ行", @@ -8379,6 +8390,35 @@ "show-more": "さらに表示する", "tooltip-error": "エラー:{{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "ログ行へのリンクをコピー", "copy-log": "ログ行をコピー", @@ -8505,6 +8545,7 @@ "un-themed-log-details": { "aria-label-data-links": "データリンク", "aria-label-fields": "フィールド", + "aria-label-line": "", "aria-label-log-level": "ログレベル", "aria-label-no-details": "詳細なし" }, @@ -11353,6 +11394,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "画像が表示できない場合やスクリーンリーダーを使用するユーザーのために表示される代替テキスト", "description-title-text": "画像にカーソルを合わせたときに表示されるテキスト", @@ -11373,6 +11415,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 579cbc69bb3..04ff5c03831 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -8330,6 +8330,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8353,7 +8365,6 @@ "label-wrap-lines": "줄 바꿈" }, "log-details": { - "close": "로그 세부 정보 닫기", "fields": "필드", "links": "링크", "log-line": "로그 라인", @@ -8379,6 +8390,35 @@ "show-more": "더 보기", "tooltip-error": "오류: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "링크를 로그 라인에 복사", "copy-log": "로그 라인 복사", @@ -8505,6 +8545,7 @@ "un-themed-log-details": { "aria-label-data-links": "데이터 링크", "aria-label-fields": "필드", + "aria-label-line": "", "aria-label-log-level": "로그 수준", "aria-label-no-details": "세부 정보 없음" }, @@ -11353,6 +11394,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "이미지를 표시할 수 없거나 화면 리더를 이용하는 사용자를 위해 표시되는 대체 텍스트", "description-title-text": "이미지에 커서를 가져갔을 때 표시되는 텍스트", @@ -11373,6 +11415,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index a29386de438..5b76736c0b8 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Lijnen omsluiten" }, "log-details": { - "close": "Logboekdetails sluiten", "fields": "Velden", "links": "Links", "log-line": "Logregel", @@ -8405,6 +8416,35 @@ "show-more": "meer", "tooltip-error": "Fout: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Link naar logboekregel kopiëren", "copy-log": "Logboekregel kopiëren", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Gegevenslinks", "aria-label-fields": "Velden", + "aria-label-line": "", "aria-label-log-level": "Logniveau", "aria-label-no-details": "Geen details" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternatieve tekst die wordt weergegeven als een afbeelding niet kan worden weergegeven of voor gebruikers die een schermlezer gebruiken", "description-title-text": "Tekst die wordt weergegeven wanneer de afbeelding wordt aangeraakt door een cursor", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 4b87289505b..56be073ac9a 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -8408,6 +8408,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8431,7 +8443,6 @@ "label-wrap-lines": "Zawijaj wiersze" }, "log-details": { - "close": "Zamknij szczegóły dziennika", "fields": "Pola", "links": "Linki", "log-line": "Wiersz logu", @@ -8457,6 +8468,35 @@ "show-more": "pokaż więcej", "tooltip-error": "Błąd: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Kopiuj link do wiersza logu", "copy-log": "Kopiuj wiersz logu", @@ -8583,6 +8623,7 @@ "un-themed-log-details": { "aria-label-data-links": "Połączenia danych", "aria-label-fields": "Pola", + "aria-label-line": "", "aria-label-log-level": "Poziom logów", "aria-label-no-details": "Brak szczegółów" }, @@ -11458,6 +11499,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternatywny tekst wyświetlany, jeśli obraz nie może być wyświetlony, lub w przypadku użytkowników korzystających z czytnika ekranu", "description-title-text": "Tekst wyświetlany po najechaniu kursorem na obraz", @@ -11478,6 +11520,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 4a068661cc7..631599ed56e 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Aplicar quebra de linha" }, "log-details": { - "close": "Fechar detalhes do log", "fields": "Campos", "links": "Links", "log-line": "Linha de log", @@ -8405,6 +8416,35 @@ "show-more": "exibir mais", "tooltip-error": "Erro: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Copiar link para a linha do log", "copy-log": "Copiar linha do log", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Links de dados", "aria-label-fields": "Campos", + "aria-label-line": "", "aria-label-log-level": "Nível de log", "aria-label-no-details": "Sem detalhes" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Texto alternativo que será exibido se uma imagem não puder ser exibida ou para usuários que usam leitor de tela", "description-title-text": "Texto que será exibido ao passar o cursor sobre a imagem", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index b6aa70bb2b0..3ec77590570 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Quebra de linhas" }, "log-details": { - "close": "Fechar detalhes do registo", "fields": "Campos", "links": "Links", "log-line": "Linha de registo", @@ -8405,6 +8416,35 @@ "show-more": "mostrar mais", "tooltip-error": "Erro: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Copiar link para a linha de registo", "copy-log": "Copiar linha de registo", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Ligações de dados", "aria-label-fields": "Campos", + "aria-label-line": "", "aria-label-log-level": "Nível de registo", "aria-label-no-details": "Sem detalhes" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Texto alternativo que será exibido se uma imagem não puder ser exibida ou para utilizadores que utilizem um leitor de ecrã", "description-title-text": "Texto que será exibido quando o cursor passar por cima da imagem", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index a38ed0702b1..a6df6bd1151 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -8408,6 +8408,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8431,7 +8443,6 @@ "label-wrap-lines": "Переносить строки" }, "log-details": { - "close": "Закрыть сведения о журнале", "fields": "Поля", "links": "Ссылки", "log-line": "Строка журнала", @@ -8457,6 +8468,35 @@ "show-more": "больше", "tooltip-error": "Ошибка: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Копировать ссылку в строку журнала", "copy-log": "Копировать строку журнала", @@ -8583,6 +8623,7 @@ "un-themed-log-details": { "aria-label-data-links": "Ссылки на данные", "aria-label-fields": "Поля", + "aria-label-line": "", "aria-label-log-level": "Уровень журнала", "aria-label-no-details": "Нет сведений" }, @@ -11458,6 +11499,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Альтернативный текст, который будет отображаться, если невозможно показать изображение, или для пользователей, которые используют программу чтения с экрана", "description-title-text": "Текст, который будет отображаться при наведении курсора на изображение", @@ -11478,6 +11520,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 55114795598..fa1f9cdf762 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Radbryt linjer" }, "log-details": { - "close": "Stäng loggdetaljer", "fields": "Fält", "links": "Länkar", "log-line": "Loggrad", @@ -8405,6 +8416,35 @@ "show-more": "visa mer", "tooltip-error": "Fel: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Kopiera länk till loggrad", "copy-log": "Kopiera loggrad", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Datalänkar", "aria-label-fields": "Fält", + "aria-label-line": "", "aria-label-log-level": "Loggnivå", "aria-label-no-details": "Inga uppgiftger" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternativ text som kommer att visas om en bild inte kan visas eller för användare som använder en skärmläsare", "description-title-text": "Text som visas när muspekaren hålls över bilden", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index af48be053be..6f3668386e3 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -8356,6 +8356,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8379,7 +8391,6 @@ "label-wrap-lines": "Satırları kaydır" }, "log-details": { - "close": "Günlük ayrıntılarını kapat", "fields": "Alanlar", "links": "Bağlantılar", "log-line": "Günlük satırı", @@ -8405,6 +8416,35 @@ "show-more": "daha fazla göster", "tooltip-error": "Hata: {{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "Bağlantıyı günlük satırına kopyala", "copy-log": "Günlük satırını kopyala", @@ -8531,6 +8571,7 @@ "un-themed-log-details": { "aria-label-data-links": "Veri bağlantıları", "aria-label-fields": "Alanlar", + "aria-label-line": "", "aria-label-log-level": "Günlük kaydı seviyesi", "aria-label-no-details": "Ayrıntı yok" }, @@ -11388,6 +11429,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Görsel görüntülenemediğinde veya ekran okuyucu kullanan kullanıcılar için gösterilecek alternatif metin", "description-title-text": "İmleç görselin üzerine getirildiğinde görüntülenecek metin", @@ -11408,6 +11450,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 1e1b0aa3b0e..44fe3343c4a 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -8330,6 +8330,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8353,7 +8365,6 @@ "label-wrap-lines": "多行显示" }, "log-details": { - "close": "关闭日志详情", "fields": "字段", "links": "链接", "log-line": "日志行", @@ -8379,6 +8390,35 @@ "show-more": "展开", "tooltip-error": "错误:{{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "将链接复制到日志行", "copy-log": "复制日志行", @@ -8505,6 +8545,7 @@ "un-themed-log-details": { "aria-label-data-links": "数据关联", "aria-label-fields": "字段", + "aria-label-line": "", "aria-label-log-level": "日志级别", "aria-label-no-details": "没有详情" }, @@ -11353,6 +11394,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "在图片无法显示或用户使用屏幕阅读器时,将显示的替代文本", "description-title-text": "光标悬停在图片上时将显示的文本", @@ -11373,6 +11415,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 826ce308fed..86fc428f25a 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -8330,6 +8330,18 @@ "description-enable-infinite-scrolling": "", "description-enable-syntax-highlighting": "", "description-show-controls": "", + "fields": { + "type": { + "loki": { + "indexed-label": "", + "indexed-label-plural": "", + "parsed-label-plural": "", + "parsedl-label": "", + "structured-metadata": "", + "structured-metadata-plural": "" + } + } + }, "font-size-options": { "label-default": "", "label-small": "" @@ -8353,7 +8365,6 @@ "label-wrap-lines": "換行" }, "log-details": { - "close": "關閉紀錄詳細資訊", "fields": "欄位", "links": "連結", "log-line": "日誌行", @@ -8379,6 +8390,35 @@ "show-more": "顯示更多", "tooltip-error": "錯誤:{{errorMessage}}" }, + "log-line-details": { + "clear-search": "", + "close": "", + "copy-shortlink": "", + "copy-to-clipboard": "", + "fields": { + "adhoc-statistics": "", + "copy-value-to-clipboard": "", + "filter-out": "", + "filter-out-query": "", + "toggle-field-button": { + "field-instead-message": "", + "hide-this-field": "" + } + }, + "fields-section": "", + "hide-log-line": "", + "links-section": "", + "log-line-section": "", + "no-details": "", + "pin-line": "", + "search": { + "no-results": "" + }, + "search-placeholder": "", + "show-context": "", + "show-log-line": "", + "unpin-line": "" + }, "log-line-menu": { "copy-link": "將連結複製到日誌行", "copy-log": "複製日誌行", @@ -8505,6 +8545,7 @@ "un-themed-log-details": { "aria-label-data-links": "資料連結", "aria-label-fields": "欄位", + "aria-label-line": "", "aria-label-log-level": "紀錄層級", "aria-label-no-details": "無詳細資料" }, @@ -11353,6 +11394,7 @@ "description-count-rows": "", "description-fields": "", "description-min-column-width": "", + "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "若圖片無法顯示或為使用螢幕閱讀器的使用者顯示的替代文字", "description-title-text": "滑鼠移至圖片上方時所顯示的文字", @@ -11373,6 +11415,7 @@ "name-min-column-width": "", "name-show-table-footer": "", "name-show-table-header": "", + "name-wrap-header-text": "", "placeholder-column-width": "", "placeholder-fields": "" }, From 47e5bd23163e6037fc0741068ac78930bba2769f Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Fri, 4 Jul 2025 12:26:43 +0300 Subject: [PATCH 08/67] TimeSeries/Trend: Fix infinite loop (#107615) --- public/app/core/components/TimeSeries/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/TimeSeries/utils.ts b/public/app/core/components/TimeSeries/utils.ts index d71e3246dab..407bd92ed66 100644 --- a/public/app/core/components/TimeSeries/utils.ts +++ b/public/app/core/components/TimeSeries/utils.ts @@ -394,13 +394,13 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ else { // scan right let leftIdx = 0; - while (yData[leftIdx] == null) { + while (yData[leftIdx] === null) { leftIdx++; } // scan left let rightIdx = yData.length - 1; - while (rightIdx >= leftIdx && yData[rightIdx] == null) { + while (rightIdx >= leftIdx && yData[rightIdx] === null) { rightIdx--; } From 4d93380753ca500c4aaa6306e0dfc348ec46f1ef Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 4 Jul 2025 11:35:52 +0100 Subject: [PATCH 09/67] Storybook: Deploy PR previews (#107322) * Create workflow to build storybook * rename action, gate to non-forks * fix my branch name * add upload to gcs * fix permissions * login first * try prod env * remove login * clean up GHA * try to upload storybook to better path * better cache node modules * specify parent: false * write summary * fix summary url * comment * add comment * fix commit hash * add issue number to new comment * remove comment stuff * codeowners * change deploy name preview * fix --- .github/CODEOWNERS | 1 + .../workflows/deploy-storybook-preview.yml | 93 +++++++++++++++++++ packages/grafana-ui/.storybook/preview.ts | 1 + 3 files changed, 95 insertions(+) create mode 100644 .github/workflows/deploy-storybook-preview.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3e78adb40cb..13c155ac1af 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -835,6 +835,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-verify.yml @grafana/grafana-frontend-platform +/.github/workflows/deploy-storybook-preview.yml @grafana/grafana-frontend-platform /.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform /.github/workflows/pr-go-workspace-check.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-dependabot-update-go-workspace.yml @grafana/grafana-app-platform-squad diff --git a/.github/workflows/deploy-storybook-preview.yml b/.github/workflows/deploy-storybook-preview.yml new file mode 100644 index 00000000000..a1fecce67cb --- /dev/null +++ b/.github/workflows/deploy-storybook-preview.yml @@ -0,0 +1,93 @@ +name: Deploy Storybook preview + +on: + pull_request: + paths: + - 'packages/grafana-ui/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + deploy-storybook-preview: + name: Deploy Storybook preview + runs-on: ubuntu-latest + # Don't run from forks for the moment. If we find this useful we can do the workflow_run dance + # to make it work for forks. + if: github.event.pull_request.head.repo.fork == false + permissions: + contents: read + id-token: write + + env: + BUCKET_NAME: grafana-storybook-previews + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + key: node_modules-${{ hashFiles('yarn.lock') }} + restore-keys: | + node_modules- + + - name: Install dependencies + env: + # If the PR isn't from a fork then don't use the slower yarn checks + YARN_ENABLE_HARDENED_MODE: ${{ github.event.pull_request.head.repo.fork == false && '1' || '0' }} + run: yarn install --immutable + + - name: Build storybook + run: yarn storybook:build + + # Create the GCS folder name for the preview. Creates a consistent name for all deploys for the PR. + # Matches format of `pr__`. + # Where `SANITIZED_BRANCH` is the branch name with only alphanumeric and hyphens, limited to 30 characters. + - name: Create deploy name + id: create-deploy-name + env: + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Convert branch name to only contain alphanumeric and hyphens + SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr -cs "[:alnum:]-" "-" | sed "s/^-//;s/-$//") + + # Check if SANITIZED_BRANCH is empty and fail if it is + if [ -z "$SANITIZED_BRANCH" ]; then + echo "Error: Branch name resulted in empty string after sanitization" + exit 1 + fi + + echo "deploy-name=pr_${PR_NUMBER}_${SANITIZED_BRANCH:0:30}" >> "$GITHUB_OUTPUT" + + - name: Upload Storybook + uses: grafana/shared-workflows/actions/push-to-gcs@main + with: + environment: prod + bucket: ${{ env.BUCKET_NAME }} + bucket_path: ${{ steps.create-deploy-name.outputs.deploy-name }} + path: packages/grafana-ui/dist/storybook + service_account: github-gf-storybook-preview@grafanalabs-workload-identity.iam.gserviceaccount.com + parent: false + + - name: Write summary + env: + DEPLOY_NAME: ${{ steps.create-deploy-name.outputs.deploy-name }} + run: | + echo "## Storybook preview deployed! 🚀" >> $GITHUB_STEP_SUMMARY + echo "Check it out at https://storage.googleapis.com/${BUCKET_NAME}/${DEPLOY_NAME}/index.html" >> $GITHUB_STEP_SUMMARY diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index 41311f34244..93892fae443 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -52,6 +52,7 @@ if (process.env.NODE_ENV === 'development') { initialize({ onUnhandledRequest: 'bypass', serviceWorker: { + // Important! The path must be relative to work when we deploy storybook to subpaths (e.g. /ui/canary) url: 'mockServiceWorker.js', }, }); From dcb853d3099c08b74370fcc965342f035dd6d6cc Mon Sep 17 00:00:00 2001 From: "Arati R." <33031346+suntala@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:48:27 +0200 Subject: [PATCH 10/67] Unified Search: Remove resource-server-specific methods from distributor (#107607) * Remove resource-server-specific methods from distributor * Remove BlobStoreServer interface implementation from distributor --- .../resource/search_server_distributor.go | 95 ------------------- 1 file changed, 95 deletions(-) diff --git a/pkg/storage/unified/resource/search_server_distributor.go b/pkg/storage/unified/resource/search_server_distributor.go index 4fbb7083aca..2febf5416ab 100644 --- a/pkg/storage/unified/resource/search_server_distributor.go +++ b/pkg/storage/unified/resource/search_server_distributor.go @@ -41,11 +41,9 @@ func ProvideSearchDistributorServer(cfg *setting.Cfg, features featuremgmt.Featu grpcServer := grpcHandler.GetServer() - resourcepb.RegisterResourceStoreServer(grpcServer, distributorServer) // resourcepb.RegisterBulkStoreServer(grpcServer, distributorServer) resourcepb.RegisterResourceIndexServer(grpcServer, distributorServer) resourcepb.RegisterManagedObjectIndexServer(grpcServer, distributorServer) - resourcepb.RegisterBlobStoreServer(grpcServer, distributorServer) grpc_health_v1.RegisterHealthServer(grpcServer, healthService) _, err = grpcserver.ProvideReflectionService(cfg, grpcHandler) if err != nil { @@ -106,81 +104,6 @@ func (ds *distributorServer) GetStats(ctx context.Context, r *resourcepb.Resourc return client.GetStats(ctx, r) } -func (ds *distributorServer) Read(ctx context.Context, r *resourcepb.ReadRequest) (*resourcepb.ReadResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Key.Namespace, "Read") - if err != nil { - return nil, err - } - - return client.Read(ctx, r) -} - -func (ds *distributorServer) Create(ctx context.Context, r *resourcepb.CreateRequest) (*resourcepb.CreateResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Key.Namespace, "Create") - if err != nil { - return nil, err - } - - return client.Create(ctx, r) -} - -func (ds *distributorServer) Update(ctx context.Context, r *resourcepb.UpdateRequest) (*resourcepb.UpdateResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Key.Namespace, "Update") - if err != nil { - return nil, err - } - - return client.Update(ctx, r) -} - -func (ds *distributorServer) Delete(ctx context.Context, r *resourcepb.DeleteRequest) (*resourcepb.DeleteResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Key.Namespace, "Delete") - if err != nil { - return nil, err - } - - return client.Delete(ctx, r) -} - -func (ds *distributorServer) List(ctx context.Context, r *resourcepb.ListRequest) (*resourcepb.ListResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Options.Key.Namespace, "List") - if err != nil { - return nil, err - } - - return client.List(ctx, r) -} - -func (ds *distributorServer) Watch(r *resourcepb.WatchRequest, srv resourcepb.ResourceStore_WatchServer) error { - // r -> consumer watch request - // srv -> stream connection with consumer - ctx := srv.Context() - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Options.Key.Namespace, "Watch") - if err != nil { - return err - } - - // watchClient -> stream connection with storage-api pod - watchClient, err := client.Watch(ctx, r) - if err != nil { - return err - } - - // WARNING - // in Watch, all messages flow from the resource server (watchClient) to the consumer (srv) - // but since this is a streaming connection, in theory the consumer could also send a message to the server - // however for the sake of simplicity we are not handling it here - // but if we decide to handle bi-directional message passing in this method, we will need to update this - // we also never handle EOF err, as the server never closes the connection willingly - for { - msg, err := watchClient.Recv() - if err != nil { - return err - } - _ = srv.Send(msg) - } -} - // TODO implement this if we want to support it in cloud // func (ds *DistributorServer) BulkProcess(srv BulkStore_BulkProcessServer) error { // return nil @@ -204,24 +127,6 @@ func (ds *distributorServer) ListManagedObjects(ctx context.Context, r *resource return client.ListManagedObjects(ctx, r) } -func (ds *distributorServer) PutBlob(ctx context.Context, r *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Resource.Namespace, "PutBlob") - if err != nil { - return nil, err - } - - return client.PutBlob(ctx, r) -} - -func (ds *distributorServer) GetBlob(ctx context.Context, r *resourcepb.GetBlobRequest) (*resourcepb.GetBlobResponse, error) { - ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Resource.Namespace, "GetBlob") - if err != nil { - return nil, err - } - - return client.GetBlob(ctx, r) -} - func (ds *distributorServer) getClientToDistributeRequest(ctx context.Context, namespace string, methodName string) (context.Context, ResourceClient, error) { ringHasher := fnv.New32a() _, err := ringHasher.Write([]byte(namespace)) From 3ba2457a7090b2b76f4b368c8a9db835d0bec66c Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 4 Jul 2025 13:45:30 +0200 Subject: [PATCH 11/67] Query splitting: Interpolate queries at the start of the process (#107534) * Loki query splitting: interpolate queries before execution * Update tests * Prettier * shardQuerySplitting: remove unnecessary call --- .../datasource/loki/querySplitting.test.ts | 15 ++++++++ .../plugins/datasource/loki/querySplitting.ts | 5 ++- .../loki/shardQuerySplitting.test.ts | 37 +++++++++++++------ .../datasource/loki/shardQuerySplitting.ts | 6 +-- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/public/app/plugins/datasource/loki/querySplitting.test.ts b/public/app/plugins/datasource/loki/querySplitting.test.ts index e57c3053c32..82a585739aa 100644 --- a/public/app/plugins/datasource/loki/querySplitting.test.ts +++ b/public/app/plugins/datasource/loki/querySplitting.test.ts @@ -72,6 +72,21 @@ describe('runSplitQuery()', () => { }); }); + test('Interpolates queries before execution', async () => { + const request = createRequest([{ expr: 'count_over_time({a="b"}[$__auto])', refId: 'A', step: '$step' }]); + datasource = createLokiDatasource({ + replace: (input = '') => { + return input.replace('$__auto', '5m').replace('$step', '5m'); + }, + getVariables: () => [], + }); + jest.spyOn(datasource, 'runQuery').mockReturnValue(of({ data: [] })); + await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => { + expect(jest.mocked(datasource.runQuery).mock.calls[0][0].targets[0].expr).toBe('count_over_time({a="b"}[5m])'); + expect(jest.mocked(datasource.runQuery).mock.calls[0][0].targets[0].step).toBe('5m'); + }); + }); + test('Skips partial updates as an option', async () => { await expect(runSplitQuery(datasource, request, { skipPartialUpdates: true })).toEmitValuesWith((emitted) => { // 3 days, 3 chunks, 3 requests. diff --git a/public/app/plugins/datasource/loki/querySplitting.ts b/public/app/plugins/datasource/loki/querySplitting.ts index 069ed4e7b90..d0fd1678455 100644 --- a/public/app/plugins/datasource/loki/querySplitting.ts +++ b/public/app/plugins/datasource/loki/querySplitting.ts @@ -293,7 +293,10 @@ export function runSplitQuery( request: DataQueryRequest, options: QuerySplittingOptions = {} ) { - const queries = request.targets.filter((query) => !query.hide).filter((query) => query.expr); + const queries = request.targets + .filter((query) => !query.hide) + .filter((query) => query.expr) + .map((query) => datasource.applyTemplateVariables(query, request.scopedVars, request.filters)); const [nonSplittingQueries, normalQueries] = partition(queries, (query) => !querySupportsSplitting(query)); const [logQueries, metricQueries] = partition(normalQueries, (query) => isLogsQuery(query.expr)); diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts index 566eddeae81..04cb8c15055 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts @@ -53,11 +53,9 @@ describe('runShardSplitQuery()', () => { request = createRequest([{ expr: '$SELECTOR', refId: 'A', direction: LokiQueryDirection.Scan }]); datasource = createLokiDatasource(); datasource.languageProvider.fetchLabelValues = jest.fn(); - datasource.interpolateVariablesInQueries = jest.fn().mockImplementation((queries: LokiQuery[]) => { - return queries.map((query) => { - query.expr = query.expr.replace('$SELECTOR', '{a="b"}'); - return query; - }); + datasource.applyTemplateVariables = jest.fn().mockImplementation((query: LokiQuery) => { + query.expr = query.expr.replace('$SELECTOR', '{a="b"}'); + return query; }); jest.mocked(datasource.languageProvider.fetchLabelValues).mockResolvedValue(['1', '10', '2', '20', '3']); const { metricFrameA } = getMockFrames(); @@ -83,6 +81,25 @@ describe('runShardSplitQuery()', () => { }); }); + test('Interpolates queries before execution', async () => { + const request = createRequest([{ expr: 'count_over_time({a="b"}[$__auto])', refId: 'A', step: '$step' }]); + datasource = createLokiDatasource({ + replace: (input = '') => { + return input.replace('$__auto', '5m').replace('$step', '5m'); + }, + getVariables: () => [], + }); + jest.spyOn(datasource, 'runQuery').mockReturnValue(of({ data: [] })); + datasource.languageProvider.fetchLabelValues = jest.fn(); + jest.mocked(datasource.languageProvider.fetchLabelValues).mockResolvedValue(['1', '10', '2', '20', '3']); + await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { + expect(jest.mocked(datasource.runQuery).mock.calls[0][0].targets[0].expr).toBe( + 'count_over_time({a="b", __stream_shard__=~"20|10"} | drop __stream_shard__[5m])' + ); + expect(jest.mocked(datasource.runQuery).mock.calls[0][0].targets[0].step).toBe('5m'); + }); + }); + test('Users query splitting for querying over a day', async () => { await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { // 5 shards, 3 groups + empty shard group, 4 requests @@ -92,7 +109,7 @@ describe('runShardSplitQuery()', () => { test('Interpolates queries before running', async () => { await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { - expect(datasource.interpolateVariablesInQueries).toHaveBeenCalledTimes(1); + expect(datasource.applyTemplateVariables).toHaveBeenCalledTimes(5); expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), @@ -153,11 +170,9 @@ describe('runShardSplitQuery()', () => { }); test('Sends the whole stream selector to fetch values', async () => { - datasource.interpolateVariablesInQueries = jest.fn().mockImplementation((queries: LokiQuery[]) => { - return queries.map((query) => { - query.expr = query.expr.replace('$SELECTOR', '{service_name="test", filter="true"}'); - return query; - }); + datasource.applyTemplateVariables = jest.fn().mockImplementation((query: LokiQuery) => { + query.expr = query.expr.replace('$SELECTOR', '{service_name="test", filter="true"}'); + return query; }); await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.ts index 9f8dc397b8d..6a949ece553 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.ts @@ -46,10 +46,10 @@ import { LokiQuery } from './types'; */ export function runShardSplitQuery(datasource: LokiDatasource, request: DataQueryRequest) { - const queries = datasource - .interpolateVariablesInQueries(request.targets, request.scopedVars) + const queries = request.targets .filter((query) => query.expr) - .filter((query) => !query.hide); + .filter((query) => !query.hide) + .map((query) => datasource.applyTemplateVariables(query, request.scopedVars, request.filters)); return splitQueriesByStreamShard(datasource, request, queries); } From 76a21fb2e2fc276878300eaf59b84d73bded021d Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 4 Jul 2025 13:09:19 +0100 Subject: [PATCH 12/67] Docs: Clarify that log level critical is just an alias for error (#107454) Docs: Clarify that level = critical is just an alias for error --- conf/defaults.ini | 2 +- conf/sample.ini | 2 +- docs/sources/setup-grafana/configure-grafana/_index.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 39d79f1509e..093c66e18ab 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1121,7 +1121,7 @@ content_types = text/html # Use space to separate multiple modes, e.g. "console file" mode = console file -# Either "debug", "info", "warn", "error", "critical", default is "info" +# Either "debug", "info", "warn", "error". Default is "info" level = info # optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug diff --git a/conf/sample.ini b/conf/sample.ini index d4f71ccdb51..8835829ddb2 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1105,7 +1105,7 @@ # Use space to separate multiple modes, e.g. "console file" ;mode = console file -# Either "debug", "info", "warn", "error", "critical", default is "info" +# Either "debug", "info", "warn", "error". Default is "info" ;level = info # optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 50b0e485bdd..ddc65c17d7a 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -1547,7 +1547,7 @@ Use spaces to separate multiple modes, for example, `console file`. #### `level` -Options are `debug`, `info`, `warn`, `error`, and `critical`. Default is `info`. +Options are `debug`, `info`, `warn`, `error`. `critical` is an alias for `error`. Default is `info`. #### `filters` @@ -1576,7 +1576,7 @@ Only applicable when `console` is used in `[log]` mode. #### `level` -Options are `debug`, `info`, `warn`, `error`, and `critical`. Default is inherited from `[log]` level. +See [`[log] level`](#level) for values. Default is inherited from `[log]` level. #### `format` @@ -1590,7 +1590,7 @@ Only applicable when `file` used in `[log]` mode. #### `level` -Options are `debug`, `info`, `warn`, `error`, and `critical`. Default is inherited from `[log]` level. +See [`[log] level`](#level) for values. Default is inherited from `[log]` level. #### `format` @@ -1625,7 +1625,7 @@ Only applicable when `syslog` used in `[log]` mode. #### `level` -Options are `debug`, `info`, `warn`, `error`, and `critical`. Default is inherited from `[log]` level. +See [`[log] level`](#level) for values. Default is inherited from `[log]` level. #### `format` From 46c38fdbb7030537be90587d9cba2c4cfdc081d0 Mon Sep 17 00:00:00 2001 From: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Date: Fri, 4 Jul 2025 13:13:48 +0100 Subject: [PATCH 13/67] SecretsManager: Introduce worker and secret async service (#107614) SecretsManager: Introduce worker and secret aysnc service Co-authored-by: PoorlyDefinedBehaviour Co-authored-by: Matheus Macabu Co-authored-by: Michael Mandrus --- .../apis/secret/service/secure_value.go | 268 +++++++++++++++++ .../apis/secret/testutils/testutils.go | 202 +++++++++++++ pkg/registry/apis/secret/tracectx/carrier.go | 57 ++++ .../apis/secret/tracectx/carrier_test.go | 88 ++++++ pkg/registry/apis/secret/worker/metrics.go | 44 +++ pkg/registry/apis/secret/worker/worker.go | 274 ++++++++++++++++++ .../apis/secret/worker/worker_test.go | 246 ++++++++++++++++ .../backgroundsvcs/background_services.go | 3 + pkg/server/wire.go | 9 +- pkg/server/wire_gen.go | 79 ++++- pkg/storage/secret/metadata/outbox_store.go | 4 +- 11 files changed, 1264 insertions(+), 10 deletions(-) create mode 100644 pkg/registry/apis/secret/service/secure_value.go create mode 100644 pkg/registry/apis/secret/testutils/testutils.go create mode 100644 pkg/registry/apis/secret/tracectx/carrier.go create mode 100644 pkg/registry/apis/secret/tracectx/carrier_test.go create mode 100644 pkg/registry/apis/secret/worker/metrics.go create mode 100644 pkg/registry/apis/secret/worker/worker.go create mode 100644 pkg/registry/apis/secret/worker/worker_test.go diff --git a/pkg/registry/apis/secret/service/secure_value.go b/pkg/registry/apis/secret/service/secure_value.go new file mode 100644 index 00000000000..e667ad4c4be --- /dev/null +++ b/pkg/registry/apis/secret/service/secure_value.go @@ -0,0 +1,268 @@ +package service + +import ( + "context" + "fmt" + + claims "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/tracectx" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +type SecureValueService struct { + tracer trace.Tracer + accessClient claims.AccessClient + database contracts.Database + secureValueMetadataStorage contracts.SecureValueMetadataStorage + outboxQueue contracts.OutboxQueue + encryptionManager contracts.EncryptionManager +} + +func ProvideSecureValueService( + tracer trace.Tracer, + accessClient claims.AccessClient, + database contracts.Database, + secureValueMetadataStorage contracts.SecureValueMetadataStorage, + outboxQueue contracts.OutboxQueue, + encryptionManager contracts.EncryptionManager, +) *SecureValueService { + return &SecureValueService{ + tracer: tracer, + accessClient: accessClient, + database: database, + secureValueMetadataStorage: secureValueMetadataStorage, + outboxQueue: outboxQueue, + encryptionManager: encryptionManager, + } +} + +func (s *SecureValueService) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueService.Create", trace.WithAttributes( + attribute.String("name", sv.GetName()), + attribute.String("namespace", sv.GetNamespace()), + attribute.String("actor", actorUID), + )) + defer span.End() + + sv.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhasePending, Message: "Creating secure value"} + + var out *secretv0alpha1.SecureValue + + encryptedSecret, err := s.encryptionManager.Encrypt(ctx, sv.Namespace, []byte(sv.Spec.Value.DangerouslyExposeAndConsumeValue())) + if err != nil { + return nil, fmt.Errorf("encrypting secure value secret: %w", err) + } + + // Specifically here so that the spans from the worker are not inside the transaction. + requestID := tracectx.HexEncodeTraceFromContext(ctx) + + if err := s.database.Transaction(ctx, func(ctx context.Context) error { + createdSecureValue, err := s.secureValueMetadataStorage.Create(ctx, sv, actorUID) + if err != nil { + return fmt.Errorf("failed to create securevalue: %w", err) + } + out = createdSecureValue + + if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{ + RequestID: requestID, + Type: contracts.CreateSecretOutboxMessage, + Name: sv.Name, + Namespace: sv.Namespace, + EncryptedSecret: string(encryptedSecret), + KeeperName: sv.Spec.Keeper, + }); err != nil { + return fmt.Errorf("failed to append message to create secure value to outbox queue: %w", err) + } + + return nil + }); err != nil { + return out, err + } + + return out, nil +} + +func (s *SecureValueService) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueService.Read", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + )) + defer span.End() + + return s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: false}) +} + +func (s *SecureValueService) List(ctx context.Context, namespace xkube.Namespace) (*secretv0alpha1.SecureValueList, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueService.List", trace.WithAttributes( + attribute.String("namespace", namespace.String()), + )) + defer span.End() + + user, ok := claims.AuthInfoFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing auth info in context") + } + + hasPermissionFor, err := s.accessClient.Compile(ctx, user, claims.ListRequest{ + Group: secretv0alpha1.GROUP, + Resource: secretv0alpha1.SecureValuesResourceInfo.GetName(), + Namespace: namespace.String(), + Verb: utils.VerbGet, // Why not VerbList? + }) + if err != nil { + return nil, fmt.Errorf("failed to compile checker: %w", err) + } + + secureValuesMetadata, err := s.secureValueMetadataStorage.List(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("fetching secure values from storage: %+w", err) + } + + out := make([]secretv0alpha1.SecureValue, 0) + + for _, metadata := range secureValuesMetadata { + // Check whether the user has permission to access this specific SecureValue in the namespace. + if !hasPermissionFor(metadata.Name, "") { + continue + } + + out = append(out, metadata) + } + + return &secretv0alpha1.SecureValueList{ + Items: out, + }, nil +} + +func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, bool, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueService.Create", trace.WithAttributes( + attribute.String("name", newSecureValue.GetName()), + attribute.String("namespace", newSecureValue.GetNamespace()), + attribute.String("actor", actorUID), + )) + defer span.End() + + // True when the effects of an update can be seen immediately. + // Never true in this case since updating a secure value is async. + const updateIsSync = false + + var ( + out *secretv0alpha1.SecureValue + encryptedSecret string + ) + + if newSecureValue.Spec.Value != "" { + buffer, err := s.encryptionManager.Encrypt(ctx, newSecureValue.Namespace, []byte(newSecureValue.Spec.Value.DangerouslyExposeAndConsumeValue())) + if err != nil { + return nil, false, fmt.Errorf("encrypting secure value secret: %w", err) + } + encryptedSecret = string(buffer) + } + + // Especifically here so that the spans from the worker are not inside the transaction. + requestID := tracectx.HexEncodeTraceFromContext(ctx) + + if err := s.database.Transaction(ctx, func(ctx context.Context) error { + sv, err := s.secureValueMetadataStorage.Read(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, contracts.ReadOpts{ForUpdate: true}) + if err != nil { + return fmt.Errorf("fetching secure value: %+w", err) + } + + if sv.Status.Phase == secretv0alpha1.SecureValuePhasePending { + return contracts.ErrSecureValueOperationInProgress + } + + // Succeed immediately if the value is not going to be updated + if encryptedSecret == "" { + newSecureValue.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded} + } else { + newSecureValue.Status = secretv0alpha1.SecureValueStatus{ + Message: "Updating secure value", + Phase: secretv0alpha1.SecureValuePhasePending, + } + } + + // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? + updatedSecureValue, err := s.secureValueMetadataStorage.Update(ctx, newSecureValue, actorUID) + if err != nil { + return fmt.Errorf("failed to update secure value: %w", err) + } + out = updatedSecureValue + + // Only the value needs to be updated asynchronously by the outbox worker + if encryptedSecret != "" { + if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{ + RequestID: requestID, + Type: contracts.UpdateSecretOutboxMessage, + Name: newSecureValue.Name, + Namespace: newSecureValue.Namespace, + EncryptedSecret: encryptedSecret, + KeeperName: newSecureValue.Spec.Keeper, + ExternalID: &updatedSecureValue.Status.ExternalID, + }); err != nil { + return fmt.Errorf("failed to append message to update secure value to outbox queue: %w", err) + } + } + + return nil + }); err != nil { + return out, updateIsSync, err + } + + return out, updateIsSync, nil +} + +func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueService.Delete", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + )) + defer span.End() + + // Set inside of the transaction callback + var out *secretv0alpha1.SecureValue + + // Especifically here so that the spans from the worker are not inside the transaction. + requestID := tracectx.HexEncodeTraceFromContext(ctx) + + if err := s.database.Transaction(ctx, func(ctx context.Context) error { + sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: true}) + if err != nil { + return fmt.Errorf("fetching secure value: %+w", err) + } + + if sv.Status.Phase == secretv0alpha1.SecureValuePhasePending { + return contracts.ErrSecureValueOperationInProgress + } + + sv.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhasePending, Message: "Deleting secure value"} + + if err := s.secureValueMetadataStorage.SetStatus(ctx, namespace, name, sv.Status); err != nil { + return fmt.Errorf("setting secure value status phase: %+w", err) + } + + if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{ + RequestID: requestID, + Type: contracts.DeleteSecretOutboxMessage, + Name: name, + Namespace: namespace.String(), + KeeperName: sv.Spec.Keeper, + ExternalID: &sv.Status.ExternalID, + }); err != nil { + return fmt.Errorf("appending delete secure value message to outbox queue: %+w", err) + } + + out = sv + + return nil + }); err != nil { + return out, err + } + + return out, nil +} diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go new file mode 100644 index 00000000000..40e571aca63 --- /dev/null +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -0,0 +1,202 @@ +package testutils + +import ( + "context" + "testing" + "time" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption" + "go.opentelemetry.io/otel/trace/noop" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" + "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" + "github.com/grafana/grafana/pkg/registry/apis/secret/service" + "github.com/grafana/grafana/pkg/registry/apis/secret/worker" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/secret/database" + "github.com/grafana/grafana/pkg/storage/secret/metadata" + "github.com/grafana/grafana/pkg/storage/secret/migrator" + "github.com/stretchr/testify/require" +) + +type setupConfig struct { + workerCfg worker.Config + keeperService contracts.KeeperService +} + +func defaultSetupCfg() setupConfig { + return setupConfig{ + workerCfg: worker.Config{ + BatchSize: 10, + ReceiveTimeout: 1 * time.Second, + PollingInterval: time.Millisecond, + MaxMessageProcessingAttempts: 5, + }, + } +} + +func WithWorkerConfig(cfg worker.Config) func(*setupConfig) { + return func(setupCfg *setupConfig) { + setupCfg.workerCfg = cfg + } +} + +func WithKeeperService(keeperService contracts.KeeperService) func(*setupConfig) { + return func(setupCfg *setupConfig) { + setupCfg.keeperService = keeperService + } +} + +func Setup(t *testing.T, opts ...func(*setupConfig)) Sut { + setupCfg := defaultSetupCfg() + for _, opt := range opts { + opt(&setupCfg) + } + + tracer := noop.NewTracerProvider().Tracer("test") + testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) + + database := database.ProvideDatabase(testDB, tracer) + + outboxQueue := metadata.ProvideOutboxQueue(database, tracer, nil) + + features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) + + keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(database, tracer, features, nil) + require.NoError(t, err) + + secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(database, tracer, features, nil) + require.NoError(t, err) + + // Initialize access client + access control + accessControl := &actest.FakeAccessControl{ExpectedEvaluate: true} + accessClient := accesscontrol.NewLegacyAccessClient(accessControl) + + defaultKey := "SdlklWklckeLS" + cfg := &setting.Cfg{ + SecretsManagement: setting.SecretsManagerSettings{ + SecretKey: defaultKey, + EncryptionProvider: "secretKey.v1", + }, + } + store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil) + require.NoError(t, err) + + usageStats := &usagestats.UsageStatsMock{T: t} + + encryptionManager, err := manager.ProvideEncryptionManager( + tracer, + store, + cfg, + usageStats, + encryption.ProvideThirdPartyProviderMap(), + ) + require.NoError(t, err) + + // Initialize encrypted value storage with a fake db + encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features) + require.NoError(t, err) + + sqlKeeper := sqlkeeper.NewSQLKeeper(tracer, encryptionManager, encValueStore, nil) + + var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper) + + if setupCfg.keeperService != nil { + keeperService = setupCfg.keeperService + } + + secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, outboxQueue, encryptionManager) + + worker, err := worker.NewWorker( + setupCfg.workerCfg, + tracer, + database, + outboxQueue, + secureValueMetadataStorage, + keeperMetadataStorage, + keeperService, + encryptionManager, + features, + nil, // metrics + ) + require.NoError(t, err) + + return Sut{Worker: worker, SecureValueService: secureValueService, SecureValueMetadataStorage: secureValueMetadataStorage, OutboxQueue: outboxQueue, Database: database} +} + +type Sut struct { + Worker *worker.Worker + SecureValueService *service.SecureValueService + SecureValueMetadataStorage contracts.SecureValueMetadataStorage + OutboxQueue contracts.OutboxQueue + Database *database.Database +} + +type CreateSvConfig struct { + Sv *secretv0alpha1.SecureValue +} + +func CreateSvWithSv(sv *secretv0alpha1.SecureValue) func(*CreateSvConfig) { + return func(cfg *CreateSvConfig) { + cfg.Sv = sv + } +} + +func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*secretv0alpha1.SecureValue, error) { + cfg := CreateSvConfig{ + Sv: &secretv0alpha1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sv1", + Namespace: "ns1", + }, + Spec: secretv0alpha1.SecureValueSpec{ + Description: "desc1", + Value: secretv0alpha1.NewExposedSecureValue("v1"), + }, + Status: secretv0alpha1.SecureValueStatus{ + Phase: secretv0alpha1.SecureValuePhasePending, + }, + }, + } + for _, opt := range opts { + opt(&cfg) + } + + createdSv, err := s.SecureValueService.Create(ctx, cfg.Sv, "actor") + if err != nil { + return nil, err + } + return createdSv, nil +} + +func (s *Sut) UpdateSv(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error) { + newSv, _, err := s.SecureValueService.Update(ctx, sv, "actor") + return newSv, err +} + +func (s *Sut) DeleteSv(ctx context.Context, namespace, name string) (*secretv0alpha1.SecureValue, error) { + sv, err := s.SecureValueService.Delete(ctx, xkube.Namespace(namespace), name) + return sv, err +} + +type keeperServiceWrapper struct { + keeper contracts.Keeper +} + +func newKeeperServiceWrapper(keeper contracts.Keeper) *keeperServiceWrapper { + return &keeperServiceWrapper{keeper: keeper} +} + +func (wrapper *keeperServiceWrapper) KeeperForConfig(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) { + return wrapper.keeper, nil +} diff --git a/pkg/registry/apis/secret/tracectx/carrier.go b/pkg/registry/apis/secret/tracectx/carrier.go new file mode 100644 index 00000000000..a8153c42d1e --- /dev/null +++ b/pkg/registry/apis/secret/tracectx/carrier.go @@ -0,0 +1,57 @@ +package tracectx + +import ( + "context" + "encoding/hex" + "fmt" + "strings" + + "go.opentelemetry.io/otel/propagation" +) + +const ( + kvSeparator = "=" + pairSeparator = "#" +) + +func HexEncodeTraceFromContext(ctx context.Context) string { + carrier := propagation.MapCarrier(make(map[string]string)) + + propagation.TraceContext{}.Inject(ctx, carrier) + + // no trace in context + if len(carrier) == 0 { + return "" + } + + pairs := make([]string, 0, len(carrier)) + for k, v := range carrier { + pairs = append(pairs, k+kvSeparator+v) + } + + return hex.EncodeToString([]byte(strings.Join(pairs, pairSeparator))) +} + +func HexDecodeTraceIntoContext(ctx context.Context, encoded string) (context.Context, error) { + if encoded == "" { + return ctx, nil + } + + decoded, err := hex.DecodeString(encoded) + if err != nil { + return nil, err + } + + pairs := strings.Split(string(decoded), pairSeparator) + + carrier := make(propagation.MapCarrier, len(pairs)) + for _, pair := range pairs { + kv := strings.SplitN(pair, kvSeparator, 2) + if len(kv) != 2 || kv[0] == "" || kv[1] == "" { + return nil, fmt.Errorf("invalid key-value pair: %s", pair) + } + carrier[kv[0]] = kv[1] + } + + return propagation.TraceContext{}.Extract(ctx, carrier), nil +} diff --git a/pkg/registry/apis/secret/tracectx/carrier_test.go b/pkg/registry/apis/secret/tracectx/carrier_test.go new file mode 100644 index 00000000000..043428faeb2 --- /dev/null +++ b/pkg/registry/apis/secret/tracectx/carrier_test.go @@ -0,0 +1,88 @@ +package tracectx + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +func TestHexEncodeTraceFromContext(t *testing.T) { + t.Run("when no trace is present in context, it returns empty string", func(t *testing.T) { + ctx := context.Background() + + encoded := HexEncodeTraceFromContext(ctx) + require.Empty(t, encoded) + }) + + t.Run("when trace is present in context, it returns hex-encoded string", func(t *testing.T) { + carrier := propagation.MapCarrier{ + "traceparent": "00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01", + "tracestate": "first=abc1234,second=xyz7890", + } + ctx := propagation.TraceContext{}.Extract(context.Background(), carrier) + + encoded := HexEncodeTraceFromContext(ctx) + require.NotEmpty(t, encoded) + + traceCtx, err := HexDecodeTraceIntoContext(context.Background(), encoded) + require.NoError(t, err) + + span := trace.SpanFromContext(traceCtx) + require.True(t, span.SpanContext().IsValid()) + + carrier = propagation.MapCarrier(make(map[string]string)) + propagation.TraceContext{}.Inject(traceCtx, carrier) + require.Contains(t, carrier, "traceparent") + require.Contains(t, carrier, "tracestate") + }) +} + +func TestHexDecodeTraceIntoContext(t *testing.T) { + t.Run("when encoded string is empty, it returns original context", func(t *testing.T) { + ctx := context.Background() + + result, err := HexDecodeTraceIntoContext(ctx, "") + require.NoError(t, err) + require.Equal(t, ctx, result) + }) + + t.Run("when encoded string is valid hex, it returns context with trace", func(t *testing.T) { + encoded := hex.EncodeToString([]byte("traceparent=00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01#tracestate=first=abc1234,second=xyz7890")) + + ctx, err := HexDecodeTraceIntoContext(context.Background(), encoded) + require.NoError(t, err) + + span := trace.SpanFromContext(ctx) + require.True(t, span.SpanContext().IsValid()) + }) + + t.Run("when encoded string has invalid hex encoding, it returns an error", func(t *testing.T) { + invalidHex := "invalid-hex-zzz" + + result, err := HexDecodeTraceIntoContext(context.Background(), invalidHex) + require.Error(t, err) + require.Nil(t, result) + }) + + t.Run("when decoded string has invalid key-value pair format, it returns an error", func(t *testing.T) { + // missing key + encoded := hex.EncodeToString([]byte("00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01")) + + result, err := HexDecodeTraceIntoContext(context.Background(), encoded) + require.Error(t, err) + require.Nil(t, result) + }) + + t.Run("when decoded string has key without value, it returns error", func(t *testing.T) { + // missing value + encoded := hex.EncodeToString([]byte("traceparent=")) + + result, err := HexDecodeTraceIntoContext(context.Background(), encoded) + require.Error(t, err) + require.Nil(t, result) + }) +} diff --git a/pkg/registry/apis/secret/worker/metrics.go b/pkg/registry/apis/secret/worker/metrics.go new file mode 100644 index 00000000000..a6f690f7163 --- /dev/null +++ b/pkg/registry/apis/secret/worker/metrics.go @@ -0,0 +1,44 @@ +package worker + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + namespace = "grafana_secrets_manager" + subsystem = "outbox_worker" +) + +// OutboxMetrics is a struct that contains all the metrics for an implementation of the secrets service. +type OutboxMetrics struct { + OutboxMessageProcessingDuration *prometheus.HistogramVec +} + +func newOutboxMetrics() *OutboxMetrics { + return &OutboxMetrics{ + OutboxMessageProcessingDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "message_processing_duration_seconds", + Help: "Duration of outbox message processing", + Buckets: prometheus.DefBuckets, + }, []string{"message_type", "keeper_type"}), + } +} + +// NewOutboxMetrics creates a new SecretsMetrics struct containing registered metrics +func NewOutboxMetrics(reg prometheus.Registerer) *OutboxMetrics { + m := newOutboxMetrics() + + if reg != nil { + reg.MustRegister( + m.OutboxMessageProcessingDuration, + ) + } + + return m +} + +func NewTestMetrics() *OutboxMetrics { + return newOutboxMetrics() +} diff --git a/pkg/registry/apis/secret/worker/worker.go b/pkg/registry/apis/secret/worker/worker.go new file mode 100644 index 00000000000..35474fdae47 --- /dev/null +++ b/pkg/registry/apis/secret/worker/worker.go @@ -0,0 +1,274 @@ +package worker + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/grafana/grafana-app-sdk/logging" + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/tracectx" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +// Consumes and processes messages from the secure value outbox queue +type Worker struct { + config Config + tracer trace.Tracer + database contracts.Database + outboxQueue contracts.OutboxQueue + secureValueMetadataStorage contracts.SecureValueMetadataStorage + keeperMetadataStorage contracts.KeeperMetadataStorage + keeperService contracts.KeeperService + encryptionManager contracts.EncryptionManager + metrics *OutboxMetrics + enabled bool +} + +// DefaultConfig for the secure value outbox worker. +var DefaultConfig = Config{ + BatchSize: 20, + ReceiveTimeout: 5 * time.Second, + PollingInterval: 100 * time.Millisecond, + MaxMessageProcessingAttempts: 10, +} + +// ProvideWorkerConfig used for wire. +func ProvideWorkerConfig() Config { + return DefaultConfig +} + +type Config struct { + // The max number of messages to fetch from the outbox queue in a batch + BatchSize uint + // How long to wait for a request to fetch messages from the outbox queue + ReceiveTimeout time.Duration + // How often to poll the outbox queue for new messages + PollingInterval time.Duration + // How many tries to try to process a message before marking the operation as failed + MaxMessageProcessingAttempts uint +} + +func NewWorker( + config Config, + tracer trace.Tracer, + database contracts.Database, + outboxQueue contracts.OutboxQueue, + secureValueMetadataStorage contracts.SecureValueMetadataStorage, + keeperMetadataStorage contracts.KeeperMetadataStorage, + keeperService contracts.KeeperService, + encryptionManager contracts.EncryptionManager, + features featuremgmt.FeatureToggles, + reg prometheus.Registerer, +) (*Worker, error) { + if config.BatchSize == 0 { + return nil, fmt.Errorf("config.BatchSize is required") + } + if config.ReceiveTimeout == 0 { + return nil, fmt.Errorf("config.ReceiveTimeout is required") + } + if config.PollingInterval == 0 { + return nil, fmt.Errorf("config.PollingInterval is required") + } + if config.MaxMessageProcessingAttempts == 0 { + return nil, fmt.Errorf("config.MaxMessageProcessingAttempts is required") + } + + // Require both features to be enabled for the worker to run. + enabled := features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) && features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) + + return &Worker{ + config: config, + tracer: tracer, + database: database, + outboxQueue: outboxQueue, + secureValueMetadataStorage: secureValueMetadataStorage, + keeperMetadataStorage: keeperMetadataStorage, + keeperService: keeperService, + encryptionManager: encryptionManager, + metrics: NewOutboxMetrics(reg), + enabled: enabled, + }, nil +} + +// Ensure that Worker implements the BackgroundService interface, so we can start it as a background service. +var _ registry.BackgroundService = (*Worker)(nil) + +// Run is the main method to drive the worker +func (w *Worker) Run(ctx context.Context) error { + if !w.enabled { + return nil + } + + logging.FromContext(ctx).Debug("starting worker control loop") + + t := time.NewTicker(w.config.PollingInterval) + defer t.Stop() + + for { + select { + // If the context was canceled + case <-ctx.Done(): + // return the reason it was canceled + return ctx.Err() + + // Otherwise try to receive messages + case <-t.C: + if ctx.Err() != nil { + return ctx.Err() + } + + if err := w.ReceiveAndProcessMessages(ctx); err != nil { + logging.FromContext(ctx).Error("receiving outbox messages", "err", err.Error()) + } + } + } +} + +// TODO: don't rollback every message when a single error happens +func (w *Worker) ReceiveAndProcessMessages(ctx context.Context) error { + messageIDs := make([]int64, 0) + + txErr := w.database.Transaction(ctx, func(ctx context.Context) error { + timeoutCtx, cancel := context.WithTimeout(ctx, w.config.ReceiveTimeout) + messages, err := w.outboxQueue.ReceiveN(timeoutCtx, w.config.BatchSize) + cancel() + if err != nil { + return err + } + + for _, message := range messages { + messageIDs = append(messageIDs, message.MessageID) + if err := w.processMessage(ctx, message); err != nil { + return fmt.Errorf("processing message: %+v %w", message, err) + } + } + return nil + }) + + // This call is made outside the transaction to make sure the receive count is updated on rollbacks. + incrementErr := w.outboxQueue.IncrementReceiveCount(ctx, messageIDs) + if incrementErr != nil { + incrementErr = fmt.Errorf("incrementing receive count for outbox message: %w", incrementErr) + } + + return errors.Join(txErr, incrementErr) +} + +func (w *Worker) processMessage(ctx context.Context, message contracts.OutboxMessage) error { + start := time.Now() + keeperType := "unknown" + defer func() { + w.metrics.OutboxMessageProcessingDuration.WithLabelValues(string(message.Type), keeperType).Observe(time.Since(start).Seconds()) + }() + logging.FromContext(ctx).Debug("processing message", "type", message.Type, "name", message.Name, "namespace", message.Namespace, "receiveCount", message.ReceiveCount) + + opts := []trace.SpanStartOption{} + // If there's no request ID in the message, start a new root span and log an error. + ctx, err := tracectx.HexDecodeTraceIntoContext(ctx, message.RequestID) + if err != nil { + opts = append(opts, trace.WithNewRoot()) + logging.FromContext(ctx).Error("decoding trace context from message", "err", err.Error(), "message.requestID", message.RequestID) + } + + opts = append(opts, trace.WithAttributes( + attribute.String("message.requestID", message.RequestID), + attribute.Int64("message.id", message.MessageID), + attribute.String("message.type", string(message.Type)), + attribute.String("message.namespace", message.Namespace), + attribute.String("message.secureValue.name", message.Name), + attribute.Int("message.receive.count", message.ReceiveCount), + )) + + ctx, span := w.tracer.Start(ctx, "Worker.ProcessMessage", opts...) + defer span.End() + + if message.ReceiveCount >= int(w.config.MaxMessageProcessingAttempts) { + if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseFailed, Message: fmt.Sprintf("Reached max number of attempts to complete operation: %s", message.Type)}); err != nil { + return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v", message) + } + if err := w.outboxQueue.Delete(ctx, message.MessageID); err != nil { + return fmt.Errorf("deleting message from outbox queue: %w", err) + } + return nil + } + + keeperCfg, err := w.keeperMetadataStorage.GetKeeperConfig(ctx, message.Namespace, message.KeeperName, contracts.ReadOpts{ForUpdate: true}) + if err != nil { + return fmt.Errorf("fetching keeper config: namespace=%+v keeperName=%+v %w", message.Namespace, message.KeeperName, err) + } + keeperType = string(keeperCfg.Type()) + + keeper, err := w.keeperService.KeeperForConfig(keeperCfg) + if err != nil { + return fmt.Errorf("getting keeper for config: namespace=%+v keeperName=%+v %w", message.Namespace, message.KeeperName, err) + } + logging.FromContext(ctx).Debug("retrieved keeper", "namespace", message.Namespace, "keeperName", message.KeeperName, "type", keeperCfg.Type()) + + switch message.Type { + case contracts.CreateSecretOutboxMessage: + rawSecret, err := w.encryptionManager.Decrypt(ctx, message.Namespace, []byte(message.EncryptedSecret)) + if err != nil { + return fmt.Errorf("decrypting secure value secret: %w", err) + } + + externalID, err := keeper.Store(ctx, keeperCfg, message.Namespace, string(rawSecret)) + if err != nil { + return fmt.Errorf("storing secret: message=%+v %w", message, err) + } + + if err := w.secureValueMetadataStorage.SetExternalID(ctx, xkube.Namespace(message.Namespace), message.Name, externalID); err != nil { + return fmt.Errorf("setting secret metadata externalID: externalID=%+v message=%+v %w", externalID, message, err) + } + + // Setting the status to Succeeded must be the last action + // since it acts as a fence to clients. + if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded}); err != nil { + return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v %w", message, err) + } + + case contracts.UpdateSecretOutboxMessage: + rawSecret, err := w.encryptionManager.Decrypt(ctx, message.Namespace, []byte(message.EncryptedSecret)) + if err != nil { + return fmt.Errorf("decrypting secure value secret: %w", err) + } + + if err := keeper.Update(ctx, keeperCfg, message.Namespace, contracts.ExternalID(*message.ExternalID), string(rawSecret)); err != nil { + return fmt.Errorf("calling keeper to update secret: %w", err) + } + + // Setting the status to Succeeded must be the last action + // since it acts as a fence to clients. + if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded}); err != nil { + return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v", message) + } + + case contracts.DeleteSecretOutboxMessage: + if err := keeper.Delete(ctx, keeperCfg, message.Namespace, contracts.ExternalID(*message.ExternalID)); err != nil { + return fmt.Errorf("calling keeper to delete secret: %w", err) + } + if err := w.secureValueMetadataStorage.Delete(ctx, xkube.Namespace(message.Namespace), message.Name); err != nil { + return fmt.Errorf("deleting secure value metadata: %+w", err) + } + + default: + return fmt.Errorf("unhandled message type: %s", message.Type) + } + + // Delete the message from the queue after completing all operations because + // if the message is deleted first, the response may be lost, + // resulting in an error, but since the message was actually deleted + // the worker would never retry. + if err := w.outboxQueue.Delete(ctx, message.MessageID); err != nil { + return fmt.Errorf("deleting message from outbox queue: %w", err) + } + + return nil +} diff --git a/pkg/registry/apis/secret/worker/worker_test.go b/pkg/registry/apis/secret/worker/worker_test.go new file mode 100644 index 00000000000..0d20a668e52 --- /dev/null +++ b/pkg/registry/apis/secret/worker/worker_test.go @@ -0,0 +1,246 @@ +package worker_test + +import ( + "context" + "fmt" + "testing" + "time" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/registry/apis/secret/worker" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/stretchr/testify/require" +) + +type fakeKeeperService struct { + keeperForConfigFunc func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) +} + +func newFakeKeeperService(keeperForConfigFunc func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error)) *fakeKeeperService { + return &fakeKeeperService{keeperForConfigFunc: keeperForConfigFunc} +} + +func (s *fakeKeeperService) KeeperForConfig(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) { + return s.keeperForConfigFunc(cfg) +} + +func TestProcessMessage(t *testing.T) { + t.Parallel() + + t.Run("secure value metadata status is set to Failed when processing a message fails too many times", func(t *testing.T) { + t.Parallel() + + // Given a worker that will attempt to process a message N times + workerCfg := worker.Config{ + BatchSize: 10, + ReceiveTimeout: 1 * time.Second, + PollingInterval: time.Millisecond, + MaxMessageProcessingAttempts: 2, + } + + // And an error that keeps happening + keeperService := newFakeKeeperService(func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) { + return nil, fmt.Errorf("oops") + }) + + sut := testutils.Setup(t, testutils.WithWorkerConfig(workerCfg), testutils.WithKeeperService(keeperService)) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + + for range workerCfg.MaxMessageProcessingAttempts + 1 { + // The secure value status should be Pending while the worker is trying to process the message + sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase) + + // Worker tries to process messages + _ = sut.Worker.ReceiveAndProcessMessages(ctx) + } + + // After the worker fails to process a message too many times, + // the secure value status is changed to Failed + sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhaseFailed, sv.Status.Phase) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Empty(t, messages) + }) + + t.Run("create sv: secure value metadata status is set to Succeeded when message is processed successfully", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + + // Worker receives and processes the message + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + // and sets the secure value status to Succeeded + sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Empty(t, messages) + }) + + t.Run("update sv: secure value metadata status is set to Succeeded when message is processed successfully", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + + // Worker receives and processes the message + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + // and sets the secure value status to Succeeded + sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase) + + sv.Spec.Description = "desc2" + sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2") + + // Queue an update operation + sv, err = sut.UpdateSv(ctx, sv) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase) + + // Worker receives and processes the message + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + updatedSv, err := sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, updatedSv.Status.Phase) + require.Equal(t, sv.Spec.Description, updatedSv.Spec.Description) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Empty(t, messages) + }) + + t.Run("delete sv: secure value metadata is deleted", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + + // Worker receives and processes the message + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + // and sets the secure value status to Succeeded + sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase) + + // Queue a delete operation + updatedSv, err := sut.DeleteSv(ctx, sv.Namespace, sv.Name) + require.NoError(t, err) + require.Equal(t, secretv0alpha1.SecureValuePhasePending, updatedSv.Status.Phase) + + // Worker receives and processes the message + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + // The secure value has been deleted + _, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{}) + require.ErrorIs(t, err, contracts.ErrSecureValueNotFound) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Empty(t, messages) + }) + + t.Run("when creating a secure value, the secret is encrypted before it is added to the outbox queue", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + var secret string + _, err := sut.CreateSv(ctx, func(cfg *testutils.CreateSvConfig) { + secret = string(cfg.Sv.Spec.Value) + }) + require.NoError(t, err) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Equal(t, 1, len(messages)) + + encryptedSecret := messages[0].EncryptedSecret + require.NotEmpty(t, secret) + require.NotEmpty(t, encryptedSecret) + require.NotEqual(t, secret, encryptedSecret) + }) + + t.Run("when updating a secure value, the secret is encrypted before it is added to the outbox queue", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2") + + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + newValue := "v2" + sv.Spec.Value = secretv0alpha1.NewExposedSecureValue(newValue) + + // Queue an update secure value operation + _, err = sut.UpdateSv(ctx, sv) + require.NoError(t, err) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Equal(t, 1, len(messages)) + + encryptedSecret := messages[0].EncryptedSecret + require.NotEmpty(t, encryptedSecret) + require.NotEqual(t, newValue, encryptedSecret) + }) + + t.Run("when deleting a secure value, no value is added to the outbox message", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + ctx := context.Background() + + // Queue a create secure value operation + sv, err := sut.CreateSv(ctx) + require.NoError(t, err) + sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2") + + require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx)) + + // Queue a delete secure value operation + _, err = sut.DeleteSv(ctx, sv.Namespace, sv.Name) + require.NoError(t, err) + + messages, err := sut.OutboxQueue.ReceiveN(ctx, 100) + require.NoError(t, err) + require.Equal(t, 1, len(messages)) + require.Empty(t, messages[0].EncryptedSecret) + }) +} diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index f6ae88f9dd1..d8a9010bfd2 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats/statscollector" "github.com/grafana/grafana/pkg/registry" apiregistry "github.com/grafana/grafana/pkg/registry/apis" + secretworker "github.com/grafana/grafana/pkg/registry/apis/secret/worker" appregistry "github.com/grafana/grafana/pkg/registry/apps" "github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite" "github.com/grafana/grafana/pkg/services/anonymous/anonimpl" @@ -70,6 +71,7 @@ func ProvideBackgroundServiceRegistry( appRegistry *appregistry.Service, pluginDashboardUpdater *plugindashboardsservice.DashboardUpdater, dashboardServiceImpl *service.DashboardServiceImpl, + secretManagerWorker *secretworker.Worker, // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ serviceaccounts.Service, @@ -117,6 +119,7 @@ func ProvideBackgroundServiceRegistry( appRegistry, pluginDashboardUpdater, dashboardServiceImpl, + secretManagerWorker, ) } diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 42cf7f6f499..efb82d04bc0 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -45,6 +45,8 @@ import ( secretdecrypt "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt" gsmEncryption "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" encryptionManager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + secretsecurevalueservice "github.com/grafana/grafana/pkg/registry/apis/secret/service" + secretworker "github.com/grafana/grafana/pkg/registry/apis/secret/worker" appregistry "github.com/grafana/grafana/pkg/registry/apps" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -425,16 +427,19 @@ var wireBasicSet = wire.NewSet( secretmetadata.ProvideSecureValueMetadataStorage, secretmetadata.ProvideKeeperMetadataStorage, secretmetadata.ProvideDecryptStorage, + secretdecrypt.ProvideDecryptAuthorizer, + secretdecrypt.ProvideDecryptAllowList, secretencryption.ProvideDataKeyStorage, secretencryption.ProvideEncryptedValueStorage, secretmetadata.ProvideOutboxQueue, + secretsecurevalueservice.ProvideSecureValueService, secretmigrator.NewWithEngine, secretdatabase.ProvideDatabase, wire.Bind(new(secretcontracts.Database), new(*secretdatabase.Database)), encryptionManager.ProvideEncryptionManager, gsmEncryption.ProvideThirdPartyProviderMap, - secretdecrypt.ProvideDecryptAuthorizer, - secretdecrypt.ProvideDecryptAllowList, + secretworker.ProvideWorkerConfig, + secretworker.NewWorker, // Unified storage resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index d52743dc2da..166c20a19dc 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -61,8 +61,11 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt" - encryption3 "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" + encryption2 "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" manager4 "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper" + service11 "github.com/grafana/grafana/pkg/registry/apis/secret/service" + "github.com/grafana/grafana/pkg/registry/apis/secret/worker" "github.com/grafana/grafana/pkg/registry/apis/userstorage" "github.com/grafana/grafana/pkg/registry/apps" advisor2 "github.com/grafana/grafana/pkg/registry/apps/advisor" @@ -111,7 +114,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources/guardian" service7 "github.com/grafana/grafana/pkg/services/datasources/service" - "github.com/grafana/grafana/pkg/services/encryption" + encryption3 "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption/provider" service2 "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/extsvcauth" @@ -233,7 +236,7 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" database5 "github.com/grafana/grafana/pkg/storage/secret/database" - encryption2 "github.com/grafana/grafana/pkg/storage/secret/encryption" + "github.com/grafana/grafana/pkg/storage/secret/encryption" "github.com/grafana/grafana/pkg/storage/secret/metadata" migrator2 "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/grafana/grafana/pkg/storage/unified" @@ -702,6 +705,38 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser } importDashboardService := service9.ProvideService(routeRegisterImpl, quotaService, service12, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles) dashboardUpdater := service6.ProvideDashboardUpdater(inProcBus, pluginstoreService, service12, importDashboardService, service11, pluginService, dashboardService) + config := worker.ProvideWorkerConfig() + databaseDatabase := database5.ProvideDatabase(sqlStore, tracer) + outboxQueue := metadata.ProvideOutboxQueue(databaseDatabase, tracer, registerer) + secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + encryptedValueStorage, err := encryption.ProvideEncryptedValueStorage(databaseDatabase, tracer, featureToggles) + if err != nil { + return nil, err + } + dataKeyStorage, err := encryption.ProvideDataKeyStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + providerMap := encryption2.ProvideThirdPartyProviderMap() + encryptionManager, err := manager4.ProvideEncryptionManager(tracer, dataKeyStorage, cfg, usageStats, providerMap) + if err != nil { + return nil, err + } + ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, registerer) + if err != nil { + return nil, err + } + workerWorker, err := worker.NewWorker(config, tracer, databaseDatabase, outboxQueue, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService, encryptionManager, featureToggles, registerer) + if err != nil { + return nil, err + } sanitizerProvider := sanitizer.ProvideService(renderingService) healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider) if err != nil { @@ -768,7 +803,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser } ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService() registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokenService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationService) - backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) + backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, workerWorker, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService) server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer) if err != nil { @@ -1215,6 +1250,38 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { } importDashboardService := service9.ProvideService(routeRegisterImpl, quotaService, service12, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles) dashboardUpdater := service6.ProvideDashboardUpdater(inProcBus, pluginstoreService, service12, importDashboardService, service11, pluginService, dashboardService) + config := worker.ProvideWorkerConfig() + databaseDatabase := database5.ProvideDatabase(sqlStore, tracer) + outboxQueue := metadata.ProvideOutboxQueue(databaseDatabase, tracer, registerer) + secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + encryptedValueStorage, err := encryption.ProvideEncryptedValueStorage(databaseDatabase, tracer, featureToggles) + if err != nil { + return nil, err + } + dataKeyStorage, err := encryption.ProvideDataKeyStorage(databaseDatabase, tracer, featureToggles, registerer) + if err != nil { + return nil, err + } + providerMap := encryption2.ProvideThirdPartyProviderMap() + encryptionManager, err := manager4.ProvideEncryptionManager(tracer, dataKeyStorage, cfg, usageStats, providerMap) + if err != nil { + return nil, err + } + ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, registerer) + if err != nil { + return nil, err + } + workerWorker, err := worker.NewWorker(config, tracer, databaseDatabase, outboxQueue, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService, encryptionManager, featureToggles, registerer) + if err != nil { + return nil, err + } sanitizerProvider := sanitizer.ProvideService(renderingService) healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider) if err != nil { @@ -1281,7 +1348,7 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { } ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService() registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokentestService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationServiceMock) - backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) + backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, workerWorker, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService) server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer) if err != nil { @@ -1428,7 +1495,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, featuremgmt.ProvideStaticEvaluator, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, encryption2.ProvideDataKeyStorage, encryption2.ProvideEncryptedValueStorage, metadata.ProvideOutboxQueue, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, encryption3.ProvideThirdPartyProviderMap, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptAllowList, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption3.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, featuremgmt.ProvideStaticEvaluator, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptAllowList, encryption.ProvideDataKeyStorage, encryption.ProvideEncryptedValueStorage, metadata.ProvideOutboxQueue, service11.ProvideSecureValueService, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, encryption2.ProvideThirdPartyProviderMap, worker.ProvideWorkerConfig, worker.NewWorker, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/storage/secret/metadata/outbox_store.go b/pkg/storage/secret/metadata/outbox_store.go index 1f183b7882b..efe569384f4 100644 --- a/pkg/storage/secret/metadata/outbox_store.go +++ b/pkg/storage/secret/metadata/outbox_store.go @@ -52,7 +52,6 @@ type outboxMessageDB struct { } func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (messageID int64, err error) { - start := time.Now() ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes( attribute.String("name", input.Name), attribute.String("namespace", input.Namespace), @@ -74,6 +73,7 @@ func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMe assert.True(input.Type != "", "outboxStore.Append: outbox message type is required") + start := time.Now() messageID, err = s.insertMessage(ctx, input) if err != nil { return messageID, fmt.Errorf("inserting message into outbox table: %+w", err) @@ -156,7 +156,6 @@ func (s *outboxStore) insertMessage(ctx context.Context, input contracts.AppendO } func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.OutboxMessage, error) { - start := time.Now() messageIDs, err := s.fetchMessageIdsInQueue(ctx, limit) if err != nil { return nil, fmt.Errorf("fetching message ids from queue: %w", err) @@ -170,6 +169,7 @@ func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.Out MessageIDs: messageIDs, } + start := time.Now() query, err := sqltemplate.Execute(sqlSecureValueOutboxReceiveN, req) if err != nil { return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxReceiveN.Name(), err) From 6bb74ff56a0d5a8880568c2f92f5919616790195 Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Fri, 4 Jul 2025 14:52:24 +0200 Subject: [PATCH 14/67] unistore: add key validation (#107622) --- pkg/storage/unified/resource/kv.go | 15 ++++++++++ pkg/storage/unified/resource/kv_test.go | 40 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/pkg/storage/unified/resource/kv.go b/pkg/storage/unified/resource/kv.go index fe3b6cedede..e4af06a9aa1 100644 --- a/pkg/storage/unified/resource/kv.go +++ b/pkg/storage/unified/resource/kv.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "iter" + "regexp" "time" badger "github.com/dgraph-io/badger/v4" @@ -216,3 +217,17 @@ func PrefixRangeEnd(prefix string) string { } return string(end) } + +var ( + // validKeyRegex validates keys used in the unified storage + // Keys can contain lowercase alphanumeric characters, '-', '.', '/', and '~' + // Any combination of these characters is allowed as long as the key is not empty + validKeyRegex = regexp.MustCompile(`^[a-z0-9./~-]+$`) +) + +func IsValidKey(key string) bool { + if key == "" { + return false + } + return validKeyRegex.MatchString(key) +} diff --git a/pkg/storage/unified/resource/kv_test.go b/pkg/storage/unified/resource/kv_test.go index 8ecf6a54751..a2e3544db7c 100644 --- a/pkg/storage/unified/resource/kv_test.go +++ b/pkg/storage/unified/resource/kv_test.go @@ -222,3 +222,43 @@ func TestBadgerKV_UnderlyingStorage(t *testing.T) { } }) } + +func TestIsValidKey(t *testing.T) { + tests := []struct { + name string + key string + expected bool + }{ + // Valid keys + {"simple key", "a", true}, + {"key with numbers", "a123", true}, + {"key with hyphens", "a-b-c", true}, + {"key with dots", "a.b.c", true}, + {"key with mixed", "a1-b2.c3", true}, + {"composite key with slash", "ns/group", true}, + {"composite key with tilde", "ns~action", true}, + {"complex composite key", "ns/group/resource/name", true}, + {"data key format", "ns/group/resource/name/123~created", true}, + {"metadata key format", "group/resource/ns/name/123~created~folder", true}, + {"metadata key format ending with a ~", "group/resource/ns/name/123~created~", true}, + + // invalid keys + {"empty key", "", false}, + {"uppercase letters", "Invalid", false}, + {"special characters", "a@b", false}, + {"spaces", "a b", false}, + {"leading space", " key", false}, + {"trailing space", "key ", false}, + {"tab character", "a\tb", false}, + {"newline character", "a\nb", false}, + {"underscores", "a_b", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsValidKey(tt.key) + require.Equal(t, tt.expected, result, + "IsValidKey(%q) = %v, expected %v", tt.key, result, tt.expected) + }) + } +} From 650abfd2fff1467740ac921a358affb3a0808512 Mon Sep 17 00:00:00 2001 From: "Arati R." <33031346+suntala@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:59:22 +0200 Subject: [PATCH 15/67] Unified Storage: Remove distributor todo comment (#107623) Remove todo --- pkg/storage/unified/resource/search_server_distributor.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/storage/unified/resource/search_server_distributor.go b/pkg/storage/unified/resource/search_server_distributor.go index 2febf5416ab..9637e3055cb 100644 --- a/pkg/storage/unified/resource/search_server_distributor.go +++ b/pkg/storage/unified/resource/search_server_distributor.go @@ -41,7 +41,6 @@ func ProvideSearchDistributorServer(cfg *setting.Cfg, features featuremgmt.Featu grpcServer := grpcHandler.GetServer() - // resourcepb.RegisterBulkStoreServer(grpcServer, distributorServer) resourcepb.RegisterResourceIndexServer(grpcServer, distributorServer) resourcepb.RegisterManagedObjectIndexServer(grpcServer, distributorServer) grpc_health_v1.RegisterHealthServer(grpcServer, healthService) @@ -104,11 +103,6 @@ func (ds *distributorServer) GetStats(ctx context.Context, r *resourcepb.Resourc return client.GetStats(ctx, r) } -// TODO implement this if we want to support it in cloud -// func (ds *DistributorServer) BulkProcess(srv BulkStore_BulkProcessServer) error { -// return nil -// } - func (ds *distributorServer) CountManagedObjects(ctx context.Context, r *resourcepb.CountManagedObjectsRequest) (*resourcepb.CountManagedObjectsResponse, error) { ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Namespace, "CountManagedObjects") if err != nil { From d0c474019a155be63e052e3e47938ffb790ada3a Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Fri, 4 Jul 2025 15:32:02 +0200 Subject: [PATCH 16/67] Go: exclude `github.com/RoaringBitmap/real-roaring-datasets` since it doesn't contain a LICENSE file. (#107609) go.mod: exclude package without license file Signed-off-by: bergquist --- go.mod | 5 +++++ go.sum | 1 - go.work.sum | 8 -------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 22b9737d24d..d029517eba2 100644 --- a/go.mod +++ b/go.mod @@ -618,3 +618,8 @@ exclude github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // This was retracted, but seems to be known by the Go module proxy, and is // otherwise pulled in as a transitive dependency. exclude k8s.io/client-go v12.0.0+incompatible + +// This package contains test data for github.com/RoaringBitmap/roaring, which is +// only used to run tests and not required for building the Grafana binary. +// Since the test data doesn't contain a license file we exclude it. +exclude github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 diff --git a/go.sum b/go.sum index d0fbecc682f..5c026a7455a 100644 --- a/go.sum +++ b/go.sum @@ -763,7 +763,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/RoaringBitmap/gocroaring v0.4.0/go.mod h1:NieMwz7ZqwU2DD73/vvYwv7r4eWBKuPVSXZIpsaMwCI= -github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76/go.mod h1:oM0MHmQ3nDsq609SS36p+oYbRi16+oVvU2Bw4Ipv0SE= github.com/RoaringBitmap/roaring v0.9.1/go.mod h1:h1B7iIUOmnAeb5ytYMvnHJwxMc6LUrwBnzXWRuqTQUc= github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= diff --git a/go.work.sum b/go.work.sum index b2fd956859a..b9b914db505 100644 --- a/go.work.sum +++ b/go.work.sum @@ -659,14 +659,6 @@ github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go. github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI= github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA= github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA= -github.com/grafana/grafana/pkg/build v0.0.0-20250220114259-be81314e2118/go.mod h1:STVpVboMYeBAfyn6Zw6XHhTHqUxzMy7pzRiVgk1l0W0= -github.com/grafana/grafana/pkg/build v0.0.0-20250227105625-8f465f124924/go.mod h1:Vw0LdoMma64VgIMVpRY3i0D156jddgUGjTQBOcyeF3k= -github.com/grafana/grafana/pkg/build v0.0.0-20250227163402-d78c646f93bb/go.mod h1:Vw0LdoMma64VgIMVpRY3i0D156jddgUGjTQBOcyeF3k= -github.com/grafana/grafana/pkg/build v0.0.0-20250403075254-4918d8720c61/go.mod h1:LGVnSwdrS0ZnJ2WXEl5acgDoYPm74EUSFavca1NKHI8= -github.com/grafana/grafana/pkg/build v0.0.0-20250625151647-35f89a456cc6/go.mod h1:dIu5dZy00k2TBdpVBXkvSbxHNj5H7lW/sOTpJTtKIXg= -github.com/grafana/grafana/pkg/build v0.0.0-20250703134522-185ce90a4b20/go.mod h1:uU3TK/cKf6IdO7ou7CKgNNb8GEnW761Ecdb8tk8DQLg= -github.com/grafana/grafana/pkg/build v0.0.0-20250703142147-cfd3b9f58211/go.mod h1:uU3TK/cKf6IdO7ou7CKgNNb8GEnW761Ecdb8tk8DQLg= -github.com/grafana/grafana/pkg/build v0.0.0-20250703171819-20f30462ad64/go.mod h1:uU3TK/cKf6IdO7ou7CKgNNb8GEnW761Ecdb8tk8DQLg= github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c= 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= From 17ecd4c05170a0356b51138ebb0579c04738adcd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:43:01 +0100 Subject: [PATCH 17/67] Update dependency typescript to v5.8.3 (#101501) * Update dependency typescript to v5.8.3 * get correct window type * fix @grafana/data * update lockfile --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- package.json | 2 +- packages/grafana-alerting/package.json | 2 +- packages/grafana-data/package.json | 2 +- .../grafana-data/src/utils/location.test.ts | 11 ++-- packages/grafana-data/src/utils/url.test.ts | 7 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-flamegraph/package.json | 2 +- packages/grafana-i18n/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-runtime/package.json | 2 +- packages/grafana-schema/package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-test-utils/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../unified/hooks/useReturnTo.test.tsx | 5 +- .../scene/GoToSnapshotOriginButton.test.tsx | 7 +- .../components/ShareModal/ShareEmbed.test.tsx | 7 +- .../components/ShareModal/ShareLink.test.tsx | 5 +- .../datasources/state/actions.test.ts | 10 +-- .../app/features/playlist/PlaylistSrv.test.ts | 9 +-- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../plugins/datasource/jaeger/package.json | 2 +- .../plugins/datasource/loki/streaming.test.ts | 17 ++--- .../app/plugins/datasource/mssql/package.json | 2 +- .../app/plugins/datasource/mysql/package.json | 2 +- .../app/plugins/datasource/parca/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 66 +++++++++---------- 35 files changed, 102 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index 3fae3bc2626..198394a4313 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,7 @@ "tracelib": "1.0.1", "ts-jest": "29.2.5", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1", "webpack-assets-manifest": "^5.1.0", "webpack-cli": "6.0.1", diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index d02050db6ac..32ab506a757 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -78,7 +78,7 @@ "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "type-fest": "^4.40.0", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "@grafana/runtime": ">=11.6 <= 12.x", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index cf964ed7fb7..20c164108fe 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -99,7 +99,7 @@ "rollup": "^4.22.4", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/grafana-data/src/utils/location.test.ts b/packages/grafana-data/src/utils/location.test.ts index b0fd82bbd98..b2b2ee4bcf2 100644 --- a/packages/grafana-data/src/utils/location.test.ts +++ b/packages/grafana-data/src/utils/location.test.ts @@ -5,13 +5,14 @@ import { GrafanaConfig } from '../types/config'; import { locationUtil } from './location'; describe('locationUtil', () => { - const { location } = window; + const win: typeof globalThis = window; + const { location } = win; beforeEach(() => { // @ts-ignore - delete window.location; + delete win.location; - window.location = { + win.location = { ...location, hash: '#hash', host: 'www.domain.com:9877', @@ -26,7 +27,7 @@ describe('locationUtil', () => { }); afterEach(() => { - window.location = location; + win.location = location; }); describe('stripBaseFromUrl', () => { @@ -106,7 +107,7 @@ describe('locationUtil', () => { describe('when origin does not have a port in it', () => { beforeEach(() => { - window.location = { + win.location = { ...location, hash: '#hash', host: 'www.domain.com', diff --git a/packages/grafana-data/src/utils/url.test.ts b/packages/grafana-data/src/utils/url.test.ts index d0be86cb209..7970529ab85 100644 --- a/packages/grafana-data/src/utils/url.test.ts +++ b/packages/grafana-data/src/utils/url.test.ts @@ -100,11 +100,12 @@ describe('parseKeyValue', () => { }); describe('getUrlSearchParams', () => { - const { location } = window; + const win: typeof globalThis = window; + const { location } = win; // @ts-ignore - delete window.location; + delete win.location; - window.location = { + win.location = { ...location, hash: '#hash', host: 'www.domain.com:9877', diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 6ebb0e74af1..02d669fe21e 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -52,6 +52,6 @@ "@grafana/tsconfig": "^2.0.0", "semver": "^7.7.0", "tslib": "2.8.1", - "typescript": "5.7.3" + "typescript": "5.8.3" } } diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 7adc38a06c9..e8c0d1cfbf4 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -81,7 +81,7 @@ "rollup-plugin-node-externals": "^8.0.0", "ts-jest": "29.2.5", "ts-node": "10.9.2", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index b06109d02db..167cacd8e3a 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -67,7 +67,7 @@ "@types/react": "18.3.18", "rollup": "^4.22.4", "rollup-plugin-copy": "3.5.0", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": ">=18" diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 301762fdfd7..f69200d39e5 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -43,7 +43,7 @@ "react": "18.3.1", "ts-jest": "29.2.5", "ts-node": "10.9.2", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index f46b55fe6a4..845ae3a750f 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -19,7 +19,7 @@ "imports-loader": "^5.0.0", "replace-in-file-webpack-plugin": "1.0.6", "swc-loader": "0.2.6", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-virtual-modules": "^0.6.2" diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index b1d1ba33825..e6c1fc4180c 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -102,7 +102,7 @@ "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "testing-library-selector": "0.3.1", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 3bef89b278e..aa8375e5b7a 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -87,7 +87,7 @@ "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "rollup-plugin-sourcemaps": "0.6.3", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 35c120f7cf0..059974d9982 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -44,7 +44,7 @@ "rollup": "^4.22.4", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "dependencies": { "tslib": "2.8.1" diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 4b241f727a9..126c5845fdd 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -53,7 +53,7 @@ "jest": "^29.6.4", "ts-jest": "29.2.5", "ts-node": "10.9.2", - "typescript": "5.7.3" + "typescript": "5.8.3" }, "peerDependencies": { "@grafana/runtime": "10.4.0-pre" diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index a74497f16f8..e0a70df4f1f 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -51,6 +51,6 @@ "devDependencies": { "@grafana/tsconfig": "^2.0.0", "@types/node": "22.15.0", - "typescript": "5.7.3" + "typescript": "5.8.3" } } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 443b8cc01ae..2ead89e5028 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -200,7 +200,7 @@ "sass-loader": "16.0.4", "storybook": "^8.6.2", "style-loader": "4.0.0", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/features/alerting/unified/hooks/useReturnTo.test.tsx b/public/app/features/alerting/unified/hooks/useReturnTo.test.tsx index 80adb1661cc..b5dadf701db 100644 --- a/public/app/features/alerting/unified/hooks/useReturnTo.test.tsx +++ b/public/app/features/alerting/unified/hooks/useReturnTo.test.tsx @@ -5,9 +5,10 @@ import { useReturnTo } from './useReturnTo'; describe('useReturnTo', () => { beforeAll(() => { + const win: typeof globalThis = window; // @ts-expect-error - delete window.location; - window.location = { origin: 'https://play.grafana.net' } as Location; + delete win.location; + win.location = { origin: 'https://play.grafana.net' } as Location; }); it('should return the fallback value when `returnTo` is not present in the query string', () => { diff --git a/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.test.tsx b/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.test.tsx index 104cccc9d86..846e6090fad 100644 --- a/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.test.tsx +++ b/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.test.tsx @@ -11,10 +11,11 @@ import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton'; describe('GoToSnapshotOriginButton component', () => { beforeEach(async () => { locationService.push('/'); - const location = window.location; + const win: typeof globalThis = window; + const location = win.location; //@ts-ignore - delete window.location; - window.location = { + delete win.location; + win.location = { ...location, href: 'http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash', }; diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx index 3dd19e82a19..aad5b7554e6 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx @@ -30,7 +30,8 @@ jest.mock('app/core/services/context_srv', () => ({ })); function mockLocationHref(href: string) { - const location = window.location; + const win: typeof globalThis = window; + const location = win.location; let search = ''; const searchPos = href.indexOf('?'); @@ -39,8 +40,8 @@ function mockLocationHref(href: string) { } // @ts-ignore - delete window.location; - window.location = { + delete win.location; + win.location = { ...location, href, origin: new URL(href).origin, diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index 03647498a00..780e71bc494 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -32,9 +32,10 @@ function mockLocationHref(href: string) { search = href.substring(searchPos); } + const win: typeof globalThis = window; //@ts-ignore - delete window.location; - window.location = { + delete win.location; + win.location = { ...location, href, origin: new URL(href).origin, diff --git a/public/app/features/datasources/state/actions.test.ts b/public/app/features/datasources/state/actions.test.ts index f3b99a49bbc..0109a8c70fe 100644 --- a/public/app/features/datasources/state/actions.test.ts +++ b/public/app/features/datasources/state/actions.test.ts @@ -105,9 +105,10 @@ describe('loadDataSource()', () => { const dispatch = jest.fn(); const getState = jest.fn(); + const win: typeof globalThis = window; // @ts-ignore - delete window.location; - window.location = {} as Location; + delete win.location; + win.location = {} as Location; (api.getDataSourceByIdOrUid as jest.Mock).mockResolvedValueOnce(dataSourceMock); @@ -126,9 +127,10 @@ describe('loadDataSource()', () => { const dispatch = jest.fn(); const getState = jest.fn(); + const win: typeof globalThis = window; // @ts-ignore - delete window.location; - window.location = {} as Location; + delete win.location; + win.location = {} as Location; (api.getDataSourceByIdOrUid as jest.Mock).mockResolvedValueOnce(dataSourceMock); diff --git a/public/app/features/playlist/PlaylistSrv.test.ts b/public/app/features/playlist/PlaylistSrv.test.ts index 77c45ca388b..9b7bed662a7 100644 --- a/public/app/features/playlist/PlaylistSrv.test.ts +++ b/public/app/features/playlist/PlaylistSrv.test.ts @@ -50,15 +50,16 @@ function createPlaylistSrv(): PlaylistSrv { } const mockWindowLocation = (): [jest.Mock, () => void] => { - const oldLocation = window.location; + const win: typeof globalThis = window; + const oldLocation = win.location; const hrefMock = jest.fn(); // JSDom defines window in a way that you cannot tamper with location so this seems to be the only way to change it. // https://github.com/facebook/jest/issues/5124#issuecomment-446659510 //@ts-ignore - delete window.location; + delete win.location; - window.location = {} as Location; + win.location = {} as Location; // Only mocking href as that is all this test needs, but otherwise there is lots of things missing, so keep that // in mind if this is reused. @@ -67,7 +68,7 @@ const mockWindowLocation = (): [jest.Mock, () => void] => { get: hrefMock, }); const unmock = () => { - window.location = oldLocation; + win.location = oldLocation; }; return [hrefMock, unmock]; }; diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 7ff99e186da..fcdc79473e9 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -41,7 +41,7 @@ "i18next-parser": "9.3.0", "react-select-event": "5.5.1", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 12a8e878f30..95a21319137 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -41,7 +41,7 @@ "@types/react-dom": "18.3.5", "react-select-event": "5.5.1", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index 9dc6e60aa20..17a6f59aa44 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -26,7 +26,7 @@ "@types/node": "22.15.0", "@types/react": "18.3.18", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 43228b83c8e..fa8129f30da 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -35,7 +35,7 @@ "jest": "29.7.0", "style-loader": "4.0.0", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 3a494f7bd86..72e56014022 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -34,7 +34,7 @@ "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 1e505684375..a1d00286bd9 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -37,7 +37,7 @@ "@types/react-window": "1.8.8", "@types/uuid": "10.0.0", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/loki/streaming.test.ts b/public/app/plugins/datasource/loki/streaming.test.ts index 04a87d20856..3b0d172106f 100644 --- a/public/app/plugins/datasource/loki/streaming.test.ts +++ b/public/app/plugins/datasource/loki/streaming.test.ts @@ -8,20 +8,21 @@ jest.mock('@grafana/runtime', () => ({ })); describe('convertToWebSocketUrl', () => { - const { location } = window; + const win: typeof globalThis = window; + const { location } = win; beforeEach(() => { // @ts-ignore - delete window.location; - window.location = {} as Location; + delete win.location; + win.location = {} as Location; }); afterEach(() => { - window.location = location; + win.location = location; }); it('should convert HTTP URL to WebSocket URL', () => { - window.location.protocol = 'http:'; - window.location.host = 'example.com'; + win.location.protocol = 'http:'; + win.location.host = 'example.com'; const httpUrl = '/api/ds/proxy/1/api/v1/tail/loki?query=a'; const expectedWebSocketUrl = 'ws://example.com/grafana/api/ds/proxy/1/api/v1/tail/loki?query=a'; @@ -32,8 +33,8 @@ describe('convertToWebSocketUrl', () => { }); it('should convert HTTPS URL to WebSocket URL', () => { - window.location.protocol = 'https:'; - window.location.host = 'example.com'; + win.location.protocol = 'https:'; + win.location.host = 'example.com'; const httpsUrl = '/api/ds/proxy/1/api/v1/tail/loki?query=a'; const expectedWebSocketUrl = 'wss://example.com/grafana/api/ds/proxy/1/api/v1/tail/loki?query=a'; diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index d0bb696d900..933d90e34d3 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -28,7 +28,7 @@ "@types/react": "18.3.18", "i18next-parser": "9.3.0", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 8ee9163a902..15f54a2cfdf 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -26,7 +26,7 @@ "@types/node": "22.15.0", "@types/react": "18.3.18", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index d3d998cfeab..e45315d5373 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -27,7 +27,7 @@ "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 09c9f8e8840..9a3e6e4a211 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -54,7 +54,7 @@ "glob": "11.0.1", "react-select-event": "5.5.1", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index c8ac7eebad0..75d8c30f09d 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -30,7 +30,7 @@ "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index c2f96eec6b3..5b823a9cec7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2554,7 +2554,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2585,7 +2585,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2625,7 +2625,7 @@ __metadata: style-loader: "npm:4.0.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2663,7 +2663,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" webpack: "npm:5.97.1" peerDependencies: @@ -2705,7 +2705,7 @@ __metadata: stream-browserify: "npm:3.0.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" webpack: "npm:5.97.1" peerDependencies: @@ -2781,7 +2781,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2812,7 +2812,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2844,7 +2844,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2890,7 +2890,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -2948,7 +2948,7 @@ __metadata: string_decoder: "npm:1.3.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" webpack: "npm:5.97.1" peerDependencies: @@ -2984,7 +2984,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" peerDependencies: "@grafana/runtime": "*" @@ -3015,7 +3015,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.2.0" rollup-plugin-node-externals: "npm:^8.0.0" type-fest: "npm:^4.40.0" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" peerDependencies: "@grafana/runtime": ">=11.6 <= 12.x" "@grafana/ui": ">=11.6 <= 12.x" @@ -3100,7 +3100,7 @@ __metadata: string-hash: "npm:^1.1.3" tinycolor2: "npm:1.6.0" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uplot: "npm:1.6.32" xss: "npm:^1.0.14" peerDependencies: @@ -3124,7 +3124,7 @@ __metadata: rollup-plugin-node-externals: "npm:^8.0.0" semver: "npm:^7.7.0" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" languageName: unknown linkType: soft @@ -3242,7 +3242,7 @@ __metadata: ts-jest: "npm:29.2.5" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -3277,7 +3277,7 @@ __metadata: react-i18next: "npm:^15.0.0" rollup: "npm:^4.22.4" rollup-plugin-copy: "npm:3.5.0" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" peerDependencies: react: ">=18" languageName: unknown @@ -3355,7 +3355,7 @@ __metadata: ts-jest: "npm:29.2.5" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -3379,7 +3379,7 @@ __metadata: replace-in-file-webpack-plugin: "npm:1.0.6" swc-loader: "npm:0.2.6" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" webpack: "npm:5.97.1" webpack-bundle-analyzer: "npm:^4.10.2" webpack-virtual-modules: "npm:^0.6.2" @@ -3492,7 +3492,7 @@ __metadata: rxjs: "npm:7.8.2" semver: "npm:7.7.2" testing-library-selector: "npm:0.3.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" peerDependencies: react: ^18.0.0 @@ -3535,7 +3535,7 @@ __metadata: rollup-plugin-sourcemaps: "npm:0.6.3" rxjs: "npm:7.8.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -3601,7 +3601,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.2.0" rollup-plugin-node-externals: "npm:^8.0.0" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" languageName: unknown linkType: soft @@ -3644,7 +3644,7 @@ __metadata: ts-jest: "npm:29.2.5" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" peerDependencies: "@grafana/runtime": 10.4.0-pre @@ -3658,7 +3658,7 @@ __metadata: "@grafana/tsconfig": "npm:^2.0.0" "@types/node": "npm:22.15.0" msw: "npm:2.10.2" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" languageName: unknown linkType: soft @@ -3806,7 +3806,7 @@ __metadata: style-loader: "npm:4.0.0" tinycolor2: "npm:1.6.0" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uplot: "npm:1.6.32" uuid: "npm:11.1.0" uwrap: "npm:0.1.1" @@ -18496,7 +18496,7 @@ __metadata: tslib: "npm:2.8.1" tween-functions: "npm:^1.2.0" type-fest: "npm:^4.18.2" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uplot: "npm:1.6.32" uuid: "npm:11.1.0" visjs-network: "npm:4.25.0" @@ -31389,13 +31389,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:>=2.7, typescript@npm:>=3 < 6, typescript@npm:^5.0.4, typescript@npm:^5.4.5, typescript@npm:^5.5.4": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" +"typescript@npm:5.8.3, typescript@npm:>=2.7, typescript@npm:>=3 < 6, typescript@npm:^5.0.4, typescript@npm:^5.4.5, typescript@npm:^5.5.4": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78 + checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 languageName: node linkType: hard @@ -31429,13 +31429,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A>=2.7#optional!builtin, typescript@patch:typescript@npm%3A>=3 < 6#optional!builtin, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.5#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin, typescript@patch:typescript@npm%3A>=2.7#optional!builtin, typescript@patch:typescript@npm%3A>=3 < 6#optional!builtin, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.5#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c + checksum: 10/b9b1e73dabac5dc730c041325dbd9c99467c1b0d239f1b74ec3b90d831384af3e2ba973946232df670519147eb51a2c20f6f96163cea2b359f03de1e2091cc4f languageName: node linkType: hard From 81a20caf6bb98db590d83dfd897d805105d5f390 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:06:53 +0000 Subject: [PATCH 18/67] Update dependency @floating-ui/react to v0.27.13 (#107628) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 58 ++++++++++++------------ 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 198394a4313..7d77547f61f 100644 --- a/package.json +++ b/package.json @@ -268,7 +268,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.27.12", + "@floating-ui/react": "0.27.13", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index e6c1fc4180c..028a9c11817 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@floating-ui/react": "0.27.12", + "@floating-ui/react": "0.27.13", "@grafana/data": "12.1.0-pre", "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/i18n": "12.1.0-pre", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 2ead89e5028..0b91608ea70 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -65,7 +65,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", - "@floating-ui/react": "0.27.12", + "@floating-ui/react": "0.27.13", "@grafana/data": "12.1.0-pre", "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", diff --git a/yarn.lock b/yarn.lock index 5b823a9cec7..6c539d128ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2335,48 +2335,48 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.0.0": - version: 1.6.2 - resolution: "@floating-ui/core@npm:1.6.2" +"@floating-ui/core@npm:^1.7.2": + version: 1.7.2 + resolution: "@floating-ui/core@npm:1.7.2" dependencies: - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/5c940ef3d397aa23f859ecb033bda408dde20820af3f82090a889c35a99826cfaa7864e8131b9906a26b2c04f31fa468538a28d0715b34de541e0776e0f82d03 + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10/2885a4c824f8d148222714cf2650c29bf3b30b70f465b990734766aafb1366ba23b4a285918c4f3ee67161096e7b9e4fbe0b3ca31e0f78800338cb4e6292912d languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.0, @floating-ui/dom@npm:^1.0.1": - version: 1.6.5 - resolution: "@floating-ui/dom@npm:1.6.5" +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.7.2": + version: 1.7.2 + resolution: "@floating-ui/dom@npm:1.7.2" dependencies: - "@floating-ui/core": "npm:^1.0.0" - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/d421e7f239e9af5a2a4c7a560c29b8ce1f29398c411c8e3bd0c33a2ce800e13a378749a1606e4f6b460830f4007c459792534821013262d24d1385476b1ba48d + "@floating-ui/core": "npm:^1.7.2" + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10/2d581459973b66024c47f039cabdc059b39c72abca93a4a0a8110e0a90b79d3fb84e49099afb73e13b3e7f17096070422220d996b206a5f0120c92bd7c48b98b languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.1.2, @floating-ui/react-dom@npm:^2.1.3": - version: 2.1.3 - resolution: "@floating-ui/react-dom@npm:2.1.3" +"@floating-ui/react-dom@npm:^2.1.2, @floating-ui/react-dom@npm:^2.1.4": + version: 2.1.4 + resolution: "@floating-ui/react-dom@npm:2.1.4" dependencies: - "@floating-ui/dom": "npm:^1.0.0" + "@floating-ui/dom": "npm:^1.7.2" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/185223bb436f719943054f634ab5c885b32df745c7bc27504d77783944cfe02ce0f16a01d222d7abc511d3fc56fd36703ce6056eb2c4451b6666a7800b74a29f + checksum: 10/8fad23bd7a94b50bbd715109a272528978511b793b8954098c8e99dd0b63557a8641b315ce09301ef014eada560fd8638e4dea4c21cf7ce8d93f0038a59cd442 languageName: node linkType: hard -"@floating-ui/react@npm:0.27.12": - version: 0.27.12 - resolution: "@floating-ui/react@npm:0.27.12" +"@floating-ui/react@npm:0.27.13": + version: 0.27.13 + resolution: "@floating-ui/react@npm:0.27.13" dependencies: - "@floating-ui/react-dom": "npm:^2.1.3" - "@floating-ui/utils": "npm:^0.2.9" + "@floating-ui/react-dom": "npm:^2.1.4" + "@floating-ui/utils": "npm:^0.2.10" tabbable: "npm:^6.0.0" peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/1bd397df1aca2a095d46085f8f8763eb7ade13997a73d5148b559709991737d575d9706612a56da8051081e52d5e441ccabcc3057c81395faf2cb871f3edc2b8 + checksum: 10/84a5247e74b7666c069680e8df69e0dcdbf846f6192760c1ec8c53361b2e316895bb43847fba92f6c82e563db45794aeb8755560e769c610ef4a1b956a52d105 languageName: node linkType: hard @@ -2394,10 +2394,10 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.8, @floating-ui/utils@npm:^0.2.9": - version: 0.2.9 - resolution: "@floating-ui/utils@npm:0.2.9" - checksum: 10/0ca786347db3dd8d9034b86d1449fabb96642788e5900cc5f2aee433cd7b243efbcd7a165bead50b004ee3f20a90ddebb6a35296fc41d43cfd361b6f01b69ffb +"@floating-ui/utils@npm:^0.2.10, @floating-ui/utils@npm:^0.2.8": + version: 0.2.10 + resolution: "@floating-ui/utils@npm:0.2.10" + checksum: 10/b635ea865a8be2484b608b7157f5abf9ed439f351011a74b7e988439e2898199a9a8b790f52291e05bdcf119088160dc782d98cff45cc98c5a271bc6f51327ae languageName: node linkType: hard @@ -3433,7 +3433,7 @@ __metadata: resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus" dependencies: "@emotion/css": "npm:11.13.5" - "@floating-ui/react": "npm:0.27.12" + "@floating-ui/react": "npm:0.27.13" "@grafana/data": "npm:12.1.0-pre" "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/i18n": "npm:12.1.0-pre" @@ -3678,7 +3678,7 @@ __metadata: "@emotion/react": "npm:11.14.0" "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" - "@floating-ui/react": "npm:0.27.12" + "@floating-ui/react": "npm:0.27.13" "@grafana/data": "npm:12.1.0-pre" "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" @@ -18178,7 +18178,7 @@ __metadata: "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.27.12" + "@floating-ui/react": "npm:0.27.13" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/alerting": "workspace:*" From 1e3f7bfd67e535e8d04277c56e40978ddfd8ec16 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 4 Jul 2025 15:08:17 +0100 Subject: [PATCH 19/67] Internationalisation: Enable automatic scheduling for translations (#107629) enable schedule for crowdin task creation --- .github/workflows/i18n-crowdin-create-tasks.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml index 6ecaa883e01..e26d7806c5e 100644 --- a/.github/workflows/i18n-crowdin-create-tasks.yml +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -2,10 +2,9 @@ name: Crowdin automatic task management on: workflow_dispatch: - # once a week on Sunday at midnight - # TODO enable once we're ready to create tasks automatically - # schedule: - # - cron: "0 0 * * 0" + # once a month on the first day of the month at midnight + schedule: + - cron: "0 0 1 * *" jobs: create-tasks-in-crowdin: From 107c7fd8df12a589375fc51005d18b16ceb0f91d Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Fri, 4 Jul 2025 10:13:15 -0400 Subject: [PATCH 20/67] fix empty folder form on root folder level (#107630) --- .../browse-dashboards/hooks/useProvisionedFolderFormData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts b/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts index 527ad6bf195..6fbc5f6c6eb 100644 --- a/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts +++ b/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts @@ -39,7 +39,7 @@ export function useProvisionedFolderFormData({ const initialValues = useMemo(() => { // Only create initial values when we have the data - if (!repository || !folder || isLoading) { + if (!repository || isLoading) { return undefined; } From efbcf9d8f7d39a0f5b425248fdbb46bba0afffa2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:30:53 +0000 Subject: [PATCH 21/67] Update dependency @prometheus-io/lezer-promql to v0.304.2 (#107631) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 028a9c11817..091d6fab771 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -53,7 +53,7 @@ "@lezer/common": "1.2.3", "@lezer/highlight": "1.2.1", "@lezer/lr": "1.4.2", - "@prometheus-io/lezer-promql": "0.304.1", + "@prometheus-io/lezer-promql": "0.304.2", "@reduxjs/toolkit": "2.5.1", "@types/debounce-promise": "3.1.9", "@types/lodash": "4.17.15", diff --git a/yarn.lock b/yarn.lock index 6c539d128ee..15035d2d95f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3447,7 +3447,7 @@ __metadata: "@lezer/common": "npm:1.2.3" "@lezer/highlight": "npm:1.2.1" "@lezer/lr": "npm:1.4.2" - "@prometheus-io/lezer-promql": "npm:0.304.1" + "@prometheus-io/lezer-promql": "npm:0.304.2" "@reduxjs/toolkit": "npm:2.5.1" "@rollup/plugin-dynamic-import-vars": "npm:2.1.5" "@rollup/plugin-image": "npm:3.0.3" @@ -6158,13 +6158,13 @@ __metadata: languageName: node linkType: hard -"@prometheus-io/lezer-promql@npm:0.304.1": - version: 0.304.1 - resolution: "@prometheus-io/lezer-promql@npm:0.304.1" +"@prometheus-io/lezer-promql@npm:0.304.2": + version: 0.304.2 + resolution: "@prometheus-io/lezer-promql@npm:0.304.2" peerDependencies: "@lezer/highlight": ^1.1.2 "@lezer/lr": ^1.2.3 - checksum: 10/d308388bb817423c2b966e8d8e27deb02e98915af91043baf7c7b9634fb5ff3655378b237cc0766e5f0446fed74027f2c385aa3cff7921620e7cbdd1844bc95c + checksum: 10/dd2ce48e0f7a90ab3afb707e56252cf9debf483149bf1d63f8f80e408f76b8f8df3a99fc147876f4d360879be800e323ed53bde52c4599cd7263dac54a3d7ac6 languageName: node linkType: hard From 443ea5924cfe719cf0e093874ab8595b933daef2 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 4 Jul 2025 15:43:48 +0100 Subject: [PATCH 22/67] Eslint: Allow 'unset' in no-border-radius-literal lint rule (#106619) * allow border radius of 0 * Prefer unset or initial over 0 * readme * add an autofix for 0 -> unset * replace 0 with unset * fix fixes tests * fix snapshot * Fix lint in SecretFormField * fix unused cx --- packages/grafana-eslint-rules/README.md | 2 + .../rules/no-border-radius-literal.cjs | 41 +++++++++-- .../tests/no-border-radius-literal.test.js | 73 +++++++++++++++++-- .../src/components/Button/ButtonGroup.tsx | 8 +- .../grafana-ui/src/components/Input/Input.tsx | 40 +++++----- .../RefreshPicker/RefreshPicker.tsx | 4 +- .../SecretFormField/SecretFormField.tsx | 17 +---- pkg/extensions/enterprise_imports.go | 8 +- .../scene/VariableControls.tsx | 4 +- .../VariableQueryEditor.test.tsx.snap | 2 +- 10 files changed, 135 insertions(+), 64 deletions(-) diff --git a/packages/grafana-eslint-rules/README.md b/packages/grafana-eslint-rules/README.md index 1fb043a4ec0..3fb9a053670 100644 --- a/packages/grafana-eslint-rules/README.md +++ b/packages/grafana-eslint-rules/README.md @@ -18,6 +18,8 @@ Check if border-radius theme tokens are used. To improve the consistency across Grafana we encourage devs to use tokens instead of custom values. In this case, we want the `borderRadius` to use the appropriate token such as `theme.shape.radius.default`, `theme.shape.radius.pill` or `theme.shape.radius.circle`. +Instead of using `0` to remove a previously set border-radius, use `unset`. + ### `no-unreduced-motion` Avoid direct use of `animation*` or `transition*` properties. diff --git a/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs b/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs index 27535a5e76c..8e9eeba6c58 100644 --- a/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs +++ b/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs @@ -4,6 +4,16 @@ const createRule = ESLintUtils.RuleCreator( (name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}` ); +const BORDER_RADIUS_PROPERTIES = [ + 'borderRadius', + 'borderTopLeftRadius', + 'borderTopRightRadius', + 'borderBottomLeftRadius', + 'borderBottomRightRadius', +]; + +const RE_ZERO_VALUE = /^0([a-zA-Z%]*)$/; + const borderRadiusRule = createRule({ create(context) { return { @@ -11,13 +21,30 @@ const borderRadiusRule = createRule({ if ( node.type === AST_NODE_TYPES.Property && node.key.type === AST_NODE_TYPES.Identifier && - node.key.name === 'borderRadius' && + BORDER_RADIUS_PROPERTIES.includes(node.key.name) && node.value.type === AST_NODE_TYPES.Literal ) { - context.report({ - node, - messageId: 'borderRadiusId', - }); + const value = node.value.value; + + if (value === 'unset' || value === 'initial') { + // Allow 'unset' or 'initial' to remove border radius + return; + } else if (value === 0 || RE_ZERO_VALUE.test(value)) { + // Require 'unset' or 'initial' to remove border radius instead of `0` or `0px` + context.report({ + node, + messageId: 'borderRadiusNoZeroValue', + fix(fixer) { + return fixer.replaceText(node.value, "'unset'"); + }, + }); + } else { + // Otherwise, require theme tokens are used + context.report({ + node, + messageId: 'borderRadiusUseTokens', + }); + } } }, }; @@ -25,11 +52,13 @@ const borderRadiusRule = createRule({ name: 'no-border-radius-literal', meta: { type: 'problem', + fixable: 'code', docs: { description: 'Check if border-radius theme tokens are used', }, messages: { - borderRadiusId: 'Prefer using theme.shape.radius tokens instead of literal values.', + borderRadiusUseTokens: 'Prefer using theme.shape.radius tokens instead of literal values.', + borderRadiusNoZeroValue: 'Use unset or initial to remove a border radius.', }, schema: [], }, diff --git a/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js b/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js index 7410059db50..c2bf3f94de2 100644 --- a/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js +++ b/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js @@ -14,8 +14,12 @@ RuleTester.setDefaultConfig({ }, }); -const expectedError = { - messageId: 'borderRadiusId', +const useTokenError = { + messageId: 'borderRadiusUseTokens', +}; + +const noZeroValueError = { + messageId: 'borderRadiusNoZeroValue', }; const ruleTester = new RuleTester(); @@ -31,20 +35,44 @@ ruleTester.run('eslint no-border-radius-literal', noBorderRadiusLiteral, { { code: `css({ borderRadius: theme.shape.radius.pill })`, }, + { + code: `css({ borderTopLeftRadius: theme.shape.radius.pill })`, + }, + { + code: `css({ borderTopRightRadius: theme.shape.radius.pill })`, + }, + { + code: `css({ borderBottomLeftRadius: theme.shape.radius.pill })`, + }, + { + code: `css({ borderBottomRightRadius: theme.shape.radius.pill })`, + }, + + // allow values to remove border radius + { + code: `css({ borderRadius: 'initial' })`, + }, + { + code: `css({ borderRadius: 'unset' })`, + }, ], invalid: [ { code: `css({ borderRadius: '2px' })`, - errors: [expectedError], + errors: [useTokenError], + }, + { + code: `css({ borderRadius: 2 })`, // should error on px shorthand + errors: [useTokenError], }, { code: `css({ lineHeight: 1 }, { borderRadius: '2px' })`, - errors: [expectedError], + errors: [useTokenError], }, { code: `css([{ lineHeight: 1 }, { borderRadius: '2px' }])`, - errors: [expectedError], + errors: [useTokenError], }, { name: 'nested classes', @@ -56,7 +84,40 @@ css({ }, }, })`, - errors: [expectedError], + errors: [useTokenError], + }, + { + code: `css({ borderTopLeftRadius: 1 })`, + errors: [useTokenError], + }, + { + code: `css({ borderTopRightRadius: "2px" })`, + errors: [useTokenError], + }, + { + code: `css({ borderBottomLeftRadius: 3 })`, + errors: [useTokenError], + }, + { + code: `css({ borderBottomRightRadius: "4px" })`, + errors: [useTokenError], + }, + + // should use unset or initial to remove border radius + { + code: `css({ borderRadius: 0 })`, + output: `css({ borderRadius: 'unset' })`, + errors: [noZeroValueError], + }, + { + code: `css({ borderRadius: '0px' })`, + output: `css({ borderRadius: 'unset' })`, + errors: [noZeroValueError], + }, + { + code: `css({ borderRadius: "0%" })`, + output: `css({ borderRadius: 'unset' })`, + errors: [noZeroValueError], }, ], }); diff --git a/packages/grafana-ui/src/components/Button/ButtonGroup.tsx b/packages/grafana-ui/src/components/Button/ButtonGroup.tsx index 3ae3043cb96..176249abf49 100644 --- a/packages/grafana-ui/src/components/Button/ButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Button/ButtonGroup.tsx @@ -27,14 +27,14 @@ const getStyles = (theme: GrafanaTheme2) => ({ borderRadius: theme.shape.radius.default, '> .button-group:not(:first-child) > button, > button:not(:first-child)': { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', borderLeft: `1px solid rgba(255, 255, 255, 0.12)`, }, '> .button-group:not(:last-child) > button, > button:not(:last-child)': { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', borderRight: `1px solid rgba(0, 0, 0, 0.12)`, }, }), diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index fe142c8799a..62aca888d03 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -168,8 +168,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: '&:not(:first-child):last-child': { '> input': { borderLeft: 'none', - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', }, }, @@ -177,8 +177,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: '&:first-child:not(:last-child)': { '> input': { borderRight: 'none', - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', }, }, @@ -186,10 +186,10 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: '&:not(:first-child):not(:last-child)': { '> input': { borderRight: 'none', - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', }, }, @@ -238,20 +238,20 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: position: 'relative', '&:first-child': { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', '> :last-child': { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', }, }, '&:last-child': { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', '> :first-child': { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', }, }, '> *:focus': { @@ -266,8 +266,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: paddingLeft: theme.spacing(1), paddingRight: theme.spacing(0.5), borderRight: 'none', - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderTopRightRadius: 'unset', + borderBottomRightRadius: 'unset', }) ), suffix: cx( @@ -277,8 +277,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: paddingLeft: theme.spacing(1), paddingRight: theme.spacing(1), borderLeft: 'none', - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', right: 0, }) ), diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index 22d300bfacc..d99958e88d1 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -118,8 +118,8 @@ export class RefreshPicker extends PureComponent { {!noIntervalPicker && ( , 'onRe interactive?: boolean; } -const getSecretFormFieldStyles = () => { - return { - noRadiusInput: css({ - borderBottomRightRadius: '0 !important', - borderTopRightRadius: '0 !important', - }), - noRadiusButton: css({ - borderBottomLeftRadius: '0 !important', - borderTopLeftRadius: '0 !important', - }), - }; -}; - /** * Form field that has 2 states configured and not configured. If configured it will not show its contents and adds * a reset button that will clear the input and makes it accessible. In non configured state it behaves like normal @@ -58,7 +44,6 @@ export const SecretFormField = ({ interactive, ...inputProps }: Props) => { - const styles = getSecretFormFieldStyles(); return ( ({ display: 'flex', // No border for second element (inputs) as label and input border is shared '> :nth-child(2)': css({ - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 'unset', + borderBottomLeftRadius: 'unset', }), }), labelWrapper: css({ diff --git a/public/app/plugins/datasource/cloud-monitoring/components/__snapshots__/VariableQueryEditor.test.tsx.snap b/public/app/plugins/datasource/cloud-monitoring/components/__snapshots__/VariableQueryEditor.test.tsx.snap index fa53a1bac2d..c2d75c72c2d 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/__snapshots__/VariableQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/cloud-monitoring/components/__snapshots__/VariableQueryEditor.test.tsx.snap @@ -67,7 +67,7 @@ exports[`VariableQueryEditor renders correctly 1`] = `

+ + + + @@ -180,7 +184,6 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps

- {options.jsonData.sslmode !== PostgresTLSModes.disable ? ( ) : null} - - - {jsonData.sslmode !== PostgresTLSModes.disable ? ( - <> - - + {jsonData.sslmode !== PostgresTLSModes.disable ? ( + <> {jsonData.tlsConfigurationMethod === PostgresTLSMethods.fileContent ? ( )} - - - ) : null} + + ) : null} + From 5a39f29c74672f03a10d801485f36e8502ffb897 Mon Sep 17 00:00:00 2001 From: Gareth Date: Mon, 7 Jul 2025 13:04:07 +0100 Subject: [PATCH 45/67] Prometheus: display a warning when subquery is used (#106199) * display warning when subquery used * lint fix * update tests to work with new return value * prettier * use object shorthand * only warn agains x:x, ignore 2x:x * update error message * prettier * fix tests * add tests * prettier * Update packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.ts Co-authored-by: ismail simsek * prettier --------- Co-authored-by: ismail simsek --- .../monaco-query-field/MonacoQueryField.tsx | 37 ++++--- .../validation.test.ts | 83 ++++++++-------- .../monaco-completion-provider/validation.ts | 96 ++++++++++++++----- 3 files changed, 139 insertions(+), 77 deletions(-) diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx index 1c24fadd96b..0200645d392 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx @@ -257,23 +257,30 @@ const MonacoQueryField = (props: Props) => { return; } const query = model.getValue(); - const errors = - validateQuery( - query, - datasource.interpolateString(query, placeHolderScopedVars), - model.getLinesContent(), - parser - ) || []; + const { errors, warnings } = validateQuery( + query, + datasource.interpolateString(query, placeHolderScopedVars), + model.getLinesContent(), + parser + ); - const markers = errors.map(({ error, ...boundary }) => ({ - message: `${ - error ? `Error parsing "${error}"` : 'Parse error' - }. The query appears to be incorrect and could fail to be executed.`, - severity: monaco.MarkerSeverity.Error, - ...boundary, - })); + const errorMarkers = errors.map(({ issue, ...boundary }) => { + return { + message: `${issue ? `Error parsing "${issue}"` : 'Parse error'}. The query appears to be incorrect and could fail to be executed.`, + severity: monaco.MarkerSeverity.Error, + ...boundary, + }; + }); - monaco.editor.setModelMarkers(model, 'owner', markers); + const warningMarkers = warnings.map(({ issue, ...boundary }) => { + return { + message: `Warning: ${issue}`, + severity: monaco.MarkerSeverity.Warning, + ...boundary, + }; + }); + + monaco.editor.setModelMarkers(model, 'owner', [...errorMarkers, ...warningMarkers]); }); } }} diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.test.ts index 3768457dfce..14acf601422 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.test.ts @@ -1,11 +1,11 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/validation.test.ts import { parser } from '@prometheus-io/lezer-promql'; -import { validateQuery } from './validation'; +import { validateQuery, warningTypes } from './validation'; describe('Monaco Query Validation', () => { test('Identifies empty queries as valid', () => { - expect(validateQuery('', '', [], parser)).toBeFalsy(); + expect(validateQuery('', '', [], parser)).toEqual({ errors: [], warnings: [] }); }); test.each([ @@ -17,45 +17,30 @@ describe('Monaco Query Validation', () => { 'rate(http_requests_total[5m])', 'topk(3, sum by (app, proc) (rate(instance_cpu_time_ns[5m])))', ])('Identifies valid queries', (query: string) => { - expect(validateQuery(query, query, [], parser)).toBeFalsy(); + expect(validateQuery(query, query, [], parser).errors).toEqual([]); }); test('Identifies invalid queries', () => { // Missing } at the end let query = 'access_evaluation_duration_sum{job="grafana"'; - expect(validateQuery(query, query, [query], parser)).toEqual([ - { - endColumn: 45, - endLineNumber: 1, - error: '{job="grafana"', - startColumn: 31, - startLineNumber: 1, - }, - ]); + expect(validateQuery(query, query, [query], parser)).toEqual({ + errors: [{ endColumn: 45, endLineNumber: 1, issue: '{job="grafana"', startColumn: 31, startLineNumber: 1 }], + warnings: [], + }); // Missing handler="value" query = 'http_requests_total{job="apiserver", handler}[5m]'; - expect(validateQuery(query, query, [query], parser)).toEqual([ - { - endColumn: 45, - endLineNumber: 1, - error: 'handler', - startColumn: 38, - startLineNumber: 1, - }, - ]); + expect(validateQuery(query, query, [query], parser)).toEqual({ + errors: [{ endColumn: 45, endLineNumber: 1, issue: 'handler', startColumn: 38, startLineNumber: 1 }], + warnings: [], + }); // Missing : in [30s:5s] query = 'max_over_time(deriv(rate(distance_covered_total[5s])[30s5s])[10m:])'; - expect(validateQuery(query, query, [query], parser)).toEqual([ - { - endColumn: 59, - endLineNumber: 1, - error: '5s', - startColumn: 57, - startLineNumber: 1, - }, - ]); + expect(validateQuery(query, query, [query], parser)).toEqual({ + errors: [{ endColumn: 59, endLineNumber: 1, issue: '5s', startColumn: 57, startLineNumber: 1 }], + warnings: [], + }); }); test('Identifies valid multi-line queries', () => { @@ -64,7 +49,7 @@ sum by (job) ( rate(http_requests_total[5m]) )`; const queryLines = query.split('\n'); - expect(validateQuery(query, query, queryLines, parser)).toBeFalsy(); + expect(validateQuery(query, query, queryLines, parser)).toEqual({ errors: [], warnings: [] }); }); test('Identifies invalid multi-line queries', () => { @@ -73,14 +58,32 @@ sum by (job) ( rate(http_requests_total[]) )`; const queryLines = query.split('\n'); - expect(validateQuery(query, query, queryLines, parser)).toEqual([ - { - endColumn: 30, - endLineNumber: 3, - error: '', - startColumn: 30, - startLineNumber: 3, - }, - ]); + expect(validateQuery(query, query, queryLines, parser)).toEqual({ + errors: [{ endColumn: 30, endLineNumber: 3, issue: '', startColumn: 30, startLineNumber: 3 }], + warnings: [], + }); + }); + + test('Warns agains subqueries with same duration and step', () => { + const query = 'rate(http_requests_total[5m:5m])'; + const queryLines = query.split('\n'); + expect(validateQuery(query, query, queryLines, parser)).toEqual({ + errors: [], + warnings: [ + { endColumn: 32, endLineNumber: 1, issue: warningTypes.SubqueryExpr, startColumn: 6, startLineNumber: 1 }, + ], + }); + }); + + test('Warns agains queries with multiple subqueries', () => { + const query = 'quantile_over_time(0.5, rate(http_requests_total[1m:1m]) [1m:1m])'; + const queryLines = query.split('\n'); + expect(validateQuery(query, query, queryLines, parser)).toEqual({ + errors: [], + warnings: [ + { issue: warningTypes.SubqueryExpr, startColumn: 25, endColumn: 65, startLineNumber: 1, endLineNumber: 1 }, + { issue: warningTypes.SubqueryExpr, startColumn: 30, endColumn: 56, startLineNumber: 1, endLineNumber: 1 }, + ], + }); }); }); diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.ts index 34ce38c060b..64b96aa8c57 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/validation.ts @@ -5,15 +5,25 @@ import { LRParser } from '@lezer/lr'; // Although 0 isn't explicitly provided in the @grafana/lezer-logql library as the error node ID, it does appear to be the ID of error nodes within lezer. export const ErrorId = 0; -interface ParserErrorBoundary { +export const warningTypes: Record = { + SubqueryExpr: + 'This subquery may return only one data point, preventing rate/increase/delta calculations. Use a range at least twice the step size (e.g., [2x:x]).', +}; + +enum NodeType { + SubqueryExpr = 'SubqueryExpr', + Duration = 'NumberDurationLiteralInDurationContext', +} + +interface ParserIssueBoundary { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number; - error: string; + issue: string; } -interface ParseError { +interface ParseIssue { text: string; node: SyntaxNode; } @@ -30,9 +40,9 @@ export function validateQuery( interpolatedQuery: string, queryLines: string[], parser: LRParser -): ParserErrorBoundary[] | false { +): { errors: ParserIssueBoundary[]; warnings: ParserIssueBoundary[] } { if (!query) { - return false; + return { errors: [], warnings: [] }; } /** @@ -42,51 +52,93 @@ export function validateQuery( * have different lengths. With this, we also exclude irrelevant parser errors that are produced by * lezer not understanding $variables and $__variables, which usually generate 2 or 3 error SyntaxNode. */ - const interpolatedErrors: ParseError[] = parseQuery(interpolatedQuery, parser); - if (!interpolatedErrors.length) { - return false; + const { errors: interpolatedErrors, warnings: interpolatedWarnings } = parseQuery(interpolatedQuery, parser); + if (!interpolatedErrors.length && !interpolatedWarnings.length) { + return { errors: [], warnings: [] }; } - let parseErrors: ParseError[] = interpolatedErrors; + let parseErrors: ParseIssue[] = interpolatedErrors; + let parseWarnings: ParseIssue[] = interpolatedWarnings; if (query !== interpolatedQuery) { - const queryErrors: ParseError[] = parseQuery(query, parser); + const { errors: queryErrors, warnings: queryWarnings } = parseQuery(query, parser); parseErrors = interpolatedErrors.flatMap( (interpolatedError) => queryErrors.filter((queryError) => interpolatedError.text === queryError.text) || interpolatedError ); + parseWarnings = interpolatedWarnings.flatMap( + (interpolatedWarning) => + queryWarnings.filter((queryWarning) => interpolatedWarning.node.from === queryWarning.node.from) || + interpolatedWarning + ); } - return parseErrors.map((parseError) => findErrorBoundary(query, queryLines, parseError)).filter(isErrorBoundary); + const errorBoundaries = parseErrors + .map((parseError) => findIssueBoundary(query, queryLines, parseError, 'error')) + .filter(isValidIssueBoundary); + const warningBoundaries = parseWarnings + .map((parseWarning) => findIssueBoundary(query, queryLines, parseWarning, 'warning')) + .filter(isValidIssueBoundary); + + return { + errors: errorBoundaries, + warnings: warningBoundaries, + }; } function parseQuery(query: string, parser: LRParser) { - const parseErrors: ParseError[] = []; + const parseErrors: ParseIssue[] = []; + const parseWarnings: ParseIssue[] = []; + const tree = parser.parse(query); tree.iterate({ enter: (nodeRef): false | void => { if (nodeRef.type.id === ErrorId) { const node = nodeRef.node; - parseErrors.push({ - node: node, - text: query.substring(node.from, node.to), - }); + parseErrors.push({ node: node, text: query.substring(node.from, node.to) }); + } + + if (nodeRef.type.name === NodeType.SubqueryExpr) { + const node = nodeRef.node; + const durations: string[] = []; + + const children = node.getChildren(NodeType.Duration); + for (const child of children) { + durations.push(query.substring(child.from, child.to)); + } + + if (durations.length === 2 && durations[0] === durations[1]) { + parseWarnings.push({ node: node, text: query.substring(node.from, node.to) }); + } } }, }); - return parseErrors; + + return { errors: parseErrors, warnings: parseWarnings }; } -function findErrorBoundary(query: string, queryLines: string[], parseError: ParseError): ParserErrorBoundary | null { +function findIssueBoundary( + query: string, + queryLines: string[], + parseError: ParseIssue, + issueType: 'error' | 'warning' +): ParserIssueBoundary | null { if (queryLines.length === 1) { const isEmptyString = parseError.node.from === parseError.node.to; const errorNode = isEmptyString && parseError.node.parent ? parseError.node.parent : parseError.node; - const error = isEmptyString ? query.substring(errorNode.from, errorNode.to) : parseError.text; + let issue: string; + + if (issueType === 'error') { + issue = isEmptyString ? query.substring(errorNode.from, errorNode.to) : parseError.text; + } else { + issue = warningTypes[parseError.node.type.name]; + } + return { startLineNumber: 1, startColumn: errorNode.from + 1, endLineNumber: 1, endColumn: errorNode.to + 1, - error, + issue, }; } @@ -105,14 +157,14 @@ function findErrorBoundary(query: string, queryLines: string[], parseError: Pars startColumn: parseError.node.from - startPos + 1, endLineNumber: line + 1, endColumn: parseError.node.to - startPos + 1, - error: parseError.text, + issue: issueType === 'error' ? parseError.text : warningTypes[parseError.node.type.name], }; } return null; } -function isErrorBoundary(boundary: ParserErrorBoundary | null): boundary is ParserErrorBoundary { +function isValidIssueBoundary(boundary: ParserIssueBoundary | null): boundary is ParserIssueBoundary { return boundary !== null; } From b9fba9584921f792d7a6df0e9591418be49f1c38 Mon Sep 17 00:00:00 2001 From: Yash Suresh Chandra Date: Mon, 7 Jul 2025 17:36:01 +0530 Subject: [PATCH 46/67] Dashboard: Fix cache validation to prevent stale cache (#105918) * compare dashboard updated at while fetching * test added * added created field in dashboard dto meta object for k8s api. fixed cache updation check * code refactor * code formatting with prettier * added created at field in DashboardPageProxy.test.tsx --- .../DashboardScenePageStateManager.test.ts | 33 +++++++++++++++++++ .../pages/DashboardScenePageStateManager.ts | 6 +++- public/app/features/dashboard/api/v1.ts | 1 + .../containers/DashboardPageProxy.test.tsx | 1 + 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 5e8b9cd57ca..287140082c7 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -218,6 +218,39 @@ describe('DashboardScenePageStateManager v1', () => { }); describe('caching', () => { + it('should return cached scene if updated_at matches', async () => { + const loader = new DashboardScenePageStateManager({}); + + // set cache + loader.setSceneCache( + 'fake-dash', + new DashboardScene({ title: 'Dashboard 1', uid: 'fake-dash', meta: { created: '1' }, version: 0 }, 'v1') + ); + + // should return cached scene + expect( + loader.transformResponseToScene( + { + meta: { created: '1' }, + dashboard: { title: 'Dashboard 1', uid: 'fake-dash', schemaVersion: 1, version: 0 }, + }, + { uid: 'fake-dash', route: DashboardRoutes.Normal } + )?.state.title + ).toBe('Dashboard 1'); + + // try loading new scene + loader.transformResponseToScene( + { + meta: { created: '2' }, + dashboard: { title: 'Dashboard 2', uid: 'fake-dash', schemaVersion: 1, version: 0 }, + }, + { uid: 'fake-dash', route: DashboardRoutes.Normal } + ); + + // should update cache with new scene + expect(loader.getSceneFromCache('fake-dash').state.title).toBe('Dashboard 2'); + }); + it('should take scene from cache if it exists', async () => { setupLoadDashboardMock({ dashboard: { uid: 'fake-dash', version: 10 }, meta: {} }); diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index e143df2b776..e6c2cb3e0a8 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -371,7 +371,11 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag transformResponseToScene(rsp: DashboardDTO | null, options: LoadDashboardOptions): DashboardScene | null { const fromCache = this.getSceneFromCache(options.uid); - if (fromCache && fromCache.state.version === rsp?.dashboard.version) { + if ( + fromCache && + fromCache.state.version === rsp?.dashboard.version && + fromCache.state.meta.created === rsp?.meta.created + ) { return fromCache; } diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index 58edcc56d70..58708bcdcd8 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -128,6 +128,7 @@ export class K8sDashboardAPI implements DashboardAPI { uid: dash.metadata.name, k8s: dash.metadata, version: dash.metadata.generation, + created: dash.metadata.creationTimestamp, }, dashboard: { ...dash.spec, diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx index 7b206a79e44..ee38959345f 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx @@ -33,6 +33,7 @@ const dashMock: DashboardDTO = { }, meta: { canEdit: false, + created: 'Friday, 4 July 2025 07:56:41 GMT+05:30', }, }; From c2b7acae35ff5fb0d596ded294ec5e60be480f7a Mon Sep 17 00:00:00 2001 From: Andres Torres Date: Mon, 7 Jul 2025 08:23:51 -0400 Subject: [PATCH 47/67] chore(datasources): Adding debug logging to datasource provisioning when resource is not found in SqlStore (#107652) --- pkg/services/datasources/service/store.go | 1 + pkg/services/datasources/service/store_test.go | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/services/datasources/service/store.go b/pkg/services/datasources/service/store.go index d7a4056f165..f005516433a 100644 --- a/pkg/services/datasources/service/store.go +++ b/pkg/services/datasources/service/store.go @@ -83,6 +83,7 @@ func (ss *SqlStore) getDataSource(_ context.Context, query *datasources.GetDataS ss.logger.Error("Failed getting data source", "err", err, "uid", query.UID, "id", query.ID, "name", query.Name, "orgId", query.OrgID) // nolint:staticcheck return nil, err } else if !has { + ss.logger.Debug("Data source not found", "uid", query.UID, "id", query.ID, "name", query.Name, "orgId", query.OrgID) // nolint:staticcheck return nil, datasources.ErrDataSourceNotFound } diff --git a/pkg/services/datasources/service/store_test.go b/pkg/services/datasources/service/store_test.go index 9e8b64b44ec..7acb1dbafca 100644 --- a/pkg/services/datasources/service/store_test.go +++ b/pkg/services/datasources/service/store_test.go @@ -281,7 +281,10 @@ func TestIntegrationDataAccess(t *testing.T) { t.Run("Can not delete datasource with wrong orgID", func(t *testing.T) { db := db.InitTestDB(t) ds := initDatasource(db) - ss := SqlStore{db: db} + ss := SqlStore{ + db: db, + logger: log.NewNopLogger(), + } err := ss.DeleteDataSource(context.Background(), &datasources.DeleteDataSourceCommand{ID: ds.ID, OrgID: 123123}) @@ -322,7 +325,10 @@ func TestIntegrationDataAccess(t *testing.T) { t.Run("does not fire an event when the datasource is not deleted", func(t *testing.T) { db := db.InitTestDB(t) - ss := SqlStore{db: db} + ss := SqlStore{ + db: db, + logger: log.NewNopLogger(), + } var called bool db.Bus().AddEventListener(func(ctx context.Context, e *events.DataSourceDeleted) error { From 77f84e494df6abf58951949bd247d63be94be133 Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Mon, 7 Jul 2025 14:51:04 +0200 Subject: [PATCH 48/67] Plugin Extensions: Add error boundaries (#107515) * feat(grafana-data): expose PluginContext This is aimed to be used in the `PluginErrorBoundary` (which is a class component, and cannot use the hook.) * feat(PluginErrorBoundary): add an error boundary for plugins * feat(ExtensionsErrorBoundary): add an error boundary for extensions) * feat(Extensions/Utils): wrap components with error boundaries * feat(Plugins): wrap root plugin page with an error boundary * fix: Fallback component should always be visible for onClick() modals * review: use object arguments instead of positional ones for `renderWithPluginContext()` * review: update `wrapWithPluginContext()` to receive args as an object * refactor(AppChromeExtensionPoint): remove the error boundary We have an error boundary on the extensions-framework level now * refactor(ExtensionSidebar): remove the ErrorBoundary from the extensions This is handled on the extensions-framework level now. * test(ExtensionSidebar): add tests * chore: translation extraction * chore: prettier formatting * fix(PluginErrorBoundary): remove unnecessary type casting --- packages/grafana-data/src/index.ts | 6 +- .../AppChrome/AppChromeExtensionPoint.tsx | 8 +- .../ExtensionSidebar.test.tsx | 167 ++++++++++++++++ .../ExtensionSidebar/ExtensionSidebar.tsx | 6 +- .../ExtensionSidebarProvider.tsx | 2 +- .../plugins/components/AppRootPage.tsx | 43 ++-- .../components/PluginErrorBoundary.test.tsx | 184 +++++++++++++++++ .../components/PluginErrorBoundary.tsx | 49 +++++ .../extensions/ExtensionErrorAlert.tsx | 20 ++ .../extensions/ExtensionErrorBoundary.tsx | 42 ++++ .../plugins/extensions/getPluginExtensions.ts | 7 +- .../registry/AddedComponentsRegistry.ts | 7 +- .../plugins/extensions/usePluginComponent.tsx | 7 +- .../plugins/extensions/utils.test.tsx | 188 +++++++++++++++++- .../app/features/plugins/extensions/utils.tsx | 48 ++++- public/locales/en-US/grafana.json | 5 + 16 files changed, 737 insertions(+), 52 deletions(-) create mode 100644 public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.test.tsx create mode 100644 public/app/features/plugins/components/PluginErrorBoundary.test.tsx create mode 100644 public/app/features/plugins/components/PluginErrorBoundary.tsx create mode 100644 public/app/features/plugins/extensions/ExtensionErrorAlert.tsx create mode 100644 public/app/features/plugins/extensions/ExtensionErrorBoundary.tsx diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index dde87b84498..80351af8bc2 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -432,7 +432,11 @@ export { export { createFieldConfigRegistry } from './panel/registryFactories'; export { type QueryRunner, type QueryRunnerOptions } from './types/queryRunner'; export { type GroupingToMatrixTransformerOptions } from './transformations/transformers/groupingToMatrix'; -export { type PluginContextType, type DataSourcePluginContextType } from './context/plugins/PluginContext'; +export { + type PluginContextType, + type DataSourcePluginContextType, + Context as PluginContext, +} from './context/plugins/PluginContext'; export { type PluginContextProviderProps, PluginContextProvider } from './context/plugins/PluginContextProvider'; export { type DataSourcePluginContextProviderProps, diff --git a/public/app/core/components/AppChrome/AppChromeExtensionPoint.tsx b/public/app/core/components/AppChrome/AppChromeExtensionPoint.tsx index 9c1f0330ac7..16d651e72bd 100644 --- a/public/app/core/components/AppChrome/AppChromeExtensionPoint.tsx +++ b/public/app/core/components/AppChrome/AppChromeExtensionPoint.tsx @@ -2,7 +2,6 @@ import { useLocation } from 'react-router-dom'; import { PluginExtensionPoints } from '@grafana/data'; import { config, renderLimitedComponents, usePluginComponents } from '@grafana/runtime'; -import { ErrorBoundaryAlert } from '@grafana/ui'; const excludedRoutes: Record = { '/login': true, @@ -24,13 +23,10 @@ export function AppChromeExtensionPoint(): JSX.Element | null { return null; } - return ( - - - - ); + return ; } +// We have this "internal" component so we can prevent pre-loading the plugins associated with the extension-point if the feature is not enabled. function InternalAppChromeExtensionPoint(): JSX.Element | null { const { components, isLoading } = usePluginComponents({ extensionPointId: PluginExtensionPoints.AppChrome, diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.test.tsx new file mode 100644 index 00000000000..0582e5778fe --- /dev/null +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.test.tsx @@ -0,0 +1,167 @@ +import { render, screen } from '@testing-library/react'; + +import { ExtensionInfo, PluginExtensionTypes } from '@grafana/data'; +import { config, usePluginComponents } from '@grafana/runtime'; +import { AddedComponentRegistryItem } from 'app/features/plugins/extensions/registry/AddedComponentsRegistry'; +import { createComponentWithMeta } from 'app/features/plugins/extensions/usePluginComponents'; + +import { ExtensionSidebar } from './ExtensionSidebar'; +import { + ExtensionSidebarContextType, + getComponentIdFromComponentMeta, + useExtensionSidebarContext, +} from './ExtensionSidebarProvider'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + usePluginComponents: jest.fn(), +})); + +jest.mock('./ExtensionSidebarProvider', () => ({ + ...jest.requireActual('./ExtensionSidebarProvider'), + useExtensionSidebarContext: jest.fn(), +})); + +const mockUsePluginComponents = jest.mocked(usePluginComponents); +const mockUseExtensionSidebarContext = jest.mocked(useExtensionSidebarContext); + +const MockComponent = () =>

Mock Component
; +const pluginId = 'test-plugin'; +const extensionPointId = 'grafana/extension-sidebar/v0-alpha'; + +MockComponent.meta = { + pluginId, + title: 'Test Component', + id: 'test-component-id', + type: PluginExtensionTypes.component, + description: 'Test Component', +}; + +const addedComponentConfigMock: ExtensionInfo = { + targets: extensionPointId, + title: 'Test Component', +}; + +const extensionSidebarContextMock: ExtensionSidebarContextType = { + dockedComponentId: getComponentIdFromComponentMeta(pluginId, addedComponentConfigMock), + isEnabled: true, + props: {}, + isOpen: true, + setDockedComponentId: jest.fn(), + availableComponents: new Map(), + extensionSidebarWidth: 300, + setExtensionSidebarWidth: jest.fn(), +}; + +const addedComponentRegistryItemMock: AddedComponentRegistryItem = { + pluginId, + title: addedComponentConfigMock.title, + component: MockComponent, +}; + +describe('ExtensionSidebar', () => { + const originalEnv = config.buildInfo.env; + + beforeEach(() => { + jest.clearAllMocks(); + config.buildInfo.env = 'development'; + }); + + afterEach(() => { + config.buildInfo.env = originalEnv; + }); + + it('should render nothing when the extension sidebar is not enabled', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: false, + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: false, + }); + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should render nothing when the extension sidebar is enabled but no component is docked', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: true, + dockedComponentId: undefined, + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: false, + }); + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: true, + dockedComponentId: 'test-component-id-not-found', + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: false, + }); + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: true, + dockedComponentId: 'test-component-id-not-found', + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: false, + }); + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should render nothing when components are loading', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: true, + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: true, + }); + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should render the component when all conditions are met', () => { + mockUseExtensionSidebarContext.mockReturnValue({ + ...extensionSidebarContextMock, + isEnabled: true, + }); + + mockUsePluginComponents.mockReturnValue({ + components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)], + isLoading: false, + }); + + const { container } = render(); + expect(container.firstChild).toBeInTheDocument(); + expect(screen.getByText('Mock Component')).toBeInTheDocument(); + }); +}); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx index 5f1e1b826ba..89cb4b3db7c 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { usePluginComponents } from '@grafana/runtime'; -import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui'; +import { useTheme2 } from '@grafana/ui'; import { EXTENSION_SIDEBAR_EXTENSION_POINT_ID, @@ -45,9 +45,7 @@ export function ExtensionSidebar() { return (
- - - +
); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 63b4812955e..8693b71ed04 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -18,7 +18,7 @@ const PERMITTED_EXTENSION_SIDEBAR_PLUGINS = [ 'grafana-grafanadocsplugin-app', ]; -type ExtensionSidebarContextType = { +export type ExtensionSidebarContextType = { /** * Whether the extension sidebar is enabled. */ diff --git a/public/app/features/plugins/components/AppRootPage.tsx b/public/app/features/plugins/components/AppRootPage.tsx index 3bb8d1c3169..c6cdc47e810 100644 --- a/public/app/features/plugins/components/AppRootPage.tsx +++ b/public/app/features/plugins/components/AppRootPage.tsx @@ -16,7 +16,7 @@ import { } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config, locationSearchToObject } from '@grafana/runtime'; -import { Alert } from '@grafana/ui'; +import { Alert, ErrorWithStack } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; @@ -36,6 +36,7 @@ import { getPluginSettings } from '../pluginSettings'; import { importAppPlugin } from '../plugin_loader'; import { buildPluginSectionNav, pluginsLogger } from '../utils'; +import { PluginErrorBoundary } from './PluginErrorBoundary'; import { buildPluginPageContext, PluginPageContext } from './PluginPageContext'; interface Props { @@ -106,22 +107,32 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) { const pluginRoot = plugin.root && ( - ( + + )} > - - + + + + ); diff --git a/public/app/features/plugins/components/PluginErrorBoundary.test.tsx b/public/app/features/plugins/components/PluginErrorBoundary.test.tsx new file mode 100644 index 00000000000..377e508a33d --- /dev/null +++ b/public/app/features/plugins/components/PluginErrorBoundary.test.tsx @@ -0,0 +1,184 @@ +import { render, screen } from '@testing-library/react'; +import * as React from 'react'; + +import { PluginMeta, PluginType, PluginContext } from '@grafana/data'; +import { getMockPlugin } from '@grafana/data/test'; + +import { PluginErrorBoundary } from './PluginErrorBoundary'; + +const ThrowingComponent = ({ shouldThrow }: { shouldThrow: boolean }) => { + if (shouldThrow) { + throw new Error('Test error message'); + } + return
Working component
; +}; + +const TestFallback = ({ error, errorInfo }: { error: Error | null; errorInfo: React.ErrorInfo | null }) => ( +
+
Fallback rendered
+
Error: {error?.message}
+ {errorInfo &&
Error info available
} +
+); + +const renderWithPluginContext = ({ + children, + pluginMeta, + fallback, + onError, +}: { + children: React.ReactNode; + pluginMeta?: PluginMeta; + fallback?: React.ComponentType<{ error: Error | null; errorInfo: React.ErrorInfo | null }>; + onError?: (error: Error, info: React.ErrorInfo) => void; +}) => { + const mockPluginMeta = pluginMeta || getMockPlugin({ id: 'test-plugin', type: PluginType.panel }); + + return render( + + + {children} + + + ); +}; + +describe('PluginErrorBoundary', () => { + let consoleErrorSpy: jest.SpyInstance; + + beforeEach(() => { + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + it('should render children normally when no error occurs', () => { + renderWithPluginContext({ children: }); + + expect(screen.getByText('Working component')).toBeInTheDocument(); + }); + + it('should render null when an error occurs and no fallback is provided', () => { + const { container } = renderWithPluginContext({ children: }); + + expect(container.firstChild).toBeNull(); + }); + + it('should render custom fallback component when an error occurs', () => { + renderWithPluginContext({ children: , fallback: TestFallback }); + + expect(screen.getByText('Fallback rendered')).toBeInTheDocument(); + expect(screen.getByText('Error: Test error message')).toBeInTheDocument(); + expect(screen.getByText('Error info available')).toBeInTheDocument(); + }); + + it('should call onError callback when an error occurs', () => { + const onErrorMock = jest.fn(); + + renderWithPluginContext({ children: , onError: onErrorMock }); + + expect(onErrorMock).toHaveBeenCalledTimes(1); + expect(onErrorMock).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Test error message' }), + expect.objectContaining({ + componentStack: expect.any(String), + }) + ); + }); + + it('should log error to console with plugin ID when no onError callback is provided', () => { + const mockPluginMeta = getMockPlugin({ id: 'my-test-plugin', type: PluginType.datasource }); + + renderWithPluginContext({ children: , pluginMeta: mockPluginMeta }); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Plugin "my-test-plugin" failed to load:', + expect.objectContaining({ message: 'Test error message' }), + expect.objectContaining({ + componentStack: expect.any(String), + }) + ); + }); + + it('should handle error when plugin context is not available', () => { + render( + + + + ); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Plugin "undefined" failed to load:', + expect.objectContaining({ message: 'Test error message' }), + expect.objectContaining({ + componentStack: expect.any(String), + }) + ); + }); + + it('should update state correctly when error occurs', () => { + renderWithPluginContext({ children: , fallback: TestFallback }); + + // Verify that both error and errorInfo are available in the fallback + expect(screen.getByText('Error: Test error message')).toBeInTheDocument(); + expect(screen.getByText('Error info available')).toBeInTheDocument(); + }); + + it('should reset error state when children change to non-throwing component', () => { + const { rerender } = renderWithPluginContext({ + children: , + fallback: TestFallback, + }); + + // Initially should show fallback + expect(screen.getByText('Fallback rendered')).toBeInTheDocument(); + + // Re-render with non-throwing component + rerender( + + + + + + ); + + // Should still show fallback since error boundary doesn't reset automatically + expect(screen.getByText('Fallback rendered')).toBeInTheDocument(); + }); + + it('should handle multiple children correctly', () => { + renderWithPluginContext({ + children: ( + <> +
First child
+ +
Third child
+ + ), + }); + + expect(screen.getByText('First child')).toBeInTheDocument(); + expect(screen.getByText('Working component')).toBeInTheDocument(); + expect(screen.getByText('Third child')).toBeInTheDocument(); + }); + + it('should handle error in one of multiple children', () => { + renderWithPluginContext({ + children: ( + <> +
First child
+ +
Third child
+ + ), + fallback: TestFallback, + }); + + // Should show fallback and not render any of the children + expect(screen.getByText('Fallback rendered')).toBeInTheDocument(); + expect(screen.queryByText('First child')).not.toBeInTheDocument(); + expect(screen.queryByText('Third child')).not.toBeInTheDocument(); + }); +}); diff --git a/public/app/features/plugins/components/PluginErrorBoundary.tsx b/public/app/features/plugins/components/PluginErrorBoundary.tsx new file mode 100644 index 00000000000..6495acfeaf3 --- /dev/null +++ b/public/app/features/plugins/components/PluginErrorBoundary.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; + +import { PluginContext } from '@grafana/data'; + +interface PluginErrorBoundaryProps { + children: React.ReactNode; + fallback?: React.ComponentType<{ error: Error | null; errorInfo: React.ErrorInfo | null }>; + onError?: (error: Error, info: React.ErrorInfo) => void; +} + +interface PluginErrorBoundaryState { + hasError: boolean; + error: Error | null; + errorInfo: React.ErrorInfo | null; +} + +export class PluginErrorBoundary extends React.Component { + static contextType = PluginContext; + + declare context: React.ContextType; + + constructor(props: PluginErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null, errorInfo: null }; + } + + static getDerivedStateFromError(error: Error): PluginErrorBoundaryState { + return { hasError: true, error: error, errorInfo: null }; + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + if (this.props.onError) { + this.props.onError(error, info); + } else { + console.error(`Plugin "${this.context?.meta.id}" failed to load:`, error, info); + } + + this.setState({ error, errorInfo: info }); + } + + render() { + const Fallback = this.props.fallback; + if (this.state.hasError) { + return Fallback ? : null; + } + + return this.props.children; + } +} diff --git a/public/app/features/plugins/extensions/ExtensionErrorAlert.tsx b/public/app/features/plugins/extensions/ExtensionErrorAlert.tsx new file mode 100644 index 00000000000..41b1b021d34 --- /dev/null +++ b/public/app/features/plugins/extensions/ExtensionErrorAlert.tsx @@ -0,0 +1,20 @@ +import { t } from '@grafana/i18n'; +import { Alert } from '@grafana/ui'; + +export const ExtensionErrorAlert = ({ pluginId, extensionTitle }: { pluginId: string; extensionTitle: string }) => { + return ( + + {t('plugins.extensions.extension-error-alert-description', 'Check the console for more details on the error.')} + + ); +}; diff --git a/public/app/features/plugins/extensions/ExtensionErrorBoundary.tsx b/public/app/features/plugins/extensions/ExtensionErrorBoundary.tsx new file mode 100644 index 00000000000..5d85e16eee1 --- /dev/null +++ b/public/app/features/plugins/extensions/ExtensionErrorBoundary.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; + +import { PluginErrorBoundary } from '../components/PluginErrorBoundary'; + +import { ExtensionErrorAlert } from './ExtensionErrorAlert'; +import { ExtensionsLog, log as baseLog } from './logs/log'; +import { isGrafanaDevMode } from './utils'; + +export const ExtensionErrorBoundary = ({ + children, + pluginId, + extensionTitle, + log = baseLog, + fallbackAlwaysVisible = false, +}: { + children: React.ReactNode; + pluginId: string; + extensionTitle: string; + log?: ExtensionsLog; + fallbackAlwaysVisible?: boolean; +}) => { + return ( + { + log.error(`Extension "${pluginId}/${extensionTitle}" failed to load.`, { + message: error.message, + componentStack: errorInfo.componentStack ?? '', + digest: errorInfo.digest ?? '', + }); + }} + fallback={() => { + if (isGrafanaDevMode() || fallbackAlwaysVisible) { + return ; + } + + return null; + }} + > + {children} + + ); +}; diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index aed7866e9b2..5968bb6fc42 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -172,7 +172,12 @@ export const getPluginExtensions: GetExtensions = ({ pluginId: addedComponent.pluginId, title: addedComponent.title, description: addedComponent.description ?? '', - component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component, componentLog), + component: wrapWithPluginContext({ + pluginId: addedComponent.pluginId, + extensionTitle: addedComponent.title, + Component: addedComponent.component, + log: componentLog, + }), }; extensions.push(extension); diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts index d4b8b0b04f5..de732ce8c6d 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts @@ -62,7 +62,12 @@ export class AddedComponentsRegistry extends Registry< const result = { pluginId, - component: wrapWithPluginContext(pluginId, config.component, pointIdLog), + component: wrapWithPluginContext({ + pluginId, + extensionTitle: config.title, + Component: config.component, + log: pointIdLog, + }), description: config.description, title: config.title, }; diff --git a/public/app/features/plugins/extensions/usePluginComponent.tsx b/public/app/features/plugins/extensions/usePluginComponent.tsx index 5e079beea93..7a2d3669b24 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.tsx @@ -54,7 +54,12 @@ export function usePluginComponent(id: string): UsePl return { isLoading: false, - component: wrapWithPluginContext(registryItem.pluginId, registryItem.component, componentLog), + component: wrapWithPluginContext({ + pluginId: registryItem.pluginId, + extensionTitle: registryItem.title, + Component: registryItem.component, + log: componentLog, + }), }; }, [id, pluginContext, registryState, isLoadingAppPlugins]); } diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx index 2755d98d064..e3b74d8cd1a 100644 --- a/public/app/features/plugins/extensions/utils.test.tsx +++ b/public/app/features/plugins/extensions/utils.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { type Unsubscribable } from 'rxjs'; import { dateTime, usePluginContext, PluginLoadingStrategy } from '@grafana/data'; @@ -30,6 +30,8 @@ jest.mock('app/features/plugins/pluginSettings', () => ({ })); describe('Plugin Extensions / Utils', () => { + const originalEnv = config.buildInfo.env; + beforeEach(() => { jest.spyOn(log, 'error').mockImplementation(() => {}); jest.spyOn(log, 'warning').mockImplementation(() => {}); @@ -38,6 +40,8 @@ describe('Plugin Extensions / Utils', () => { afterEach(() => { jest.resetAllMocks(); + + config.buildInfo.env = originalEnv; }); describe('deepFreeze()', () => { @@ -620,7 +624,11 @@ describe('Plugin Extensions / Utils', () => { it('should open modal with provided title and body', async () => { const pluginId = 'grafana-worldmap-panel'; - const openModal = createOpenModalFunction(pluginId); + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: 'Title in modal', + }); openModal({ title: 'Title in modal', @@ -634,7 +642,11 @@ describe('Plugin Extensions / Utils', () => { it('should open modal with default width if not specified', async () => { const pluginId = 'grafana-worldmap-panel'; - const openModal = createOpenModalFunction(pluginId); + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: 'Title in modal', + }); openModal({ title: 'Title in modal', @@ -650,7 +662,11 @@ describe('Plugin Extensions / Utils', () => { it('should open modal with specified width', async () => { const pluginId = 'grafana-worldmap-panel'; - const openModal = createOpenModalFunction(pluginId); + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: 'Title in modal', + }); openModal({ title: 'Title in modal', @@ -666,7 +682,11 @@ describe('Plugin Extensions / Utils', () => { it('should open modal with specified height', async () => { const pluginId = 'grafana-worldmap-panel'; - const openModal = createOpenModalFunction(pluginId); + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: 'Title in modal', + }); openModal({ title: 'Title in modal', @@ -682,7 +702,11 @@ describe('Plugin Extensions / Utils', () => { it('should open modal with the plugin context being available', async () => { const pluginId = 'grafana-worldmap-panel'; - const openModal = createOpenModalFunction(pluginId); + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: 'Title in modal', + }); const ModalContent = () => { const context = usePluginContext(); @@ -698,6 +722,72 @@ describe('Plugin Extensions / Utils', () => { const modal = await screen.findByRole('dialog'); expect(modal).toHaveTextContent('Version: 1.0.0'); }); + + it('should show an error alert in the modal IN DEV MODE if the extension throws an error', async () => { + config.buildInfo.env = 'development'; + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const pluginId = 'grafana-worldmap-panel'; + const extensionTitle = 'Title in modal'; + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: extensionTitle, + }); + + const ModalContent = () => { + throw new Error('Test error'); + }; + + openModal({ + title: extensionTitle, + body: ModalContent, + }); + + await screen.findByRole('dialog'); + + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith(`Extension "${pluginId}/${extensionTitle}" failed to load.`, { + message: 'Test error', + componentStack: expect.any(String), + digest: expect.any(String), + }); + + expect(screen.getByText(`Extension failed to load: "${pluginId}/${extensionTitle}"`)).toBeVisible(); + }); + + it('should also show an error alert in the modal IN PRODUCTION MODE if the extension throws an error', async () => { + config.buildInfo.env = 'production'; + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const pluginId = 'grafana-worldmap-panel'; + const extensionTitle = 'Title in modal'; + const openModal = createOpenModalFunction({ + pluginId, + extensionPointId: 'myorg-extensions-app/link/v1', + title: extensionTitle, + }); + + const ModalContent = () => { + throw new Error('Test error'); + }; + + openModal({ + title: extensionTitle, + body: ModalContent, + }); + + await screen.findByRole('dialog'); + + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith(`Extension "${pluginId}/${extensionTitle}" failed to load.`, { + message: 'Test error', + componentStack: expect.any(String), + digest: expect.any(String), + }); + + expect(screen.getByText(`Extension failed to load: "${pluginId}/${extensionTitle}"`)).toBeVisible(); + }); }); describe('wrapWithPluginContext()', () => { @@ -728,7 +818,12 @@ describe('Plugin Extensions / Utils', () => { it('should make the plugin context available for the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); + const Component = wrapWithPluginContext({ + pluginId, + extensionTitle: 'ExampleComponent', + Component: ExampleComponent, + log, + }); render(); @@ -738,7 +833,12 @@ describe('Plugin Extensions / Utils', () => { it('should pass the properties into the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); + const Component = wrapWithPluginContext({ + pluginId, + extensionTitle: 'ExampleComponent', + Component: ExampleComponent, + log, + }); render(); @@ -749,7 +849,12 @@ describe('Plugin Extensions / Utils', () => { it('should not be possible to mutate the props in development mode, but it logs an error', async () => { config.buildInfo.env = 'development'; const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); + const Component = wrapWithPluginContext({ + pluginId, + extensionTitle: 'ExampleComponent', + Component: ExampleComponent, + log, + }); const props = { a: { b: { c: 'Grafana' } } }; render(); @@ -772,7 +877,12 @@ describe('Plugin Extensions / Utils', () => { it('should not be possible to mutate the props in production mode either, but it logs a warning', async () => { config.buildInfo.env = 'production'; const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); + const Component = wrapWithPluginContext({ + pluginId, + extensionTitle: 'ExampleComponent', + Component: ExampleComponent, + log, + }); const props = { a: { b: { c: 'Grafana' } } }; render(); @@ -791,6 +901,64 @@ describe('Plugin Extensions / Utils', () => { // Not able to mutate the props in production mode either expect(props.a.b.c).toBe('Grafana'); }); + + it('should render an error alert IN DEV MODE if the extension throws an error', async () => { + config.buildInfo.env = 'development'; + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const pluginId = 'grafana-worldmap-panel'; + const ComponentWithError = () => { + throw new Error('Test error'); + }; + const extensionTitle = 'ComponentWithError'; + const WrappedComponent = wrapWithPluginContext({ + pluginId, + extensionTitle, + Component: ComponentWithError, + log, + }); + + render(); + + expect(await screen.findByText(`Extension failed to load: "${pluginId}/${extensionTitle}"`)).toBeVisible(); + + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith(`Extension "${pluginId}/${extensionTitle}" failed to load.`, { + message: 'Test error', + componentStack: expect.any(String), + digest: expect.any(String), + }); + }); + + it('should not render anything IN PRODUCTION MODE if the extension throws an error, but still logs an error', async () => { + config.buildInfo.env = 'production'; + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const pluginId = 'grafana-worldmap-panel'; + const ComponentWithError = () => { + throw new Error('Test error'); + }; + const extensionTitle = 'ComponentWithError'; + const WrappedComponent = wrapWithPluginContext({ + pluginId, + extensionTitle, + Component: ComponentWithError, + log, + }); + + render(); + + await waitFor(() => + expect(screen.queryByText(`Extension failed to load: "${pluginId}/${extensionTitle}"`)).not.toBeInTheDocument() + ); + + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith(`Extension "${pluginId}/${extensionTitle}" failed to load.`, { + message: 'Test error', + componentStack: expect.any(String), + digest: expect.any(String), + }); + }); }); describe('getAppPluginConfigs()', () => { diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx index ec994d81383..23d21c11058 100644 --- a/public/app/features/plugins/extensions/utils.tsx +++ b/public/app/features/plugins/extensions/utils.tsx @@ -22,6 +22,7 @@ import appEvents from 'app/core/app_events'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; import { OpenExtensionSidebarEvent, ShowModalReactEvent } from 'app/types/events'; +import { ExtensionErrorBoundary } from './ExtensionErrorBoundary'; import { ExtensionsLog, log as baseLog } from './logs/log'; import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry'; import { assertIsNotPromise, assertLinkPathIsValid, assertStringProps, isPromise } from './validators'; @@ -38,17 +39,18 @@ export function handleErrorsInFn(fn: Function, errorMessagePrefix = '') { }; } -export function createOpenModalFunction(pluginId: string): PluginExtensionEventHelpers['openModal'] { +export function createOpenModalFunction(config: AddedLinkRegistryItem): PluginExtensionEventHelpers['openModal'] { return async (options) => { const { title, body, width, height } = options; appEvents.publish( new ShowModalReactEvent({ - component: wrapWithPluginContext( - pluginId, - getModalWrapper({ title, body, width, height }), - baseLog - ), + component: wrapWithPluginContext({ + pluginId: config.pluginId, + extensionTitle: config.title, + Component: getModalWrapper({ title, body, width, height, config }), + log: baseLog, + }), }) ); }; @@ -58,7 +60,17 @@ type ModalWrapperProps = { onDismiss: () => void; }; -export const wrapWithPluginContext = (pluginId: string, Component: React.ComponentType, log: ExtensionsLog) => { +export const wrapWithPluginContext = ({ + pluginId, + extensionTitle, + Component, + log, +}: { + pluginId: string; + extensionTitle: string; + Component: React.ComponentType; + log: ExtensionsLog; +}) => { const WrappedExtensionComponent = (props: T & React.JSX.IntrinsicAttributes) => { const { error, @@ -85,7 +97,9 @@ export const wrapWithPluginContext = (pluginId: string, Component: React.Com return ( - + + + ); }; @@ -102,13 +116,25 @@ const getModalWrapper = ({ body: Body, width, height, -}: PluginExtensionOpenModalOptions) => { + config, +}: PluginExtensionOpenModalOptions & { config: AddedLinkRegistryItem }) => { const className = css({ width, height }); const ModalWrapper = ({ onDismiss }: ModalWrapperProps) => { return ( - + {/* + We also add an error boundary here (apart from the one in the `wrapWithPluginContext`) + so the error appears inside the modal (and not at the bottom of the page.) + */} + + + ); }; @@ -491,7 +517,7 @@ export function getLinkExtensionOnClick( const helpers: PluginExtensionEventHelpers = { context, - openModal: createOpenModalFunction(pluginId), + openModal: createOpenModalFunction(config), openSidebar: (componentTitle, context) => { appEvents.publish( new OpenExtensionSidebarEvent({ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 4ee07cfc845..226dc40018d 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -9825,6 +9825,7 @@ "permission": "You do not have permission to see this page.", "title-access-denied": "Access denied" }, + "error-loading-plugin": "Plugin failed to load", "no-root-app-page-component-found": "No root app page component found" }, "browse": { @@ -9923,6 +9924,10 @@ "empty-state": { "message": "No plugins found" }, + "extensions": { + "extension-error-alert-description": "Check the console for more details on the error.", + "extension-error-alert-title": "Extension failed to load: \"{{pluginId}}/{{extensionTitle}}\"" + }, "extensions-log-data-source": { "message": { "ok": "OK" From 28ef5672c47d9902483b0d1344f12cf99a61308a Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Mon, 7 Jul 2025 14:53:09 +0200 Subject: [PATCH 49/67] Update Advisor docs (#107683) * Update advisor docs * Update _index.md --------- Co-authored-by: Anna Urbiztondo --- docs/sources/administration/grafana-advisor/_index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sources/administration/grafana-advisor/_index.md b/docs/sources/administration/grafana-advisor/_index.md index cd9b812d59e..28caf9b1953 100644 --- a/docs/sources/administration/grafana-advisor/_index.md +++ b/docs/sources/administration/grafana-advisor/_index.md @@ -90,6 +90,12 @@ This collapsible section provides more details about which checks have been perf Click the cogwheel in this section to access Grafana Advisor settings, where you can enable or disable checks according to your preferences. {{< /admonition >}} +### Enable LLM suggestions + +If the [Grafana LLM app](https://grafana.com/grafana/plugins/grafana-llm-app/) is installed, the Advisor can use it to generate suggestions for issues. Enable the LLM app and click the magic (✨) button to generate a suggestion for an issue. + +![](/media/docs/grafana-advisor/llm-suggestions.png) + ## Address issues To resolve issues flagged by Grafana Advisor and maintain system reliability, follow the best practices below. Regularly check the Advisor to keep your Grafana instance secure and up to date. From ddbba7b460fd546ed9936a1f3316d4b61ee68131 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 7 Jul 2025 14:56:31 +0200 Subject: [PATCH 50/67] Alerting: Add support for "alerting.notifications.routes.conflictingMatchers" (#107687) add support for "alerting.notifications.routes.conflictingMatchers" --- .../PolicyUpdateErrorAlert.tsx | 15 +----- .../alerting/unified/utils/k8s/errors.test.ts | 45 ++++++++++++++++ .../alerting/unified/utils/k8s/errors.ts | 54 ++++++++++++++++--- .../alerting/unified/utils/misc.test.ts | 6 +-- .../features/alerting/unified/utils/misc.ts | 24 ++++++--- public/locales/en-US/grafana.json | 6 ++- 6 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 public/app/features/alerting/unified/utils/k8s/errors.test.ts diff --git a/public/app/features/alerting/unified/components/notification-policies/PolicyUpdateErrorAlert.tsx b/public/app/features/alerting/unified/components/notification-policies/PolicyUpdateErrorAlert.tsx index 842b4840a47..48c573c6f5e 100644 --- a/public/app/features/alerting/unified/components/notification-policies/PolicyUpdateErrorAlert.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/PolicyUpdateErrorAlert.tsx @@ -4,23 +4,12 @@ import { Alert } from '@grafana/ui'; import { stringifyErrorLike } from '../../utils/misc'; export const NotificationPoliciesErrorAlert = ({ error }: { error: unknown }) => { - const title = t('alerting.policies.update-errors.title', 'Error saving notification policy'); + const title = t('alerting.policies.update-errors.title', 'Failed to add or update notification policy'); const errMessage = stringifyErrorLike(error); return ( -
- - Something went wrong when updating your notification policies. - -
-
- {errMessage || ( - - Error message: "{{ error }}" - - )} -
+
{errMessage}
Please refresh the page and try again.
diff --git a/public/app/features/alerting/unified/utils/k8s/errors.test.ts b/public/app/features/alerting/unified/utils/k8s/errors.test.ts new file mode 100644 index 00000000000..ac7c03aa907 --- /dev/null +++ b/public/app/features/alerting/unified/utils/k8s/errors.test.ts @@ -0,0 +1,45 @@ +import { + ApiMachineryErrorResponse, + ERROR_ROUTES_MATCHER_CONFLICT, + getErrorMessageFromApiMachineryErrorResponse, +} from './errors'; + +describe('getErrorMessageFromCode', () => { + it(`should handle ${ERROR_ROUTES_MATCHER_CONFLICT}`, () => { + const error: ApiMachineryErrorResponse = { + status: 400, + config: { url: '' }, + data: { + kind: 'Status', + apiVersion: 'v1', + metadata: {}, + status: 'Failure', + message: 'this is ignored', + reason: 'BadRequest', + details: { + uid: 'alerting.notifications.routes.conflictingMatchers', + causes: [ + { + message: '"[foo=\\"bar\\" baz=\\"qux\\"]"', + field: 'Matchers', + }, + { + message: '"[foo2=\\"bar2\\" baz2=\\"qux2\\"]"', + field: 'Matchers', + }, + ], + }, + code: 400, + }, + }; + + expect(getErrorMessageFromApiMachineryErrorResponse(error)).toBe( + 'Cannot add or update route: matchers conflict with an external routing tree if we merged matchers \"[foo=\\\"bar\\\" baz=\\\"qux\\\"]\", \"[foo2=\\\"bar2\\\" baz2=\\\"qux2\\\"]\". This would make the route unreachable.' + ); + + delete error.data.details?.causes; + expect(getErrorMessageFromApiMachineryErrorResponse(error)).toBe( + 'Cannot add or update route: matchers conflict with an external routing tree if we merged matchers . This would make the route unreachable.' + ); + }); +}); diff --git a/public/app/features/alerting/unified/utils/k8s/errors.ts b/public/app/features/alerting/unified/utils/k8s/errors.ts index 3d315471321..0bb486710a1 100644 --- a/public/app/features/alerting/unified/utils/k8s/errors.ts +++ b/public/app/features/alerting/unified/utils/k8s/errors.ts @@ -3,19 +3,56 @@ import { get } from 'lodash'; import { t } from '@grafana/i18n'; import { FetchError, isFetchError } from '@grafana/runtime'; -export type SupportedErrors = 'alerting.notifications.conflict' | string; +import { getErrorCode } from '../misc'; -export const ERROR_NEWER_CONFIGURATION = 'alerting.notifications.conflict'; +export const ERROR_NEWER_CONFIGURATION = 'alerting.notifications.conflict' as const; +export const ERROR_ROUTES_MATCHER_CONFLICT = 'alerting.notifications.routes.conflictingMatchers' as const; -/** This function gives us the opportunity to translate or transform error codes that are returned from the Kubernetes APIs */ +export type ApiMachineryErrorResponse = FetchError; + +// these are known error IDs, used by both Kubernetes API and the front-end (using error `cause`). +export type KnownErrorCodes = typeof ERROR_NEWER_CONFIGURATION; +// Kubernetes API Machinery errors are a superset of supported errors codes. +export type KnownMachineryErrorCodes = KnownErrorCodes | typeof ERROR_ROUTES_MATCHER_CONFLICT; + +/** + * This function gives us the opportunity to translate or transform error codes that are returned from the Kubernetes APIs + */ +export function getErrorMessageFromApiMachineryErrorResponse(error: ApiMachineryErrorResponse): string | undefined { + const code = getErrorCode(error); + if (!code) { + return error.data.message; + } + + const errorMessageMap: Record = { + [ERROR_NEWER_CONFIGURATION]: getErrorMessageFromCode(code), + [ERROR_ROUTES_MATCHER_CONFLICT]: t( + 'alerting.policies.update-errors.routes.conflictingMatchers', + 'Cannot add or update route: matchers conflict with an external routing tree if we merged matchers {{-matchers}}. This would make the route unreachable.', + { + matchers: + error.data.details?.causes?.map((cause) => cause.message).join(', ') ?? + t('alerting.policies.update-errors.routes.unknownMatchers', ''), + } + ), + }; + + // @ts-expect-error this allows the typechecker to warn us about forgetting to handle some KnownMachineryErrors and still return "undefined" for unknown codes; + return errorMessageMap[code]; +} + +/** + * This function gives us the opportunity to translate or transform error codes + */ export function getErrorMessageFromCode(code: string): string | undefined { - const errorMessageMap: Record = { + const errorMessageMap: Record = { [ERROR_NEWER_CONFIGURATION]: t( 'alerting.policies.update-errors.conflict', 'The notification policy tree has been updated by another user.' ), }; + // @ts-expect-error this allows the typechecker to warn us about forgetting to handle some KnownErrors and still return "undefined" for unknown codes; return errorMessageMap[code]; } @@ -29,7 +66,12 @@ export type ApiMachineryError = { group?: string; kind?: string; retryAfterSeconds?: number; - causes?: []; // @TODO type this, see apimachinery@v0.31.1/pkg/apis/meta/v1/types.go + // https://github.com/kubernetes/apimachinery/blob/v0.33.2/pkg/apis/meta/v1/types.go#L1020-L1040 + causes?: Array<{ + message?: string; + field?: string; + reason?: string; + }>; }; status: 'Failure'; metadata?: Record; @@ -37,6 +79,6 @@ export type ApiMachineryError = { reason: string; }; -export function isApiMachineryError(error: unknown): error is FetchError { +export function isApiMachineryError(error: unknown): error is ApiMachineryErrorResponse { return isFetchError(error) && get(error.data, 'kind') === 'Status' && get(error.data, 'status') === 'Failure'; } diff --git a/public/app/features/alerting/unified/utils/misc.test.ts b/public/app/features/alerting/unified/utils/misc.test.ts index 8dae9443827..8916603f2ee 100644 --- a/public/app/features/alerting/unified/utils/misc.test.ts +++ b/public/app/features/alerting/unified/utils/misc.test.ts @@ -125,12 +125,12 @@ describe('create links', () => { describe('stringifyErrorLike', () => { it('should stringify error with cause', () => { - const error = new Error('Something went strong', { cause: new Error('database did not respond') }); - expect(stringifyErrorLike(error)).toBe('Something went strong, cause: database did not respond'); + const error = new Error('Something went wrong', { cause: new Error('database did not respond') }); + expect(stringifyErrorLike(error)).toBe('Something went wrong, cause: database did not respond'); }); it('should stringify error with cause being a code', () => { - const error = new Error('Something went strong', { cause: ERROR_NEWER_CONFIGURATION }); + const error = new Error('Something went wrong', { cause: ERROR_NEWER_CONFIGURATION }); expect(stringifyErrorLike(error)).toBe(getErrorMessageFromCode(ERROR_NEWER_CONFIGURATION)); }); diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 561e5be930d..4501be47b69 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -1,4 +1,4 @@ -import { sortBy } from 'lodash'; +import { isString, sortBy } from 'lodash'; import { Labels, UrlQueryMap } from '@grafana/data'; import { GrafanaEdition } from '@grafana/data/internal'; @@ -34,7 +34,12 @@ import { import { ALERTMANAGER_NAME_QUERY_KEY } from './constants'; import { getRulesSourceName } from './datasource'; -import { SupportedErrors, getErrorMessageFromCode, isApiMachineryError } from './k8s/errors'; +import { + KnownErrorCodes, + getErrorMessageFromApiMachineryErrorResponse, + getErrorMessageFromCode, + isApiMachineryError, +} from './k8s/errors'; import { getMatcherQueryParams } from './matchers'; import { rulesNav } from './navigation'; import * as ruleId from './rule-id'; @@ -282,15 +287,20 @@ export function isErrorLike(error: unknown): error is Error { return Boolean(error && typeof error === 'object' && 'message' in error); } -export function getErrorCode(error: Error): unknown { +export function getErrorCode(error: unknown): string | undefined { if (isApiMachineryError(error) && error.data.details) { return error.data.details.uid; } - return error.cause; + + if (isErrorLike(error) && isString(error.cause)) { + return error.cause; + } + + return; } /* this function will check if the error passed as the first argument contains an error code */ -export function isErrorMatchingCode(error: Error | undefined, code: SupportedErrors): boolean { +export function isErrorMatchingCode(error: Error | undefined, code: KnownErrorCodes): boolean { if (!error) { return false; } @@ -301,8 +311,8 @@ export function isErrorMatchingCode(error: Error | undefined, code: SupportedErr export function stringifyErrorLike(error: unknown): string { const fetchError = isFetchError(error); if (fetchError) { - if (isApiMachineryError(error) && error.data.details) { - const message = getErrorMessageFromCode(error.data.details.uid); + if (isApiMachineryError(error)) { + const message = getErrorMessageFromApiMachineryErrorResponse(error); if (message) { return message; } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 226dc40018d..fc392dece19 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2095,9 +2095,11 @@ "update-errors": { "conflict": "The notification policy tree has been updated by another user.", "error-code": "Error message: \"{{error}}\"", - "fallback": "Something went wrong when updating your notification policies.", + "routes": { + "conflictingMatchers": "Cannot add or update route: matchers conflict with an external routing tree if we merged matchers {{-matchers}}. This would make the route unreachable." + }, "suffix": "Please refresh the page and try again.", - "title": "Error saving notification policy" + "title": "Failed to add or update notification policy" } }, "policy": { From 7787ff6b2e42d207f66cf6fc77398b6d84b2caff Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Mon, 7 Jul 2025 15:00:40 +0200 Subject: [PATCH 51/67] unistore: add more kvstore tests (#107692) add more kvstore tests --- pkg/storage/unified/testing/kv.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index da91a8647d2..5c205697e69 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -179,6 +179,24 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { err = obj.Value.Close() require.NoError(t, err) }) + + t.Run("save key with no data", func(t *testing.T) { + // Save a key with empty data + err := kv.Save(ctx, section, "empty-key", strings.NewReader("")) + require.NoError(t, err) + + // Verify it was saved with empty data + obj, err := kv.Get(ctx, section, "empty-key") + require.NoError(t, err) + assert.Equal(t, "empty-key", obj.Key) + + value, err := io.ReadAll(obj.Value) + require.NoError(t, err) + assert.Equal(t, "", string(value)) + assert.Len(t, value, 0) + err = obj.Value.Close() + require.NoError(t, err) + }) } func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { @@ -251,6 +269,19 @@ func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { assert.Contains(t, errors[0].Error(), "section is required") assert.Empty(t, keys) }) + + t.Run("list keys returns 0 keys", func(t *testing.T) { + // Use a different section with no keys + emptySection := nsPrefix + "-empty-keys" + + var keys []string + for k, err := range kv.Keys(ctx, emptySection, resource.ListOptions{}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Empty(t, keys) + assert.Len(t, keys, 0) + }) } func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { From cfa05a63c544000d57e5c5f7064bb5321000555a Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Mon, 7 Jul 2025 15:05:13 +0200 Subject: [PATCH 52/67] Plugin Loader: Don't load an app plugin multiple times (#107579) * fix(plugin_loader): don't import an app plugin twice * review: extract IIFE to a separate function (async part of plugin loading) * fix: remove code for testing --- public/app/features/plugins/plugin_loader.ts | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 6a415316caf..a5e642ffe06 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -187,16 +187,21 @@ export function importDataSourcePlugin(meta: DataSourcePluginMeta): Promise = {}; +// Cache for import promises to prevent duplicate imports +const importPromises: Record> = {}; export async function importAppPlugin(meta: PluginMeta): Promise { const pluginId = meta.id; - if (importedAppPlugins[pluginId]) { - return importedAppPlugins[pluginId]; + // We are caching the import promises to prevent duplicate imports + if (importPromises[pluginId] === undefined) { + importPromises[pluginId] = doImportAppPlugin(meta); } + return importPromises[pluginId]; +} + +async function doImportAppPlugin(meta: PluginMeta): Promise { throwIfAngular(meta); const pluginExports = await importPluginModule({ @@ -214,24 +219,22 @@ export async function importAppPlugin(meta: PluginMeta): Promise { plugin.setComponentsFromLegacyExports(pluginExports); exposedComponentsRegistry.register({ - pluginId, + pluginId: meta.id, configs: plugin.exposedComponentConfigs || [], }); addedComponentsRegistry.register({ - pluginId, + pluginId: meta.id, configs: plugin.addedComponentConfigs || [], }); addedLinksRegistry.register({ - pluginId, + pluginId: meta.id, configs: plugin.addedLinkConfigs || [], }); addedFunctionsRegistry.register({ - pluginId, + pluginId: meta.id, configs: plugin.addedFunctionConfigs || [], }); - importedAppPlugins[pluginId] = plugin; - return plugin; } From 075770070e7a8dac152f1b3ef27df6f82a390d8e Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Mon, 7 Jul 2025 15:31:25 +0200 Subject: [PATCH 53/67] Future-proofing query and data source model in Dashboard Schema v2 (#104194) --------- Signed-off-by: Igor Suleymanov Co-authored-by: alexandra vargas Co-authored-by: Haris Rozajac Co-authored-by: Dominik Prokop Co-authored-by: Igor Suleymanov Co-authored-by: spinillos --- apps/alerting/notifications/Makefile | 5 +- apps/alerting/notifications/go.mod | 16 +- apps/alerting/notifications/go.sum | 32 +- .../pkg/apis/alerting_manifest.go | 2 + apps/dashboard/Makefile | 2 +- apps/dashboard/go.mod | 8 +- apps/dashboard/go.sum | 20 +- .../kinds/v2alpha1/dashboard_spec.cue | 18 +- .../dashboard/v2alpha1/dashboard_spec.cue | 18 +- .../dashboard/v2alpha1/dashboard_spec_gen.go | 96 ++- .../v2alpha1/zz_generated.openapi.go | 79 ++- apps/folder/go.mod | 8 +- apps/folder/go.sum | 20 +- apps/folder/pkg/apis/folder_manifest.go | 2 + apps/iam/go.mod | 8 +- apps/iam/go.sum | 20 +- apps/iam/pkg/apis/iam_manifest.go | 2 - apps/playlist/go.mod | 12 +- apps/playlist/go.sum | 24 +- conf/provisioning/sample/dashboard-v2.json | 604 +++++++++++++++++- e2e/dashboards/TestV2Dashboard.json | 67 +- e2e/utils/flows/scenes/importDashboard.ts | 4 + go.mod | 18 +- go.sum | 36 +- go.work.sum | 207 +++++- .../src/schema/dashboard/v2_examples.ts | 68 +- .../dashboard/v2alpha1/types.spec.gen.ts | 42 +- pkg/aggregator/go.mod | 10 +- pkg/aggregator/go.sum | 20 +- pkg/apimachinery/go.mod | 4 +- pkg/apimachinery/go.sum | 8 +- pkg/apis/secret/go.mod | 10 +- pkg/apis/secret/go.sum | 20 +- pkg/apiserver/go.mod | 12 +- pkg/apiserver/go.sum | 24 +- pkg/codegen/go.mod | 2 +- pkg/codegen/go.sum | 4 +- pkg/plugins/codegen/go.mod | 2 +- pkg/plugins/codegen/go.sum | 4 +- pkg/promlib/go.mod | 4 +- pkg/promlib/go.sum | 8 +- .../dashboard.grafana.app-v2alpha1.json | 45 +- .../kinds/dataquery/types_dataquery_gen.go | 56 ++ .../kinds/dataquery/types_dataquery_gen.go | 12 +- .../kinds/dataquery/types_dataquery_gen.go | 38 ++ .../DashboardScenePageStateManager.test.ts | 52 +- .../scene/export/exporters.test.ts | 20 +- .../dashboard-scene/scene/export/exporters.ts | 5 +- .../DashboardSceneSerializer.test.ts | 29 +- .../serialization/DashboardSceneSerializer.ts | 6 +- ...sformSceneToSaveModelSchemaV2.test.ts.snap | 47 +- .../layoutSerializers/utils.test.ts | 62 +- .../serialization/layoutSerializers/utils.ts | 45 +- .../sceneVariablesSetToVariables.test.ts | 137 ++-- .../sceneVariablesSetToVariables.ts | 22 +- .../transformSaveModelSchemaV2ToScene.test.ts | 88 ++- .../transformSaveModelSchemaV2ToScene.ts | 47 +- .../transformSceneToSaveModelSchemaV2.test.ts | 61 +- .../transformSceneToSaveModelSchemaV2.ts | 67 +- .../v2schema/ImportDashboardOverviewV2.tsx | 39 +- .../dashboard-scene/v2schema/test-helpers.ts | 3 +- .../api/ResponseTransformers.test.ts | 59 +- .../dashboard/api/ResponseTransformers.ts | 38 +- .../manage-dashboards/state/actions.test.ts | 25 +- .../manage-dashboards/state/actions.ts | 57 +- 65 files changed, 1916 insertions(+), 714 deletions(-) diff --git a/apps/alerting/notifications/Makefile b/apps/alerting/notifications/Makefile index d745632abf9..2c50dee3970 100644 --- a/apps/alerting/notifications/Makefile +++ b/apps/alerting/notifications/Makefile @@ -19,9 +19,6 @@ update-app-sdk: ## Update the Grafana App SDK dependency in go.mod go mod tidy .PHONY: generate -generate: do-generate ## Run Grafana App SDK code generation - -.PHONY: do-generate -do-generate: install-app-sdk update-app-sdk +generate: install-app-sdk update-app-sdk ## --defencoding=none and noschemasinmanifest are needed to avoid infinite loop while generating recursive models (see routingtree.cue) @$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=none --postprocess --noschemasinmanifest diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 21243f3510a..b8cc5a3fdee 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -3,10 +3,10 @@ module github.com/grafana/grafana/apps/alerting/notifications go 1.24.4 require ( - github.com/grafana/grafana-app-sdk v0.39.0 - github.com/grafana/grafana-app-sdk/logging v0.38.2 - k8s.io/apimachinery v0.33.1 - k8s.io/apiserver v0.33.1 + github.com/grafana/grafana-app-sdk v0.39.2 + github.com/grafana/grafana-app-sdk/logging v0.39.1 + k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.33.2 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -97,10 +97,10 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.1 // indirect - k8s.io/apiextensions-apiserver v0.33.1 // indirect - k8s.io/client-go v0.33.1 // indirect - k8s.io/component-base v0.33.1 // indirect + k8s.io/api v0.33.2 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect + k8s.io/client-go v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 1ce129e856f..b1cfa4c0337 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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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= @@ -352,18 +352,18 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= 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= diff --git a/apps/alerting/notifications/pkg/apis/alerting_manifest.go b/apps/alerting/notifications/pkg/apis/alerting_manifest.go index 9b1e424d463..4ea6c3da1c7 100644 --- a/apps/alerting/notifications/pkg/apis/alerting_manifest.go +++ b/apps/alerting/notifications/pkg/apis/alerting_manifest.go @@ -14,6 +14,8 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1" ) +var () + var appManifestData = app.ManifestData{ AppName: "alerting", Group: "notifications.alerting.grafana.app", diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile index 1a948f1b9a8..701b1d0854e 100644 --- a/apps/dashboard/Makefile +++ b/apps/dashboard/Makefile @@ -1,4 +1,4 @@ -APP_SDK_VERSION := v0.39.0 +APP_SDK_VERSION := v0.39.2 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/dashboard/go.mod b/apps/dashboard/go.mod index 6d9028dccf9..318bb5ac480 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -4,11 +4,11 @@ go 1.24.4 require ( cuelang.org/go v0.11.1 - github.com/grafana/grafana-app-sdk v0.39.0 + github.com/grafana/grafana-app-sdk v0.39.2 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 - k8s.io/apimachinery v0.33.1 + k8s.io/apimachinery v0.33.2 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -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.38.2 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.1 // 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 @@ -123,7 +123,7 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.1 // indirect + k8s.io/client-go v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 412e87a6034..acb1a072ce5 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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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= @@ -379,12 +379,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= 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= diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index ccce02984c8..180cc985669 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -111,6 +111,8 @@ DashboardLink: { keepTime: bool | *false } +// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec +// This type is widely used in the codebase and changing it will have a big impact DataSourceRef: { // The plugin type-id type?: string @@ -385,8 +387,7 @@ VizConfigKind: { } AnnotationQuerySpec: { - datasource?: DataSourceRef - query?: DataQueryKind + query: DataQueryKind enable: bool hide: bool iconColor: string @@ -412,15 +413,19 @@ QueryOptionsSpec: { } DataQueryKind: { - // The kind of a DataQueryKind is the datasource type - kind: string + kind: "DataQuery" + group: string + version: string | *"v0" + // New type for datasource reference + // Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS. + datasource?: { + name?: string + } spec: [string]: _ } PanelQuerySpec: { query: DataQueryKind - datasource?: DataSourceRef - refId: string hidden: bool } @@ -723,7 +728,6 @@ QueryVariableSpec: { refresh: VariableRefresh skipUrlSync: bool | *false description?: string - datasource?: DataSourceRef query: DataQueryKind regex: string | *"" sort: VariableSort diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue index 98fc8804de6..50306a06aa0 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue @@ -115,6 +115,8 @@ DashboardLink: { keepTime: bool | *false } +// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec +// This type is widely used in the codebase and changing it will have a big impact DataSourceRef: { // The plugin type-id type?: string @@ -389,8 +391,7 @@ VizConfigKind: { } AnnotationQuerySpec: { - datasource?: DataSourceRef - query?: DataQueryKind + query: DataQueryKind enable: bool hide: bool iconColor: string @@ -416,15 +417,19 @@ QueryOptionsSpec: { } DataQueryKind: { - // The kind of a DataQueryKind is the datasource type - kind: string + kind: "DataQuery" + group: string + version: string | *"v0" + // New type for datasource reference + // Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS. + datasource?: { + name?: string + } spec: [string]: _ } PanelQuerySpec: { query: DataQueryKind - datasource?: DataSourceRef - refId: string hidden: bool } @@ -727,7 +732,6 @@ QueryVariableSpec: { refresh: VariableRefresh skipUrlSync: bool | *false description?: string - datasource?: DataSourceRef query: DataQueryKind regex: string | *"" sort: VariableSort diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 5b599cb414e..79d77c4de74 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -23,14 +23,13 @@ func NewDashboardAnnotationQueryKind() *DashboardAnnotationQueryKind { // +k8s:openapi-gen=true type DashboardAnnotationQuerySpec struct { - Datasource *DashboardDataSourceRef `json:"datasource,omitempty"` - Query *DashboardDataQueryKind `json:"query,omitempty"` - Enable bool `json:"enable"` - Hide bool `json:"hide"` - IconColor string `json:"iconColor"` - Name string `json:"name"` - BuiltIn *bool `json:"builtIn,omitempty"` - Filter *DashboardAnnotationPanelFilter `json:"filter,omitempty"` + Query DashboardDataQueryKind `json:"query"` + Enable bool `json:"enable"` + Hide bool `json:"hide"` + IconColor string `json:"iconColor"` + Name string `json:"name"` + BuiltIn *bool `json:"builtIn,omitempty"` + Filter *DashboardAnnotationPanelFilter `json:"filter,omitempty"` // Catch-all field for datasource-specific properties LegacyOptions map[string]interface{} `json:"legacyOptions,omitempty"` } @@ -38,34 +37,28 @@ type DashboardAnnotationQuerySpec struct { // NewDashboardAnnotationQuerySpec creates a new DashboardAnnotationQuerySpec object. func NewDashboardAnnotationQuerySpec() *DashboardAnnotationQuerySpec { return &DashboardAnnotationQuerySpec{ + Query: *NewDashboardDataQueryKind(), BuiltIn: (func(input bool) *bool { return &input })(false), } } -// +k8s:openapi-gen=true -type DashboardDataSourceRef struct { - // The plugin type-id - Type *string `json:"type,omitempty"` - // Specific datasource instance - Uid *string `json:"uid,omitempty"` -} - -// NewDashboardDataSourceRef creates a new DashboardDataSourceRef object. -func NewDashboardDataSourceRef() *DashboardDataSourceRef { - return &DashboardDataSourceRef{} -} - // +k8s:openapi-gen=true type DashboardDataQueryKind struct { - // The kind of a DataQueryKind is the datasource type - Kind string `json:"kind"` - Spec map[string]interface{} `json:"spec"` + Kind string `json:"kind"` + Group string `json:"group"` + Version string `json:"version"` + // New type for datasource reference + // Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS. + Datasource *DashboardV2alpha1DataQueryKindDatasource `json:"datasource,omitempty"` + Spec map[string]interface{} `json:"spec"` } // NewDashboardDataQueryKind creates a new DashboardDataQueryKind object. func NewDashboardDataQueryKind() *DashboardDataQueryKind { return &DashboardDataQueryKind{ - Spec: map[string]interface{}{}, + Kind: "DataQuery", + Version: "v0", + Spec: map[string]interface{}{}, } } @@ -199,10 +192,9 @@ func NewDashboardPanelQueryKind() *DashboardPanelQueryKind { // +k8s:openapi-gen=true type DashboardPanelQuerySpec struct { - Query DashboardDataQueryKind `json:"query"` - Datasource *DashboardDataSourceRef `json:"datasource,omitempty"` - RefId string `json:"refId"` - Hidden bool `json:"hidden"` + Query DashboardDataQueryKind `json:"query"` + RefId string `json:"refId"` + Hidden bool `json:"hidden"` } // NewDashboardPanelQuerySpec creates a new DashboardPanelQuerySpec object. @@ -763,7 +755,9 @@ type DashboardRepeatOptions struct { // NewDashboardRepeatOptions creates a new DashboardRepeatOptions object. func NewDashboardRepeatOptions() *DashboardRepeatOptions { - return &DashboardRepeatOptions{} + return &DashboardRepeatOptions{ + Mode: DashboardRepeatMode, + } } // other repeat modes will be added in the future: label, frame @@ -938,7 +932,9 @@ type DashboardRowRepeatOptions struct { // NewDashboardRowRepeatOptions creates a new DashboardRowRepeatOptions object. func NewDashboardRowRepeatOptions() *DashboardRowRepeatOptions { - return &DashboardRowRepeatOptions{} + return &DashboardRowRepeatOptions{ + Mode: DashboardRepeatMode, + } } // +k8s:openapi-gen=true @@ -1011,7 +1007,9 @@ type DashboardAutoGridRepeatOptions struct { // NewDashboardAutoGridRepeatOptions creates a new DashboardAutoGridRepeatOptions object. func NewDashboardAutoGridRepeatOptions() *DashboardAutoGridRepeatOptions { - return &DashboardAutoGridRepeatOptions{} + return &DashboardAutoGridRepeatOptions{ + Mode: DashboardRepeatMode, + } } // +k8s:openapi-gen=true @@ -1077,7 +1075,9 @@ type DashboardTabRepeatOptions struct { // NewDashboardTabRepeatOptions creates a new DashboardTabRepeatOptions object. func NewDashboardTabRepeatOptions() *DashboardTabRepeatOptions { - return &DashboardTabRepeatOptions{} + return &DashboardTabRepeatOptions{ + Mode: DashboardRepeatMode, + } } // Links with references to other dashboards or external resources @@ -1221,7 +1221,6 @@ type DashboardQueryVariableSpec struct { Refresh DashboardVariableRefresh `json:"refresh"` SkipUrlSync bool `json:"skipUrlSync"` Description *string `json:"description,omitempty"` - Datasource *DashboardDataSourceRef `json:"datasource,omitempty"` Query DashboardDataQueryKind `json:"query"` Regex string `json:"regex"` Sort DashboardVariableSort `json:"sort"` @@ -1627,6 +1626,21 @@ func NewDashboardGroupByVariableSpec() *DashboardGroupByVariableSpec { } } +// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec +// This type is widely used in the codebase and changing it will have a big impact +// +k8s:openapi-gen=true +type DashboardDataSourceRef struct { + // The plugin type-id + Type *string `json:"type,omitempty"` + // Specific datasource instance + Uid *string `json:"uid,omitempty"` +} + +// NewDashboardDataSourceRef creates a new DashboardDataSourceRef object. +func NewDashboardDataSourceRef() *DashboardDataSourceRef { + return &DashboardDataSourceRef{} +} + // Adhoc variable kind // +k8s:openapi-gen=true type DashboardAdhocVariableKind struct { @@ -1687,7 +1701,9 @@ type DashboardAdHocFilterWithLabels struct { // NewDashboardAdHocFilterWithLabels creates a new DashboardAdHocFilterWithLabels object. func NewDashboardAdHocFilterWithLabels() *DashboardAdHocFilterWithLabels { - return &DashboardAdHocFilterWithLabels{} + return &DashboardAdHocFilterWithLabels{ + Origin: DashboardFilterOrigin, + } } // Determine the origin of the adhoc variable filter @@ -1758,6 +1774,16 @@ func NewDashboardSpec() *DashboardSpec { } } +// +k8s:openapi-gen=true +type DashboardV2alpha1DataQueryKindDatasource struct { + Name *string `json:"name,omitempty"` +} + +// NewDashboardV2alpha1DataQueryKindDatasource creates a new DashboardV2alpha1DataQueryKindDatasource object. +func NewDashboardV2alpha1DataQueryKindDatasource() *DashboardV2alpha1DataQueryKindDatasource { + return &DashboardV2alpha1DataQueryKindDatasource{} +} + // +k8s:openapi-gen=true type DashboardV2alpha1FieldConfigSourceOverrides struct { Matcher DashboardMatcherConfig `json:"matcher"` diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index 85b5a6f65cb..4e55caae3c6 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -109,6 +109,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeRangeOption": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeRangeOption(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeSettingsSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeSettingsSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTransformationKind": schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1DataQueryKindDatasource(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RangeMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RangeMapOptions(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RegexMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RegexMapOptions(ref), @@ -595,14 +596,10 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationQuerySpec(ref common. SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "datasource": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"), - }, - }, "query": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"), + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"), }, }, "enable": { @@ -660,11 +657,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationQuerySpec(ref common. }, }, }, - Required: []string{"enable", "hide", "iconColor", "name"}, + Required: []string{"query", "enable", "hide", "iconColor", "name"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAnnotationPanelFilter", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAnnotationPanelFilter", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"}, } } @@ -1505,10 +1502,29 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataQueryKind(ref common.Refere Properties: map[string]spec.Schema{ "kind": { SchemaProps: spec.SchemaProps{ - Description: "The kind of a DataQueryKind is the datasource type", - Default: "", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "datasource": { + SchemaProps: spec.SchemaProps{ + Description: "New type for datasource reference Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.", + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource"), }, }, "spec": { @@ -1526,9 +1542,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataQueryKind(ref common.Refere }, }, }, - Required: []string{"kind", "spec"}, + Required: []string{"kind", "group", "version", "spec"}, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource"}, } } @@ -1536,7 +1554,8 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataSourceRef(ref common.Refere return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec This type is widely used in the codebase and changing it will have a big impact", + Type: []string{"object"}, Properties: map[string]spec.Schema{ "type": { SchemaProps: spec.SchemaProps{ @@ -2890,11 +2909,6 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardPanelQuerySpec(ref common.Refer Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"), }, }, - "datasource": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"), - }, - }, "refId": { SchemaProps: spec.SchemaProps{ Default: "", @@ -2914,7 +2928,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardPanelQuerySpec(ref common.Refer }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"}, } } @@ -3250,11 +3264,6 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re Format: "", }, }, - "datasource": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"), - }, - }, "query": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, @@ -3332,7 +3341,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardVariableOption"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardVariableOption"}, } } @@ -4320,6 +4329,24 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref common.R } } +func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1DataQueryKindDatasource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/folder/go.mod b/apps/folder/go.mod index c12fa59b93d..88db83aa995 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -3,9 +3,9 @@ module github.com/grafana/grafana/apps/folder go 1.24.4 require ( - github.com/grafana/grafana-app-sdk v0.39.0 + github.com/grafana/grafana-app-sdk v0.39.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e - k8s.io/apimachinery v0.33.1 + k8s.io/apimachinery v0.33.2 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.38.2 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -53,7 +53,7 @@ require ( 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/client-go v0.33.1 // indirect + k8s.io/client-go v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/apps/folder/go.sum b/apps/folder/go.sum index c61276e084f..8f346fcbfcd 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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -150,12 +150,12 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= 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= diff --git a/apps/folder/pkg/apis/folder_manifest.go b/apps/folder/pkg/apis/folder_manifest.go index 8491ddc6083..8525663fea8 100644 --- a/apps/folder/pkg/apis/folder_manifest.go +++ b/apps/folder/pkg/apis/folder_manifest.go @@ -14,6 +14,8 @@ import ( v1beta1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) +var () + var appManifestData = app.ManifestData{ AppName: "folder", Group: "folder.grafana.app", diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 811abf007e5..5c4086d8596 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -3,9 +3,9 @@ module github.com/grafana/grafana/apps/iam go 1.24.4 require ( - github.com/grafana/grafana-app-sdk v0.39.0 + github.com/grafana/grafana-app-sdk v0.39.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e - k8s.io/apimachinery v0.33.1 + k8s.io/apimachinery v0.33.2 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.38.2 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -53,7 +53,7 @@ require ( 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/client-go v0.33.1 // indirect + k8s.io/client-go v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index c61276e084f..8f346fcbfcd 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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -150,12 +150,12 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= 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= diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index 8f144c3cd27..667b7a10354 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -14,8 +14,6 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" ) -var () - var appManifestData = app.ManifestData{ AppName: "iam", Group: "iam.grafana.app", diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 618ea42c9a9..43eb2f0df90 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -3,9 +3,9 @@ module github.com/grafana/grafana/apps/playlist go 1.24.4 require ( - github.com/grafana/grafana-app-sdk v0.39.0 - k8s.io/apimachinery v0.33.1 - k8s.io/client-go v0.33.1 + github.com/grafana/grafana-app-sdk v0.39.2 + k8s.io/apimachinery v0.33.2 + k8s.io/client-go v0.33.2 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -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.38.2 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -79,8 +79,8 @@ require ( 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.1 // indirect - k8s.io/apiextensions-apiserver v0.33.1 // indirect + k8s.io/api v0.33.2 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index 8726cf84afc..fbb1bce6d31 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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -206,14 +206,14 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= 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= diff --git a/conf/provisioning/sample/dashboard-v2.json b/conf/provisioning/sample/dashboard-v2.json index ad4671943c3..73592fd09f6 100644 --- a/conf/provisioning/sample/dashboard-v2.json +++ b/conf/provisioning/sample/dashboard-v2.json @@ -10,14 +10,44 @@ "kind": "AnnotationQuery", "spec": { "builtIn": true, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, "enable": true, + "filter": { + "exclude": false, + "ids": [1] + }, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts" + "name": "Annotations \u0026 Alerts", + "query": { + "group": "grafana", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Test data annotations", + "builtIn": false, + "filter": { + "exclude": false, + "ids": [1] + } } } ], @@ -37,8 +67,16 @@ "spec": { "hidden": false, "query": { - "kind": "grafana-testdata-datasource", - "spec": {} + "datasource": { + "name": "gdev-testdata" + }, + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 3 + }, + "version": "v0" }, "refId": "A" } @@ -51,7 +89,7 @@ "description": "", "id": 1, "links": [], - "title": "Simle timeseries", + "title": "Simple timeseries (WITH DS REF)", "vizConfig": { "kind": "timeseries", "spec": { @@ -93,12 +131,12 @@ "mode": "off" } }, - "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -122,7 +160,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.0-pre" + "pluginVersion": "12.1.0-pre" } } } @@ -139,8 +177,13 @@ "spec": { "hidden": false, "query": { - "kind": "grafana-testdata-datasource", - "spec": {} + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 4 + }, + "version": "v0" }, "refId": "A" } @@ -153,7 +196,7 @@ "description": "", "id": 2, "links": [], - "title": "Simple stat", + "title": "Simple stat (NO DS REF)", "vizConfig": { "kind": "stat", "spec": { @@ -162,12 +205,12 @@ "color": { "mode": "thresholds" }, - "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -193,7 +236,496 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "12.0.0-pre" + "pluginVersion": "12.1.0-pre" + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "useBackend": false + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 3, + "links": [], + "title": "Panel with NO REF to gdev-prometheus", + "vizConfig": { + "kind": "timeseries", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre" + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "gdev-prometheus" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "useBackend": false + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 4, + "links": [], + "title": "Panel with ref to gdev-prometheus", + "vizConfig": { + "kind": "timeseries", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre" + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "gdev-prometheus" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "useBackend": false + }, + "version": "v0" + }, + "refId": "A" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "gdev-testdata" + }, + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + }, + "refId": "B" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 5, + "links": [], + "title": "Mixed DS WITH REFS", + "vizConfig": { + "kind": "timeseries", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre" + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "useBackend": false + }, + "version": "v0" + }, + "refId": "A" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + }, + "refId": "B" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 6, + "links": [], + "title": "Mixed DS WITHOUT REFS", + "vizConfig": { + "kind": "timeseries", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre" } } } @@ -204,6 +736,24 @@ "spec": { "columnWidthMode": "standard", "items": [ + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, { "kind": "AutoGridLayoutItem", "spec": { @@ -221,6 +771,24 @@ "name": "panel-1" } } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } } ], "maxColumnCount": 3, @@ -235,7 +803,7 @@ "autoRefresh": "", "autoRefreshIntervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "fiscalYearStartMonth": 0, - "from": "now-6h", + "from": "now-5m", "hideTimepicker": false, "timezone": "browser", "to": "now" diff --git a/e2e/dashboards/TestV2Dashboard.json b/e2e/dashboards/TestV2Dashboard.json index da89762c3a6..13e86f6c66a 100644 --- a/e2e/dashboards/TestV2Dashboard.json +++ b/e2e/dashboards/TestV2Dashboard.json @@ -2,21 +2,11 @@ "apiVersion": "dashboard.grafana.app/v2alpha1", "kind": "Dashboard", "metadata": { - "name": "admjzp8", - "namespace": "default", - "uid": "hrbekBWXeM7nC7GtouIbcngFyuqt8Xx7KlE4AnTwV7AX", - "resourceVersion": "1", + "name": "fa400625-2a44-4add-a369-e6c972eb4bd6", "generation": 1, - "creationTimestamp": "2025-05-16T09:41:56Z", - "labels": { - "grafana.app/deprecatedInternalID": "182" - }, - "annotations": { - "grafana.app/createdBy": "user:cejvsh18uudxcf", - "grafana.app/updatedBy": "user:cejvsh18uudxcf", - "grafana.app/updatedTimestamp": "2025-05-16T09:41:56Z", - "grafana.app/folder": "" - } + "creationTimestamp": "2025-05-27T11:40:22Z", + "labels": {}, + "annotations": {} }, "spec": { "annotations": [ @@ -24,19 +14,20 @@ "kind": "AnnotationQuery", "spec": { "builtIn": true, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts" + "name": "Annotations & Alerts", + "query": { + "group": "grafana", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + } } } ], "cursorSync": "Off", - "description": "", "editable": true, "elements": { "panel-1": { @@ -49,14 +40,12 @@ { "kind": "PanelQuery", "spec": { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "PD8C576611E62080A" - }, "hidden": false, "query": { - "kind": "grafana-testdata-datasource", - "spec": {} + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": {}, + "version": "v0" }, "refId": "A" } @@ -155,14 +144,12 @@ { "kind": "PanelQuery", "spec": { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "PD8C576611E62080A" - }, "hidden": false, "query": { - "kind": "grafana-testdata-datasource", - "spec": {} + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": {}, + "version": "v0" }, "refId": "A" } @@ -261,14 +248,12 @@ { "kind": "PanelQuery", "spec": { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "PD8C576611E62080A" - }, "hidden": false, "query": { - "kind": "grafana-testdata-datasource", - "spec": {} + "group": "grafana-testdata-datasource", + "kind": "DataQuery", + "spec": {}, + "version": "v0" }, "refId": "A" } @@ -380,7 +365,7 @@ "spec": { "element": { "kind": "ElementReference", - "name": "panel-2" + "name": "panel-3" }, "height": 8, "width": 12, @@ -393,7 +378,7 @@ "spec": { "element": { "kind": "ElementReference", - "name": "panel-3" + "name": "panel-2" }, "height": 8, "width": 12, @@ -417,7 +402,7 @@ "timezone": "browser", "to": "now" }, - "title": "New Test V2 Dashboard", + "title": "Test V2 Dashboard", "variables": [] }, "status": {} diff --git a/e2e/utils/flows/scenes/importDashboard.ts b/e2e/utils/flows/scenes/importDashboard.ts index e0b0a8ae929..a916f733d06 100644 --- a/e2e/utils/flows/scenes/importDashboard.ts +++ b/e2e/utils/flows/scenes/importDashboard.ts @@ -17,5 +17,9 @@ export const importV2Dashboard = ({ title }: ImportDashboardConfig) => { if (title) { e2e.components.ImportDashboardForm.name().clear().type(title); } + + e2e.components.DataSourcePicker.inputV2().click(); + cy.get('div[data-testid="data-source-card"]').first().click(); + e2e.components.ImportDashboardForm.submit().click(); }; diff --git a/go.mod b/go.mod index d029517eba2..44aa93b4190 100644 --- a/go.mod +++ b/go.mod @@ -97,8 +97,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.39.0 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-app-sdk/logging v0.38.2 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.39.2 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk/logging v0.39.1 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.0.2 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad @@ -212,11 +212,11 @@ require ( gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group gopkg.in/yaml.v2 v2.4.0 // @grafana/alerting-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend - k8s.io/api v0.33.1 // @grafana/grafana-app-platform-squad - k8s.io/apimachinery v0.33.1 // @grafana/grafana-app-platform-squad - k8s.io/apiserver v0.33.1 // @grafana/grafana-app-platform-squad - k8s.io/client-go v0.33.1 // @grafana/grafana-app-platform-squad - k8s.io/component-base v0.33.1 // @grafana/grafana-app-platform-squad + k8s.io/api v0.33.2 // @grafana/grafana-app-platform-squad + k8s.io/apimachinery v0.33.2 // @grafana/grafana-app-platform-squad + k8s.io/apiserver v0.33.2 // @grafana/grafana-app-platform-squad + k8s.io/client-go v0.33.2 // @grafana/grafana-app-platform-squad + k8s.io/component-base v0.33.2 // @grafana/grafana-app-platform-squad k8s.io/klog/v2 v2.130.1 // @grafana/grafana-app-platform-squad k8s.io/kube-aggregator v0.33.1 // @grafana/grafana-app-platform-squad k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad @@ -587,8 +587,8 @@ require ( gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/telebot.v3 v3.2.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiextensions-apiserver v0.33.1 // indirect - k8s.io/kms v0.33.1 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect + k8s.io/kms v0.33.2 // indirect modernc.org/libc v1.65.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.10.0 // indirect diff --git a/go.sum b/go.sum index 5c026a7455a..88a184a171a 100644 --- a/go.sum +++ b/go.sum @@ -1606,10 +1606,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.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= +github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= @@ -3571,19 +3571,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -3591,8 +3591,8 @@ k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.33.1 h1:jJKrFhsbVofpyLF+G8k+drwOAF9CMQpxilHa5Uilb8Q= -k8s.io/kms v0.33.1/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= +k8s.io/kms v0.33.2 h1:GFwNXX4CZGQCg9DPOaJi1/+iKidCtB9/OIAGdzRo8FI= +k8s.io/kms v0.33.2/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= k8s.io/kube-aggregator v0.33.1 h1:PigQUqAvd6Y4hBjQAqhKz3lEJC2VHLL4bSOEuS06a40= k8s.io/kube-aggregator v0.33.1/go.mod h1:16/wlU5Lj7hNJSv7JSu5FLvxyrgiJVLCHzfVoECAsuI= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= diff --git a/go.work.sum b/go.work.sum index b9b914db505..273fa901a4b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -6,13 +6,19 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= -buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1 h1:MXQkdpJwbCwskYLQ3dUv/5gmk8fSsUwu78u+kXebbek= -buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1/go.mod h1:pjl83IqpNF7Lm/lOPMLQ5IIfCg+yTu2A9OgYTcEncCs= -buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1 h1:9nqE/pDc/HXAXiD5pZncPywjAzWgKuBkFFYgdK2lVU8= -buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1/go.mod h1:1M7nlq2ljfzb95x9LaA2j1gYIvDkVZii58mGvTa9ExM= buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew= buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go v0.118.1/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M= +cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= cloud.google.com/go/accessapproval v1.8.3 h1:axlU03FRiXDNupsmPG7LKzuS4Enk1gf598M62lWVB74= cloud.google.com/go/accessapproval v1.8.3/go.mod h1:3speETyAv63TDrDmo5lIkpVueFkQcQchkiw/TAMbBo4= cloud.google.com/go/accesscontextmanager v1.9.3 h1:8zVoeiBa4erMCLEXltOcqVEsZhS26JZ5/Vrgs59eQiI= @@ -38,6 +44,14 @@ cloud.google.com/go/asset v1.20.4 h1:6oNgjcs5KCPGBD71G0IccK6TfeFsEtBTyQ3Q+Dn09bs cloud.google.com/go/asset v1.20.4/go.mod h1:DP09pZ+SoFWUZyPZx26xVroHk+6+9umnQv+01yfJxbM= cloud.google.com/go/assuredworkloads v1.12.3 h1:RU1WhF1zMggdXAZ+ezYTn4Eh/FdiX7sz8lLXGERn4Po= cloud.google.com/go/assuredworkloads v1.12.3/go.mod h1:iGBkyMGdtlsxhCi4Ys5SeuvIrPTeI6HeuEJt7qJgJT8= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/automl v1.14.4 h1:vkD+hQ75SMINMgJBT/KDpFYvfQLzJbtIQZdw0AWq8Rs= cloud.google.com/go/automl v1.14.4/go.mod h1:sVfsJ+g46y7QiQXpVs9nZ/h8ntdujHm5xhjHW32b3n4= cloud.google.com/go/baremetalsolution v1.3.3 h1:OL+KT+wCumdDhG44aeqGAdkwdT8Wa4Lh+o4INM+CQjw= @@ -127,8 +141,12 @@ cloud.google.com/go/gkehub v0.15.3/go.mod h1:nzFT/Q+4HdQES/F+FP1QACEEWR9Hd+Sh00q cloud.google.com/go/gkemulticloud v1.5.1 h1:JWe6PDNpNU88ZYvQkTd7w28fgeIs/gg6i0hcjUkgZ3M= cloud.google.com/go/gkemulticloud v1.5.1/go.mod h1:OdmhfSPXuJ0Kn9dQ2I3Ou7XZ3QK8caV4XVOJZwrIa3s= cloud.google.com/go/grafeas v0.2.0 h1:CYjC+xzdPvbV65gi6Dr4YowKcmLo045pm18L0DhdELM= +cloud.google.com/go/grafeas v0.3.11 h1:CobnwnyeY1j1Defi5vbEircI+jfrk3ci5m004ZjiFP4= +cloud.google.com/go/grafeas v0.3.11/go.mod h1:dcQyG2+T4tBgG0MvJAh7g2wl/xHV2w+RZIqivwuLjNg= cloud.google.com/go/gsuiteaddons v1.7.4 h1:f3eMYsCDdg2AeldIPdKmBRxN1WoiTpE3RvX5orcm/I8= cloud.google.com/go/gsuiteaddons v1.7.4/go.mod h1:gpE2RUok+HUhuK7RPE/fCOEgnTffS0lCHRaAZLxAMeE= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iam v1.4.0/go.mod h1:gMBgqPaERlriaOV0CUl//XUzDhSfXevn4OEUbg6VRs4= cloud.google.com/go/iap v1.10.3 h1:OWNYFHPyIBNHEAEFdVKOltYWe0g3izSrpFJW6Iidovk= cloud.google.com/go/iap v1.10.3/go.mod h1:xKgn7bocMuCFYhzRizRWP635E2LNPnIXT7DW0TlyPJ8= cloud.google.com/go/ids v1.5.3 h1:wbFF7twu0XScFr+dtsVxTTttbFIRYt/SJjZiHFidtYE= @@ -139,6 +157,9 @@ cloud.google.com/go/language v1.14.3 h1:8hmFMiS3wjjj3TX/U1zZYTgzwZoUjDbo9PaqcYEm cloud.google.com/go/language v1.14.3/go.mod h1:hjamj+KH//QzF561ZuU2J+82DdMlFUjmiGVWpovGGSA= cloud.google.com/go/lifesciences v0.10.3 h1:Z05C+Ui953f0EQx9hJ1la6+QQl8ADrIs3iNwP5Elkpg= cloud.google.com/go/lifesciences v0.10.3/go.mod h1:hnUUFht+KcZcliixAg+iOh88FUwAzDQQt5tWd7iIpNg= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= cloud.google.com/go/managedidentities v1.7.3 h1:b9xGs24BIjfyvLgCtJoClOZpPi8d8owPgWe5JEINgaY= cloud.google.com/go/managedidentities v1.7.3/go.mod h1:H9hO2aMkjlpY+CNnKWRh+WoQiUIDO8457wWzUGsdtLA= cloud.google.com/go/maps v1.19.0 h1:deVm1ZFyCrUwxG11CdvtBz350VG5JUQ/LHTLnQrBgrM= @@ -149,6 +170,8 @@ cloud.google.com/go/memcache v1.11.3 h1:XH/qT3GbbSH//R0JTqR77lRpBxaa0N9sHgAzfwbT cloud.google.com/go/memcache v1.11.3/go.mod h1:UeWI9cmY7hvjU1EU6dwJcQb6EFG4GaM3KNXOO2OFsbI= cloud.google.com/go/metastore v1.14.3 h1:jDqeCw6NGDRAPT9+2Y/EjnWAB0BfCcUfmPLOyhB0eHs= cloud.google.com/go/metastore v1.14.3/go.mod h1:HlbGVOvg0ubBLVFRk3Otj3gtuzInuzO/TImOBwsKlG4= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= cloud.google.com/go/networkconnectivity v1.16.1 h1:YsVhG71ZC4FkqCP2oCI55x/JeGFyd7738Lt8iNTrzJw= cloud.google.com/go/networkconnectivity v1.16.1/go.mod h1:GBC1iOLkblcnhcnfRV92j4KzqGBrEI6tT7LP52nZCTk= cloud.google.com/go/networkmanagement v1.18.0 h1:oEoFGPYxTBsY47h0zdoE2ojV5aU/541D83UmxfjHWaE= @@ -213,6 +236,8 @@ cloud.google.com/go/spanner v1.76.1 h1:vYbVZuXfnFwvNcvH3lhI2PeUA+kHyqKmLC7mJWaC4 cloud.google.com/go/spanner v1.76.1/go.mod h1:YtwoE+zObKY7+ZeDCBtZ2ukM+1/iPaMfUM+KnTh/sx0= cloud.google.com/go/speech v1.26.0 h1:qvURtJs7BQzQhbxWxwai0pT79S8KLVKJ/4W8igVkt1Y= cloud.google.com/go/speech v1.26.0/go.mod h1:78bqDV2SgwFlP/M4n3i3PwLthFq6ta7qmyG6lUV7UCA= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/storagetransfer v1.12.1 h1:W3v9A7MGBN7H9sAFstyciwP/1XEQhUhZfrjclmDnpMs= cloud.google.com/go/storagetransfer v1.12.1/go.mod h1:hQqbfs8/LTmObJyCC0KrlBw8yBJ2bSFlaGila0qBMk4= cloud.google.com/go/talent v1.8.0 h1:olv+s2g+LGXeJi+MYF1wI44/TwHaVnO0N7PiucVf5ZQ= @@ -221,6 +246,7 @@ cloud.google.com/go/texttospeech v1.11.0 h1:YF/RdNb+jUEp22cIZCvqiFjfA5OxGE+Dxss3 cloud.google.com/go/texttospeech v1.11.0/go.mod h1:7M2ro3I2QfIEvArFk1TJ+pqXJqhszDtxUpnIv/150As= cloud.google.com/go/tpu v1.8.0 h1:BvMNijOb6Vd46Rr/SR5jWv1MPosOhVsi0UaeAGNjeds= cloud.google.com/go/tpu v1.8.0/go.mod h1:XyNzyK1xc55WvL5rZEML0Z9/TUHDfnq0uICkQw6rWMo= +cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= cloud.google.com/go/translate v1.12.3 h1:XJ7LipYJi80BCgVk2lx1fwc7DIYM6oV2qx1G4IAGQ5w= cloud.google.com/go/translate v1.12.3/go.mod h1:qINOVpgmgBnY4YTFHdfVO4nLrSBlpvlIyosqpGEgyEg= cloud.google.com/go/video v1.23.3 h1:C2FH+6yr6LCZC4fP0gm9FwJB/SRh5Ul88O5Sc/bL83I= @@ -293,6 +319,13 @@ github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW5 github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU= github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= +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= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSgcJLc= github.com/IBM/go-sdk-core/v5 v5.17.4/go.mod h1:KsAAI7eStAWwQa4F96MLy+whYSh39JzNjklZRbN/8ns= github.com/IBM/ibm-cos-sdk-go v1.11.0 h1:Jp55NLN3OvBwucMGpP5wNybyjncsmTZ9+GPHai/1cE8= @@ -314,7 +347,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0= github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPrQV1xCoSjc= github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s= -github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76/go.mod h1:oM0MHmQ3nDsq609SS36p+oYbRi16+oVvU2Bw4Ipv0SE= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6 h1:5kUcJJAKWWI82Xnp/CaU0eu5hLlHkmm9acjowSkwCd0= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6/go.mod h1:JwrycNnC8+sZPDyzM3MQ86LvaGzSpfxg885KOOwFRW4= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= @@ -350,22 +382,19 @@ github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fus github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4= github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI= github.com/apache/arrow/go/v11 v11.0.0 h1:hqauxvFQxww+0mEU/2XHG6LT7eZternCZq+A5Yly2uM= +github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1 h1:nMp7diZObd4XEVUR0pEvn7/E13JIgManMX79Q6quV6E= github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI= -github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= -github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= -github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= @@ -376,16 +405,12 @@ github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI= github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7M5lu8PqdvOmbRHw= github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/baidubce/bce-sdk-go v0.9.188 h1:8MA7ewe4VpX01uYl7Kic6ZvfIReUFdSKbY46ZqlQM7U= github.com/baidubce/bce-sdk-go v0.9.188/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/bazelbuild/rules_go v0.49.0 h1:5vCbuvy8Q11g41lseGJDc5vxhDjJtfxr6nM/IC4VmqM= +github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC8+vGZA= github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -427,8 +452,13 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyY github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coder/quartz v0.1.0 h1:cLL+0g5l7xTf6ordRnUMMiZtRE8Sq5LxpghS63vEXrQ= @@ -459,6 +489,8 @@ github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37g github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= @@ -533,6 +565,12 @@ github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLix github.com/elastic/lunes v0.1.0 h1:amRtLPjwkWtzDF/RKzcEPMvSsSseLDLW+bnhfNSLRe4= github.com/elastic/lunes v0.1.0/go.mod h1:xGphYIt3XdZRtyWosHQTErsQTd4OP1p9wsbVoHelrd4= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= +github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/expr-lang/expr v1.17.0/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88= @@ -543,6 +581,7 @@ github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c h1:yKN46XJHYC github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c/go.mod h1:L92h+dgwElEyUuShEwjbiHjseW410WIcNz+Bjutc8YQ= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= @@ -567,8 +606,12 @@ github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1: github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -594,16 +637,26 @@ github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/googleapis/cloud-bigtable-clients-test v0.0.3 h1:afMKTvA/jc6jSTMkeHBZGFDTt8Cc+kb1ATFzqMK85hw= +github.com/googleapis/cloud-bigtable-clients-test v0.0.3/go.mod h1:TWtDzrrAI70C3dNLDY+nZN3gxHtFdZIbpL9rCTFyxE0= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= @@ -626,8 +679,6 @@ github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05 h1:hMzOzI/S0nkZt0 github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05/go.mod h1:K3YAJumchx5EEZItGv4D3pCv/Ux796hmoOibP/p/eYk= github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 h1:H2p3XKDHnTBGkMXLCgXiqb2dFnHbQ4zPDXOwKK4Ne3Y= github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= -github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945 h1:3imTbxFpZSVI6IBIB9mn+Xc40lUweWjfMaBSgXR7rLs= -github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= @@ -642,8 +693,6 @@ github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+ github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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= -github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q= github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= @@ -672,12 +721,12 @@ 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.3 h1:9ibD1c5O5u9fifEkBSig+jAc41TUEz+M+bWQqDsofP4= -github.com/grafana/sqlds/v4 v4.2.3/go.mod h1:bv+XHabfUF4xkgg4y+nYFCK8rpMHZsMaQk56qNaJcAM= 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= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -727,6 +776,7 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLwFEN7T+Flr8sw= github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -837,6 +887,7 @@ github.com/ncw/swift/v2 v2.0.2/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItc github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= +github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.124.1 h1:+aiMrDR6xiaDM7xN4ByrBYI0Craqt68nZicmpYpt0co= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.124.1/go.mod h1:H/TEWN4jgExt0McrtrBK2VFK6r9LRsWtqhEZrH690rs= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1 h1:NrjsoVPxI6lmV8jPImDcMeqYh+97Y71f/HB5Sfpfe3I= @@ -941,8 +992,13 @@ github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5Bdj github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/substrait-io/substrait v0.66.1-0.20250205013839-a30b3e2d7ec6 h1:XqtxwYFCjS4L0o1QD4ipGHCuFG94U0f6BeldbilGQjU= github.com/substrait-io/substrait v0.66.1-0.20250205013839-a30b3e2d7ec6/go.mod h1:MPFNw6sToJgpD5Z2rj0rQrdP/Oq8HG7Z2t3CAEHtkHw= github.com/substrait-io/substrait-go/v3 v3.9.1 h1:2yfHDHpK6KMcvLd0bJVzUJoeXO+K98yS+ciBruxD9po= @@ -1034,6 +1090,9 @@ github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= +go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= +go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= go.opentelemetry.io/collector v0.124.0 h1:g/dfdGFhBcQI0ggGxTmGlJnJ6Yl6T2gVxQoIj4UfXCc= @@ -1150,54 +1209,154 @@ go.opentelemetry.io/collector/service/hostcapabilities v0.124.0 h1:ArxbARF7+bnzK go.opentelemetry.io/collector/service/hostcapabilities v0.124.0/go.mod h1:vifQsB+lkeCsjBCRPVHca9lJ3pLpLPZKCGrG77nkxFQ= go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 h1:ojdSRDvjrnm30beHOmwsSvLpoRF40MlwNCA+Oo93kXU= go.opentelemetry.io/contrib/bridges/otelzap v0.10.0/go.mod h1:oTTm4g7NEtHSV2i/0FeVdPaPgUIZPfQkFbq0vbzqnv0= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/contrib/otelconf v0.15.0 h1:BLNiIUsrNcqhSKpsa6CnhE6LdrpY1A8X0szMVsu99eo= go.opentelemetry.io/contrib/otelconf v0.15.0/go.mod h1:OPH1seO5z9dp1P26gnLtoM9ht7JDvh3Ws6XRHuXqImY= go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ= go.opentelemetry.io/contrib/propagators/b3 v1.35.0/go.mod h1:9+SNxwqvCWo1qQwUpACBY5YKNVxFJn5mlbXg/4+uKBg= go.opentelemetry.io/contrib/zpages v0.60.0 h1:wOM9ie1Hz4H88L9KE6GrGbKJhfm+8F1NfW/Y3q9Xt+8= go.opentelemetry.io/contrib/zpages v0.60.0/go.mod h1:xqfToSRGh2MYUsfyErNz8jnNDPlnpZqWM/y6Z2Cx7xw= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/bridge/opencensus v1.35.0 h1:4nJfffRbozhqnuukfRkiahA94mnpryCLJLiduMIDJKI= go.opentelemetry.io/otel/bridge/opencensus v1.35.0/go.mod h1:359S30saRYNsB4A46EDx91SpXsQFNgkma7ftg2/L5/M= go.opentelemetry.io/otel/bridge/opentracing v1.35.0 h1:qT4jl1fYl0hHuRopNcwS94QosLFhGYcS0HacPUeXmT4= go.opentelemetry.io/otel/bridge/opentracing v1.35.0/go.mod h1:p5CbIL4v7uQz7mnQD6T/AZc1pPUzwz+2wZ1zrGY9Kgs= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 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/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= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= +golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +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.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.15.2 h1:Tlfh/jBk2tqjLZ4/P8ZIwGrLEWQSPDLRm/SNWKNXiGI= gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= +google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= +google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= +google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= +google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M= +google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ= +google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= +google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= +google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU= +google.golang.org/genproto/googleapis/api v0.0.0-20250227231956-55c901821b1e/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250505200425-f936aa4a68b2 h1:DbpkGFGRkd4GORg+IWQW2EhxUaa/My/PM8d1CGyTDMY= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= 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= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -1216,6 +1375,8 @@ howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= 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= +k8s.io/code-generator v0.33.2/go.mod h1:hBjCA9kPMpjLWwxcr75ReaQfFXY8u+9bEJJ7kRw3J8c= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= diff --git a/packages/grafana-schema/src/schema/dashboard/v2_examples.ts b/packages/grafana-schema/src/schema/dashboard/v2_examples.ts index 4bf078783cb..7944cfae117 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2_examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2_examples.ts @@ -1,4 +1,4 @@ -import { Spec } from './v2alpha1/types.spec.gen'; +import { defaultDataQueryKind, Spec } from './v2alpha1/types.spec.gen'; export const handyTestingSchema: Spec = { title: 'Default Dashboard', @@ -37,15 +37,16 @@ export const handyTestingSchema: Spec = { spec: { builtIn: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'uid', + }, spec: { expr: 'test-query', }, }, - datasource: { - type: 'prometheus', - uid: 'uid', - }, filter: { ids: [1] }, enable: true, hide: false, @@ -57,15 +58,16 @@ export const handyTestingSchema: Spec = { kind: 'AnnotationQuery', spec: { builtIn: false, - datasource: { - type: 'grafana-testdata-datasource', - uid: 'uid', - }, enable: true, iconColor: 'red', name: 'Enabled', query: { - kind: 'grafana-testdata-datasource', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-testdata-datasource', + datasource: { + name: 'uid', + }, spec: { lines: 4, refId: 'Anno', @@ -79,15 +81,16 @@ export const handyTestingSchema: Spec = { kind: 'AnnotationQuery', spec: { builtIn: false, - datasource: { - type: 'grafana-testdata-datasource', - uid: 'uid', - }, enable: false, iconColor: 'yellow', name: 'Disabled', query: { - kind: 'grafana-testdata-datasource', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-testdata-datasource', + datasource: { + name: 'uid', + }, spec: { lines: 5, refId: 'Anno', scenarioId: 'annotations' }, }, hide: false, @@ -97,16 +100,17 @@ export const handyTestingSchema: Spec = { kind: 'AnnotationQuery', spec: { builtIn: false, - datasource: { - type: 'grafana-testdata-datasource', - uid: 'uid', - }, enable: true, hide: true, iconColor: 'dark-purple', name: 'Hidden', query: { - kind: 'grafana-testdata-datasource', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-testdata-datasource', + datasource: { + name: 'uid', + }, spec: { lines: 6, refId: 'Anno', @@ -128,12 +132,13 @@ export const handyTestingSchema: Spec = { kind: 'PanelQuery', spec: { refId: 'A', - datasource: { - type: 'prometheus', - uid: 'datasource1', - }, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'datasource1', + }, spec: { expr: 'test-query', }, @@ -265,10 +270,6 @@ export const handyTestingSchema: Spec = { text: 'text1', value: 'value1', }, - datasource: { - type: 'prometheus', - uid: 'datasource1', - }, definition: 'definition1', description: 'A query variable', hide: 'dontHide', @@ -278,7 +279,12 @@ export const handyTestingSchema: Spec = { name: 'queryVar', options: [], query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'datasource1', + }, spec: { expr: 'test-query', refId: 'A', diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts index 34ee46e0cff..0075a197ffa 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts @@ -11,8 +11,7 @@ export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({ }); export interface AnnotationQuerySpec { - datasource?: DataSourceRef; - query?: DataQueryKind; + query: DataQueryKind; enable: boolean; hide: boolean; iconColor: string; @@ -24,6 +23,7 @@ export interface AnnotationQuerySpec { } export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ + query: defaultDataQueryKind(), enable: false, hide: false, iconColor: "", @@ -31,24 +31,22 @@ export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ builtIn: false, }); -export interface DataSourceRef { - // The plugin type-id - type?: string; - // Specific datasource instance - uid?: string; -} - -export const defaultDataSourceRef = (): DataSourceRef => ({ -}); - export interface DataQueryKind { - // The kind of a DataQueryKind is the datasource type - kind: string; + kind: "DataQuery"; + group: string; + version: string; + // New type for datasource reference + // Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS. + datasource?: { + name?: string; + }; spec: Record; } export const defaultDataQueryKind = (): DataQueryKind => ({ - kind: "", + kind: "DataQuery", + group: "", + version: "v0", spec: {}, }); @@ -151,7 +149,6 @@ export const defaultPanelQueryKind = (): PanelQueryKind => ({ export interface PanelQuerySpec { query: DataQueryKind; - datasource?: DataSourceRef; refId: string; hidden: boolean; } @@ -994,7 +991,6 @@ export interface QueryVariableSpec { refresh: VariableRefresh; skipUrlSync: boolean; description?: string; - datasource?: DataSourceRef; query: DataQueryKind; regex: string; sort: VariableSort; @@ -1283,6 +1279,18 @@ export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ skipUrlSync: false, }); +// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec +// This type is widely used in the codebase and changing it will have a big impact +export interface DataSourceRef { + // The plugin type-id + type?: string; + // Specific datasource instance + uid?: string; +} + +export const defaultDataSourceRef = (): DataSourceRef => ({ +}); + // Adhoc variable kind export interface AdhocVariableKind { kind: "AdhocVariable"; diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index f95dfbfffca..32fbb6ea239 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -10,11 +10,11 @@ require ( github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.36.0 - k8s.io/api v0.33.1 - k8s.io/apimachinery v0.33.1 - k8s.io/apiserver v0.33.1 - k8s.io/client-go v0.33.1 - k8s.io/component-base v0.33.1 + k8s.io/api v0.33.2 + k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.33.2 + k8s.io/client-go v0.33.2 + k8s.io/component-base v0.33.2 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff sigs.k8s.io/structured-merge-diff/v4 v4.6.0 diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index d7688a0f0ea..764d40015f1 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -520,16 +520,16 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= 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= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 2155cda748e..a69b61d33ff 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -7,8 +7,8 @@ require ( github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 - k8s.io/apimachinery v0.33.1 - k8s.io/apiserver v0.33.1 + k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.33.2 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 0c3fcf26b46..e7cb5aa2cd0 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -166,10 +166,10 @@ 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/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= 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= diff --git a/pkg/apis/secret/go.mod b/pkg/apis/secret/go.mod index 78c2b9a78fa..ba65bd5f9c9 100644 --- a/pkg/apis/secret/go.mod +++ b/pkg/apis/secret/go.mod @@ -8,8 +8,8 @@ require ( google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.33.1 - k8s.io/apiserver v0.33.1 + k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.33.2 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 ) @@ -86,9 +86,9 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/api v0.33.1 // indirect - k8s.io/client-go v0.33.1 // indirect - k8s.io/component-base v0.33.1 // indirect + k8s.io/api v0.33.2 // indirect + k8s.io/client-go v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/pkg/apis/secret/go.sum b/pkg/apis/secret/go.sum index 6ff9117cba7..e8ea9a27592 100644 --- a/pkg/apis/secret/go.sum +++ b/pkg/apis/secret/go.sum @@ -319,16 +319,16 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= 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= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index b8a14bc172b..43b95bee29c 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,16 +5,16 @@ go 1.24.4 require ( github.com/google/go-cmp v0.7.0 github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d - github.com/grafana/grafana-app-sdk/logging v0.38.2 + github.com/grafana/grafana-app-sdk/logging v0.39.1 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel/trace v1.36.0 - k8s.io/apimachinery v0.33.1 - k8s.io/apiserver v0.33.1 - k8s.io/component-base v0.33.1 + k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.33.2 + k8s.io/component-base v0.33.2 k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 sigs.k8s.io/structured-merge-diff/v4 v4.6.0 @@ -98,8 +98,8 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.1 // indirect - k8s.io/client-go v0.33.1 // indirect + k8s.io/api v0.33.2 // indirect + k8s.io/client-go v0.33.2 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 3771d878851..0b7f20d4879 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -84,8 +84,8 @@ github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAh github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/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/logging v0.38.2 h1:EdQTRxbbH72zdqJ09Z76zcSjfALJXkpPLgvKEPPnloc= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= 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/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -364,16 +364,16 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= 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= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index d7ad56620cf..5e0af708098 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -6,7 +6,7 @@ require ( cuelang.org/go v0.11.1 github.com/dave/dst v0.27.3 github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d - github.com/grafana/cog v0.0.35 + github.com/grafana/cog v0.0.36 github.com/grafana/cuetsy v0.1.11 github.com/matryer/is v1.4.1 golang.org/x/tools v0.34.0 diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index f63bc60ac46..8196c0310c9 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -31,8 +31,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.35 h1:ZrGca+X17lczqhuXVCxHbLyGszP8VHGh1eAQLxbplkY= -github.com/grafana/cog v0.0.35/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc= +github.com/grafana/cog v0.0.36 h1:5kijtkHRzabhCG+ck4sCAEtYp2luaBK8v1CrUHcQ0Es= +github.com/grafana/cog v0.0.36/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws= github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk= diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index 61c1a32c6cf..f2f2a420e22 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -7,7 +7,7 @@ replace github.com/grafana/grafana/pkg/codegen => ../../codegen require ( cuelang.org/go v0.11.1 github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d - github.com/grafana/cog v0.0.35 + github.com/grafana/cog v0.0.36 github.com/grafana/cuetsy v0.1.11 github.com/grafana/grafana/pkg/codegen v0.0.0-20250514132646-acbc7b54ed9e ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index b4b6d753637..1771a71bf9b 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -30,8 +30,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.35 h1:ZrGca+X17lczqhuXVCxHbLyGszP8VHGh1eAQLxbplkY= -github.com/grafana/cog v0.0.35/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc= +github.com/grafana/cog v0.0.36 h1:5kijtkHRzabhCG+ck4sCAEtYp2luaBK8v1CrUHcQ0Es= +github.com/grafana/cog v0.0.36/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc= github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk= github.com/grafana/cuetsy v0.1.11/go.mod h1:Ix97+CPD8ws9oSSxR3/Lf4ahU1I4Np83kjJmDVnLZvc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index afcd65eb147..9fc651efc6f 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -13,7 +13,7 @@ require ( go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel/trace v1.36.0 google.golang.org/protobuf v1.36.6 - k8s.io/apimachinery v0.33.1 + k8s.io/apimachinery v0.33.2 ) require ( @@ -128,7 +128,7 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.1 // indirect + k8s.io/client-go v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 7c31bdb56ee..dce119285c9 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -430,10 +430,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= 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= diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json index 2f4dafbaa16..a5262d3e078 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json @@ -1250,6 +1250,7 @@ "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec": { "type": "object", "required": [ + "query", "enable", "hide", "iconColor", @@ -1259,9 +1260,6 @@ "builtIn": { "type": "boolean" }, - "datasource": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" - }, "enable": { "type": "boolean", "default": false @@ -1289,7 +1287,12 @@ "default": "" }, "query": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" + } + ] } } }, @@ -1865,11 +1868,24 @@ "type": "object", "required": [ "kind", + "group", + "version", "spec" ], "properties": { + "datasource": { + "description": "New type for datasource reference Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1DataQueryKindDatasource" + } + ] + }, + "group": { + "type": "string", + "default": "" + }, "kind": { - "description": "The kind of a DataQueryKind is the datasource type", "type": "string", "default": "" }, @@ -1878,10 +1894,15 @@ "additionalProperties": { "type": "object" } + }, + "version": { + "type": "string", + "default": "" } } }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef": { + "description": "Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec This type is widely used in the codebase and changing it will have a big impact", "type": "object", "properties": { "type": { @@ -2743,9 +2764,6 @@ "hidden" ], "properties": { - "datasource": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" - }, "hidden": { "type": "boolean", "default": false @@ -2993,9 +3011,6 @@ } ] }, - "datasource": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" - }, "definition": { "type": "string" }, @@ -3725,6 +3740,14 @@ } } }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1DataQueryKindDatasource": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": { "type": "object", "required": [ diff --git a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go index 861924839ec..598accb4794 100644 --- a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go @@ -13,6 +13,7 @@ package dataquery import ( json "encoding/json" + fmt "fmt" ) type AzureMonitorQuery struct { @@ -835,3 +836,58 @@ type StringOrBoolOrFloat64OrSelectableValue struct { func NewStringOrBoolOrFloat64OrSelectableValue() *StringOrBoolOrFloat64OrSelectableValue { return &StringOrBoolOrFloat64OrSelectableValue{} } + +// MarshalJSON implements a custom JSON marshalling logic to encode `StringOrBoolOrFloat64OrSelectableValue` as JSON. +func (resource StringOrBoolOrFloat64OrSelectableValue) MarshalJSON() ([]byte, error) { + if resource.String != nil { + return json.Marshal(resource.String) + } + if resource.Bool != nil { + return json.Marshal(resource.Bool) + } + if resource.Float64 != nil { + return json.Marshal(resource.Float64) + } + if resource.SelectableValue != nil { + return json.Marshal(resource.SelectableValue) + } + + return []byte("null"), nil +} + +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode StringOrBoolOrFloat64OrSelectableValue from JSON. +func (resource *StringOrBoolOrFloat64OrSelectableValue) UnmarshalJSON(raw []byte) error { + if raw == nil { + return nil + } + fields := make(map[string]json.RawMessage) + if err := json.Unmarshal(raw, &fields); err != nil { + return err + } + + if fields["String"] != nil { + if err := json.Unmarshal(fields["String"], &resource.String); err != nil { + return fmt.Errorf("error decoding field 'String': %w", err) + } + } + + if fields["Bool"] != nil { + if err := json.Unmarshal(fields["Bool"], &resource.Bool); err != nil { + return fmt.Errorf("error decoding field 'Bool': %w", err) + } + } + + if fields["Float64"] != nil { + if err := json.Unmarshal(fields["Float64"], &resource.Float64); err != nil { + return fmt.Errorf("error decoding field 'Float64': %w", err) + } + } + + if fields["SelectableValue"] != nil { + if err := json.Unmarshal(fields["SelectableValue"], &resource.SelectableValue); err != nil { + return fmt.Errorf("error decoding field 'SelectableValue': %w", err) + } + } + + return nil +} diff --git a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go index 0057d07e182..76c706094a6 100644 --- a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go @@ -211,6 +211,12 @@ func NewQueryEditorProperty() *QueryEditorProperty { } } +type QueryEditorPropertyType string + +const ( + QueryEditorPropertyTypeString QueryEditorPropertyType = "string" +) + type QueryEditorArrayExpression struct { Type QueryEditorArrayExpressionType `json:"type"` Expressions ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression `json:"expressions"` @@ -279,12 +285,6 @@ func NewQueryEditorOperatorValueType() *QueryEditorOperatorValueType { return NewStringOrBoolOrInt64OrArrayOfQueryEditorOperatorType() } -type QueryEditorPropertyType string - -const ( - QueryEditorPropertyTypeString QueryEditorPropertyType = "string" -) - type LogsQueryLanguage string const ( diff --git a/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go index ecd4d038beb..a53802ad738 100644 --- a/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go @@ -13,6 +13,7 @@ package dataquery import ( json "encoding/json" + fmt "fmt" ) type BucketAggregation = DateHistogramOrHistogramOrTermsOrFiltersOrGeoHashGridOrNested @@ -1549,6 +1550,43 @@ func NewStringOrDataqueryInlineScript() *StringOrDataqueryInlineScript { return &StringOrDataqueryInlineScript{} } +// MarshalJSON implements a custom JSON marshalling logic to encode `StringOrDataqueryInlineScript` as JSON. +func (resource StringOrDataqueryInlineScript) MarshalJSON() ([]byte, error) { + if resource.String != nil { + return json.Marshal(resource.String) + } + if resource.DataqueryInlineScript != nil { + return json.Marshal(resource.DataqueryInlineScript) + } + + return []byte("null"), nil +} + +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode StringOrDataqueryInlineScript from JSON. +func (resource *StringOrDataqueryInlineScript) UnmarshalJSON(raw []byte) error { + if raw == nil { + return nil + } + fields := make(map[string]json.RawMessage) + if err := json.Unmarshal(raw, &fields); err != nil { + return err + } + + if fields["String"] != nil { + if err := json.Unmarshal(fields["String"], &resource.String); err != nil { + return fmt.Errorf("error decoding field 'String': %w", err) + } + } + + if fields["DataqueryInlineScript"] != nil { + if err := json.Unmarshal(fields["DataqueryInlineScript"], &resource.DataqueryInlineScript); err != nil { + return fmt.Errorf("error decoding field 'DataqueryInlineScript': %w", err) + } + } + + return nil +} + type BucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics struct { BucketScript *BucketScript `json:"BucketScript,omitempty"` CumulativeSum *CumulativeSum `json:"CumulativeSum,omitempty"` diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 287140082c7..a38fe4fb66f 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -37,6 +37,39 @@ jest.mock('@grafana/runtime', () => { ...original.config.featureToggles, dashboardNewLayouts: false, // Default value }, + + bootData: { + ...original.config.bootData, + settings: { + ...original.config.bootData.settings, + datasources: { + 'gdev-testdata': { + id: 7, + uid: 'abc', + type: 'grafana-testdata-datasource', + name: 'gdev-testdata', + meta: { + id: 'grafana-testdata-datasource', + type: 'datasource', + name: 'TestData', + aliasIDs: ['testdata'], + }, + }, + '-- Grafana --': { + id: -1, + uid: 'grafana', + type: 'datasource', + name: '-- Grafana --', + meta: { + id: 'grafana', + type: 'datasource', + name: '-- Grafana --', + }, + }, + }, + defaultDatasource: 'gdev-testdata', + }, + }, }, }; }); @@ -1588,9 +1621,11 @@ const v2ProvisionedDashboardResource = { kind: 'AnnotationQuery', spec: { builtIn: true, - datasource: { - type: 'grafana', - uid: '-- Grafana --', + query: { + kind: 'DataQuery', + group: 'grafana', + spec: {}, + version: 'v0', }, enable: true, hide: true, @@ -1613,13 +1648,14 @@ const v2ProvisionedDashboardResource = { { kind: 'PanelQuery', spec: { - datasource: { - type: 'grafana-testdata-datasource', - uid: 'PD8C576611E62080A', - }, hidden: false, query: { - kind: 'grafana-testdata-datasource', + kind: 'DataQuery', + group: 'grafana-testdata-datasource', + version: 'v0', + datasource: { + name: 'PD8C576611E62080A', + }, spec: { scenarioId: 'random_walk', seriesCount: 2, diff --git a/public/app/features/dashboard-scene/scene/export/exporters.test.ts b/public/app/features/dashboard-scene/scene/export/exporters.test.ts index 9f9028e2c41..90b71e9c044 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.test.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.test.ts @@ -549,13 +549,12 @@ describe('dashboard exporter v2', () => { { kind: 'PanelQuery', spec: { - datasource: { - type: 'prometheus', - uid: '${datasourceVar}', - }, hidden: false, query: { - kind: 'prometheus', + datasource: { + name: '${datasourceVar}', + }, + group: 'prometheus', spec: { editorMode: 'builder', expr: 'go_goroutines{job="prometheus"}', @@ -583,7 +582,7 @@ describe('dashboard exporter v2', () => { it('should replace datasource in a query variable', async () => { const { dashboard } = await setup(); const variable = dashboard.variables[0] as QueryVariableKind; - expect(variable.spec.datasource?.uid).toBeUndefined(); + expect(variable.spec.query.datasource?.name).toBeUndefined(); }); it('do not expose datasource name and id in datasource variable', async () => { @@ -597,7 +596,7 @@ describe('dashboard exporter v2', () => { const { dashboard } = await setup(); const annotationQuery = dashboard.annotations[0]; - expect(annotationQuery.spec.datasource?.uid).toBeUndefined(); + expect(annotationQuery.spec.query?.datasource?.name).toBeUndefined(); }); it('should remove library panels from layout', async () => { @@ -616,11 +615,8 @@ describe('dashboard exporter v2', () => { if (panel.kind !== 'Panel') { throw new Error('Panel should be a Panel'); } - - expect(panel.spec.data.spec.queries[0].spec.datasource).toEqual({ - type: 'prometheus', - uid: '${datasourceVar}', - }); + expect(panel.spec.data.spec.queries[0].spec.query.datasource?.name).toBe('${datasourceVar}'); + expect(panel.spec.data.spec.queries[0].spec.query.group).toBe('prometheus'); }); }); diff --git a/public/app/features/dashboard-scene/scene/export/exporters.ts b/public/app/features/dashboard-scene/scene/export/exporters.ts index 4ef9f5a4cbd..7dfc76eea96 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.ts @@ -344,7 +344,8 @@ export async function makeExportableV2(dashboard: DashboardV2Spec) { const removeDataSourceRefs = ( obj: AnnotationQueryKind['spec'] | QueryVariableKind['spec'] | PanelQueryKind['spec'] ) => { - const datasourceUid = obj.datasource?.uid; + const datasourceUid = obj.query?.datasource?.name; + if (datasourceUid?.startsWith('${') && datasourceUid?.endsWith('}')) { const varName = datasourceUid.slice(2, -1); // if there's a match we don't want to remove the datasource ref @@ -354,7 +355,7 @@ export async function makeExportableV2(dashboard: DashboardV2Spec) { } } - obj.datasource = undefined; + obj.query && (obj.query.datasource = undefined); }; const processPanel = (panel: PanelKind) => { diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index 0e39748c270..ab99200417d 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -10,6 +10,7 @@ import { Dashboard, VariableModel } from '@grafana/schema'; import { Spec as DashboardV2Spec, defaultSpec as defaultDashboardV2Spec, + defaultDataQueryKind, defaultPanelSpec, defaultTimeSettingsSpec, GridLayoutKind, @@ -723,9 +724,14 @@ describe('DashboardSceneSerializer', () => { spec: { builtIn: true, name: 'Annotations & Alerts', - datasource: { - uid: '-- Grafana --', - type: 'grafana', + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana', + datasource: { + name: '-- Grafana --', + }, + spec: {}, }, enable: true, hide: true, @@ -1014,7 +1020,7 @@ describe('DashboardSceneSerializer', () => { refId: 'A', hidden: false, // No datasource defined - query: { kind: 'sql', spec: {} }, + query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'sql', spec: {} }, }, }, { @@ -1022,8 +1028,15 @@ describe('DashboardSceneSerializer', () => { spec: { refId: 'B', hidden: false, - datasource: { uid: 'datasource-1', type: 'prometheus' }, - query: { kind: 'prometheus', spec: {} }, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'datasource-1', + }, + spec: {}, + }, }, }, ], @@ -1058,7 +1071,7 @@ describe('DashboardSceneSerializer', () => { refId: 'C', hidden: false, // No datasource defined - query: { kind: 'sql', spec: {} }, + query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'sql', spec: {} }, }, }, ], @@ -1106,7 +1119,7 @@ describe('DashboardSceneSerializer', () => { kind: 'AnnotationQuery', spec: { name: 'Annotation 1', - query: { kind: 'prometheus', spec: {} }, + query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'prometheus', spec: {} }, enable: true, hide: false, iconColor: 'red', diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts index 121a3a4cca2..e96284c1008 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts @@ -288,7 +288,7 @@ export class V2DashboardSerializer const panelQueries = elementPanel.spec.data.spec.queries; for (const query of panelQueries) { - if (!query.spec.datasource) { + if (!query.spec.query.datasource?.name) { const elementId = this.getElementIdForPanel(elementPanel.spec.id); if (!this.defaultDsReferencesMap.panels.has(elementId)) { this.defaultDsReferencesMap.panels.set(elementId, new Set()); @@ -306,7 +306,7 @@ export class V2DashboardSerializer if (saveModel?.variables) { for (const variable of saveModel.variables) { // for query variables that dont have a ds defined add them to the list - if (variable.kind === 'QueryVariable' && !variable.spec.datasource) { + if (variable.kind === 'QueryVariable' && !variable.spec.query.datasource?.name) { this.defaultDsReferencesMap.variables.add(variable.spec.name); } } @@ -315,7 +315,7 @@ export class V2DashboardSerializer // initialize annotations ds references map if (saveModel?.annotations) { for (const annotation of saveModel.annotations) { - if (!annotation.spec.datasource) { + if (!annotation.spec.query?.datasource?.name) { this.defaultDsReferencesMap.annotations.add(annotation.spec.name); } } diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap index cc3a40896c7..420a02a228a 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -7,38 +7,38 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "kind": "AnnotationQuery", "spec": { "builtIn": false, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --", - }, "enable": true, "hide": false, "iconColor": "red", "name": "query1", + "query": { + "datasource": { + "name": "-- Grafana --", + }, + "group": "grafana", + "kind": "DataQuery", + "spec": {}, + "version": "v0", + }, }, }, { "kind": "AnnotationQuery", "spec": { "builtIn": false, - "datasource": { - "type": "prometheus", - "uid": "abcdef", - }, "enable": true, "hide": true, "iconColor": "blue", "name": "query2", - }, - }, - { - "kind": "AnnotationQuery", - "spec": { - "builtIn": false, - "enable": true, - "hide": true, - "iconColor": "green", - "name": "query3", + "query": { + "datasource": { + "name": "abcdef", + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": {}, + "version": "v0", + }, }, }, ], @@ -152,10 +152,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "text": "text1", "value": "value1", }, - "datasource": { - "type": "prometheus", - "uid": "datasource1", - }, "definition": "definition1", "description": "A query variable", "hide": "hideLabel", @@ -165,11 +161,16 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "name": "queryVar", "options": [], "query": { - "kind": "prometheus", + "datasource": { + "name": "datasource1", + }, + "group": "prometheus", + "kind": "DataQuery", "spec": { "expr": "label_values(node_boot_time_seconds)", "refId": "A", }, + "version": "v0", }, "refresh": "onDashboardLoad", "regex": "regex1", diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts index b5f9950b5f6..2c71a63a03d 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts @@ -1,4 +1,7 @@ -import { PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { + defaultDataQueryKind, + PanelQueryKind, +} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { getRuntimePanelDataSource } from './utils'; @@ -29,6 +32,12 @@ jest.mock('@grafana/runtime', () => ({ meta: { id: 'loki' }, type: 'datasource', }, + '-- Grafana --': { + uid: 'grafana', + name: 'Grafana', + meta: { id: 'grafana' }, + type: 'datasource', + }, }, }, }, @@ -42,22 +51,23 @@ describe('getRuntimePanelDataSource', () => { spec: { refId: 'A', hidden: false, - datasource: { - uid: 'test-ds-uid', - type: 'test-ds-type', - }, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'prometheus-uid', + }, spec: {}, }, }, }; - const result = getRuntimePanelDataSource(query); + const result = getRuntimePanelDataSource(query.spec.query); expect(result).toEqual({ - uid: 'test-ds-uid', - type: 'test-ds-type', + uid: 'prometheus-uid', + type: 'prometheus', }); }); @@ -67,15 +77,16 @@ describe('getRuntimePanelDataSource', () => { spec: { refId: 'A', hidden: false, - datasource: undefined, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', spec: {}, }, }, }; - const result = getRuntimePanelDataSource(query); + const result = getRuntimePanelDataSource(query.spec.query); expect(result).toEqual({ uid: 'default-prometheus-uid', @@ -89,15 +100,16 @@ describe('getRuntimePanelDataSource', () => { spec: { refId: 'A', hidden: false, - datasource: undefined, query: { - kind: 'loki', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'loki', spec: {}, }, }, }; - const result = getRuntimePanelDataSource(query); + const result = getRuntimePanelDataSource(query.spec.query); expect(result).toEqual({ uid: 'loki-uid', @@ -112,15 +124,16 @@ describe('getRuntimePanelDataSource', () => { spec: { refId: 'A', hidden: false, - datasource: undefined, query: { - kind: 'unknown-type', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'unknown-type', spec: {}, }, }, }; - const result = getRuntimePanelDataSource(query); + const result = getRuntimePanelDataSource(query.spec.query); expect(result).toEqual({ uid: 'default-prometheus-uid', @@ -138,18 +151,19 @@ describe('getRuntimePanelDataSource', () => { spec: { refId: 'A', hidden: false, - datasource: { - uid: '', - type: 'test-ds-type', - }, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: '', + }, spec: {}, }, }, }; - const result = getRuntimePanelDataSource(query); + const result = getRuntimePanelDataSource(query.spec.query); expect(result).toEqual({ uid: 'default-prometheus-uid', diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index c8018a98be0..0f8e0750083 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -19,6 +19,7 @@ import { PanelQueryKind, QueryVariableKind, TabsLayoutTabKind, + DataQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; @@ -185,12 +186,15 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { panel.spec.data.spec.queries.forEach((query) => { if (!datasource) { - if (!query.spec.datasource?.uid) { - datasource = getRuntimePanelDataSource(query); + if (!query.spec.query.datasource?.name) { + datasource = getRuntimePanelDataSource(query.spec.query); } else { - datasource = query.spec.datasource; + datasource = { + uid: query.spec.query.datasource?.name, + type: query.spec.query.group, + }; } - } else if (datasource.uid !== query.spec.datasource?.uid || datasource.type !== query.spec.datasource?.type) { + } else if (datasource.uid !== query.spec.query.datasource?.name || datasource.type !== query.spec.query.group) { isMixedDatasource = true; } }); @@ -199,11 +203,19 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { } export function getRuntimeVariableDataSource(variable: QueryVariableKind): DataSourceRef | undefined { - return getDataSourceForQuery(variable.spec.datasource, variable.spec.query.kind); + const ds: DataSourceRef = { + uid: variable.spec.query.datasource?.name, + type: variable.spec.query.group, + }; + return getDataSourceForQuery(ds, variable.spec.query.group); } -export function getRuntimePanelDataSource(query: PanelQueryKind): DataSourceRef | undefined { - return getDataSourceForQuery(query.spec.datasource, query.spec.query.kind); +export function getRuntimePanelDataSource(query: DataQueryKind): DataSourceRef { + const ds: DataSourceRef = { + uid: query.datasource?.name, + type: query.group, + }; + return getDataSourceForQuery(ds, query.group); } /** @@ -211,10 +223,7 @@ export function getRuntimePanelDataSource(query: PanelQueryKind): DataSourceRef * @param queryKind - The kind of query being performed * @returns The resolved DataSourceRef */ -function getDataSourceForQuery( - querySpecDS: DataSourceRef | undefined | null, - queryKind: string -): DataSourceRef | undefined { +function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | null, queryKind: string): DataSourceRef { // If datasource is specified and has a uid, use it if (querySpecDS?.uid) { return querySpecDS; @@ -253,14 +262,22 @@ function getDataSourceForQuery( }; } - // If we don't find a default datasource, return undefined - return undefined; + if (dsList && !dsList[defaultDatasource]) { + throw new Error(`Default datasource ${defaultDatasource} not found in datasource list`); + } + + // In the datasource list from bootData "id" is the type and the uid could be uid or the name + // in cases like grafana, dashboard or mixed datasource + return { + uid: dsList[defaultDatasource].uid || dsList[defaultDatasource].name, + type: dsList[defaultDatasource].meta.id, + }; } function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { return { refId: query.spec.refId, - datasource: getRuntimePanelDataSource(query), + datasource: getRuntimePanelDataSource(query.spec.query), hide: query.spec.hidden, ...query.spec.query.spec, }; diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 72cc20f2943..701d992d3b1 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -45,8 +45,8 @@ const getDataSourceMock = jest.fn(); const fakeDsMock: DataSourceApi = { name: 'fake-std', - type: 'fake-std', - getRef: () => ({ type: 'fake-std', uid: 'fake-std' }), + type: 'fake-type', + getRef: () => ({ type: 'fake-type', uid: 'fake-uid' }), query: () => Promise.resolve({ data: [], @@ -74,7 +74,7 @@ const fakeDsMock: DataSourceApi = { toDataQuery: (q) => ({ ...q, refId: 'FakeDataSource-refId' }), }, id: 1, - uid: 'fake-std', + uid: 'fake-uid', }; jest.mock('@grafana/runtime', () => ({ @@ -95,7 +95,7 @@ describe('sceneVariablesSetToVariables', () => { description: 'test-desc', value: ['selected-value'], text: ['selected-value-text'], - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, query: 'query', includeAll: true, allowCustomValue: true, @@ -123,8 +123,8 @@ describe('sceneVariablesSetToVariables', () => { ], }, "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "definition": undefined, "description": "test-desc", @@ -148,7 +148,7 @@ describe('sceneVariablesSetToVariables', () => { description: 'test-desc', value: ['selected-value'], text: ['selected-value-text'], - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, query: 'query', definition: 'query', includeAll: true, @@ -176,8 +176,8 @@ describe('sceneVariablesSetToVariables', () => { ], }, "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "definition": "query", "description": "test-desc", @@ -201,7 +201,7 @@ describe('sceneVariablesSetToVariables', () => { description: 'test-desc', value: ['selected-value'], text: ['selected-value-text'], - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, query: 'query', options: [ { label: 'test', value: 'test' }, @@ -228,7 +228,7 @@ describe('sceneVariablesSetToVariables', () => { description: 'test-desc', value: ['test'], text: ['test'], - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, query: 'query', options: [ { label: 'test', value: 'test' }, @@ -499,7 +499,7 @@ describe('sceneVariablesSetToVariables', () => { allowCustomValue: true, label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, filters: [ { key: 'filterTest', @@ -533,8 +533,8 @@ describe('sceneVariablesSetToVariables', () => { }, ], "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultKeys": undefined, "description": "test-desc", @@ -662,7 +662,7 @@ describe('sceneVariablesSetToVariables', () => { allowCustomValue: true, label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultKeys: [ { text: 'some', @@ -710,8 +710,8 @@ describe('sceneVariablesSetToVariables', () => { }, ], "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultKeys": [ { @@ -757,7 +757,7 @@ describe('sceneVariablesSetToVariables', () => { label: 'test-label', description: 'test-desc', allowCustomValue: true, - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultOptions: [ { text: 'Foo', @@ -784,8 +784,8 @@ describe('sceneVariablesSetToVariables', () => { "value": [], }, "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultValue": undefined, "description": "test-desc", @@ -813,7 +813,7 @@ describe('sceneVariablesSetToVariables', () => { name: 'test', label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultOptions: [ { text: 'Foo', @@ -841,7 +841,7 @@ describe('sceneVariablesSetToVariables', () => { description: 'test-desc', value: ['selected-value'], text: ['selected-value-text'], - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, query: 'query', includeAll: true, allValue: 'test-all', @@ -856,44 +856,45 @@ describe('sceneVariablesSetToVariables', () => { expect(result).toHaveLength(1); expect(result[0]).toMatchInlineSnapshot(` - { - "kind": "QueryVariable", - "spec": { - "allValue": "test-all", - "allowCustomValue": true, - "current": { - "text": [ - "selected-value-text", - ], - "value": [ - "selected-value", - ], - }, - "datasource": { - "type": "fake-std", - "uid": "fake-std", - }, - "definition": undefined, - "description": "test-desc", - "hide": "dontHide", - "includeAll": true, - "label": "test-label", - "multi": true, - "name": "test", - "options": [], - "query": { - "kind": "fake-std", + { + "kind": "QueryVariable", "spec": { - "__legacyStringValue": "query", + "allValue": "test-all", + "allowCustomValue": true, + "current": { + "text": [ + "selected-value-text", + ], + "value": [ + "selected-value", + ], + }, + "definition": undefined, + "description": "test-desc", + "hide": "dontHide", + "includeAll": true, + "label": "test-label", + "multi": true, + "name": "test", + "options": [], + "query": { + "datasource": { + "name": "fake-uid", + }, + "group": "fake-type", + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "query", + }, + "version": "v0", + }, + "refresh": "onDashboardLoad", + "regex": "", + "skipUrlSync": false, + "sort": "disabled", }, - }, - "refresh": "onDashboardLoad", - "regex": "", - "skipUrlSync": false, - "sort": "disabled", - }, - } - `); + } + `); }); it('should handle CustomVariable', () => { @@ -1152,7 +1153,7 @@ describe('sceneVariablesSetToVariables', () => { name: 'test', label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, filters: [ { key: 'filterTest', @@ -1188,8 +1189,8 @@ describe('sceneVariablesSetToVariables', () => { }, ], "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultKeys": [], "description": "test-desc", @@ -1214,7 +1215,7 @@ describe('sceneVariablesSetToVariables', () => { name: 'test', label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultKeys: [ { text: 'some', @@ -1264,8 +1265,8 @@ describe('sceneVariablesSetToVariables', () => { }, ], "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultKeys": [ { @@ -1312,7 +1313,7 @@ describe('sceneVariablesSetToVariables', () => { name: 'test', label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultOptions: [ { text: 'Foo', @@ -1340,8 +1341,8 @@ describe('sceneVariablesSetToVariables', () => { "value": [], }, "datasource": { - "type": "fake-std", - "uid": "fake-std", + "type": "fake-type", + "uid": "fake-uid", }, "defaultValue": undefined, "description": "test-desc", @@ -1372,7 +1373,7 @@ describe('sceneVariablesSetToVariables', () => { name: 'test', label: 'test-label', description: 'test-desc', - datasource: { uid: 'fake-std', type: 'fake-std' }, + datasource: { uid: 'fake-uid', type: 'fake-type' }, defaultOptions: [ { text: 'Foo', diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index 74304a2a9a6..c2d602d6c8f 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -24,6 +24,7 @@ import { GroupByVariableKind, defaultVariableHide, VariableOption, + defaultDataQueryKind, AdHocFilterWithLabels, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; @@ -294,14 +295,30 @@ export function sceneVariablesSetToSchemaV2Variables( } const query = variable.state.query; let dataQuery: DataQueryKind | string; + const datasource = getElementDatasource(set, variable, 'variable', undefined, dsReferencesMapping); + if (typeof query !== 'string') { dataQuery = { - kind: variable.state.datasource?.type ?? getDataQueryKind(query), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: datasource?.type ?? getDataQueryKind(query), + ...(datasource?.uid && { + datasource: { + name: datasource.uid, + }, + }), spec: getDataQuerySpec(query), }; } else { dataQuery = { - kind: variable.state.datasource?.type ?? getDataQueryKind(query), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: datasource?.type ?? getDataQueryKind(query), + ...(datasource?.uid && { + datasource: { + name: datasource.uid, + }, + }), spec: { [LEGACY_STRING_VALUE_KEY]: query, }, @@ -315,7 +332,6 @@ export function sceneVariablesSetToSchemaV2Variables( options, query: dataQuery, definition: variable.state.definition, - datasource: getElementDatasource(set, variable, 'variable', undefined, dsReferencesMapping), sort: transformSortVariableToEnum(variable.state.sort), refresh: transformVariableRefreshToEnum(variable.state.refresh), regex: variable.state.regex, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index ce31ae57d0e..c78cbd4c480 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -22,6 +22,7 @@ import { CustomVariableKind, Spec as DashboardV2Spec, DatasourceVariableKind, + defaultDataQueryKind, GridLayoutItemSpec, GridLayoutSpec, GroupByVariableKind, @@ -138,6 +139,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { // Variables const variables = scene.state?.$variables; expect(variables?.state.variables).toHaveLength(dash.variables.length); + validateVariable({ sceneVariable: variables?.state.variables[0], variableKind: dash.variables[0] as QueryVariableKind, @@ -206,36 +208,26 @@ describe('transformSaveModelSchemaV2ToScene', () => { // Annotations expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); const dataLayers = scene.state.$data as DashboardDataLayerSet; - // we should get two annotations, Grafana built-in and the custom ones expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length); - expect(dataLayers.state.annotationLayers).toHaveLength(5); - - // Built-in - const builtInAnnotation = dataLayers.state.annotationLayers[0] as unknown as DashboardAnnotationsDataLayer; - expect(builtInAnnotation.state.name).toBe('Annotations & Alerts'); - expect(builtInAnnotation.state.isEnabled).toBe(true); - expect(builtInAnnotation.state.isHidden).toBe(true); - expect(builtInAnnotation.state?.query.builtIn).toBe(1); + expect(dataLayers.state.annotationLayers[0].state.name).toBe(dash.annotations[0].spec.name); + expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(dash.annotations[0].spec.enable); + expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(dash.annotations[0].spec.hide); // Enabled expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name); expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable); expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide); + // Disabled expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name); expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable); expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide); - // Disabled + // Hidden expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name); expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable); expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide); - // Hidden - expect(dataLayers.state.annotationLayers[4].state.name).toBe(dash.annotations[4].spec.name); - expect(dataLayers.state.annotationLayers[4].state.isEnabled).toBe(dash.annotations[4].spec.enable); - expect(dataLayers.state.annotationLayers[4].state.isHidden).toBe(dash.annotations[4].spec.hide); - // VizPanel const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); expect(vizPanels).toHaveLength(2); @@ -279,13 +271,14 @@ describe('transformSaveModelSchemaV2ToScene', () => { kind: 'PanelQuery', spec: { refId: 'A', - datasource: { - type: 'graphite', - uid: 'datasource1', - }, hidden: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'graphite', + datasource: { + name: 'datasource1', + }, spec: { expr: 'test-query', }, @@ -307,13 +300,14 @@ describe('transformSaveModelSchemaV2ToScene', () => { kind: 'PanelQuery', spec: { refId: 'A', - datasource: { - type: 'prometheus', - uid: 'datasource1', - }, hidden: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'datasource1', + }, spec: { expr: 'test-query', }, @@ -340,7 +334,12 @@ describe('transformSaveModelSchemaV2ToScene', () => { refId: 'A', hidden: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'abc123', + }, spec: { expr: 'test-query', }, @@ -722,10 +721,6 @@ describe('transformSaveModelSchemaV2ToScene', () => { enable: true, hide: false, iconColor: 'purple', - datasource: { - type: 'prometheus', - uid: 'abc123', - }, legacyOptions: { expr: 'rate(http_requests_total[5m])', queryType: 'range', @@ -733,6 +728,15 @@ describe('transformSaveModelSchemaV2ToScene', () => { useValueAsTime: true, step: '1m', }, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'abc123', + }, + spec: {}, + }, }, }, ], @@ -764,14 +768,36 @@ describe('transformSaveModelSchemaV2ToScene', () => { // Get the annotation layers const dataLayerSet = scene.state.$data as DashboardDataLayerSet; expect(dataLayerSet).toBeDefined(); - // it should have two annotation layers, built-in and custom expect(dataLayerSet.state.annotationLayers.length).toBe(2); + const defaultAnnotationLayer = dataLayerSet.state.annotationLayers[0] as DashboardAnnotationsDataLayer; + + // Verify that the default annotation layer has been correctly initialized + expect(defaultAnnotationLayer.state.query).toEqual({ + datasource: { + uid: '-- Grafana --', + type: 'grafana', + }, + builtIn: 1, + enable: true, + iconColor: 'rgba(0, 211, 255, 1)', + name: 'Annotations & Alerts', + filter: undefined, + hide: true, + }); const annotationLayer = dataLayerSet.state.annotationLayers[1] as DashboardAnnotationsDataLayer; // Verify that the legacyOptions have been merged into the query object expect(annotationLayer.state.query).toMatchObject({ + datasource: { + uid: 'abc123', + type: 'prometheus', + }, name: 'Annotation with legacy options', + builtIn: 0, + enable: true, + hide: false, + iconColor: 'purple', expr: 'rate(http_requests_total[5m])', queryType: 'range', legendFormat: '{{method}} {{endpoint}}', diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 10dbc1d11e9..5d73b6cab9e 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -1,5 +1,6 @@ import { uniqueId } from 'lodash'; +import { AnnotationQuery } from '@grafana/data'; import { config, getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable, @@ -28,6 +29,7 @@ import { defaultAdhocVariableKind, defaultConstantVariableKind, defaultCustomVariableKind, + defaultDataQueryKind, defaultDatasourceVariableKind, defaultGroupByVariableKind, defaultIntervalVariableKind, @@ -64,7 +66,7 @@ import { getIntervalsFromQueryString } from '../utils/utils'; import { SnapshotVariable } from './custom-variables/SnapshotVariable'; import { layoutDeserializerRegistry } from './layoutSerializers/layoutSerializerRegistry'; -import { getRuntimeVariableDataSource } from './layoutSerializers/utils'; +import { getRuntimePanelDataSource, getRuntimeVariableDataSource } from './layoutSerializers/utils'; import { registerPanelInteractionsReporter } from './transformSaveModelToScene'; import { transformCursorSyncV2ToV1, @@ -97,25 +99,46 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo { - let annoQuerySpec = annotation.spec; + let { query: dataQuery, ...annotationQuery } = annotation.spec; + + // Mapping from AnnotationQueryKind to AnnotationQuery used by scenes. + let annoQuerySpec: AnnotationQuery = { + builtIn: annotation.spec.builtIn ? 1 : 0, + enable: annotation.spec.enable, + iconColor: annotation.spec.iconColor, + name: annotation.spec.name, + filter: annotation.spec.filter, + hide: annotation.spec.hide, + ...dataQuery?.spec, + }; + // some annotations will contain in the legacyOptions properties that need to be // added to the root level annotation spec - if (annoQuerySpec?.legacyOptions) { + if (annotationQuery.legacyOptions) { annoQuerySpec = { ...annoQuerySpec, - ...annoQuerySpec.legacyOptions, + ...annotationQuery.legacyOptions, + legacyOptions: { + ...annotationQuery.legacyOptions, + }, }; } - return new DashboardAnnotationsDataLayer({ + + // get data source from annotation query + const datasource = getRuntimePanelDataSource(dataQuery); + + const layerState = { key: uniqueId('annotations-'), query: { ...annoQuerySpec, - builtIn: annotation.spec.builtIn ? 1 : 0, + datasource, }, name: annotation.spec.name, isEnabled: Boolean(annotation.spec.enable), isHidden: Boolean(annotation.spec.hide), - }); + }; + + return new DashboardAnnotationsDataLayer(layerState); }); const isDashboardEditable = Boolean(dashboard.editable); @@ -514,7 +537,15 @@ function getGrafanaBuiltInAnnotationDataLayer(dashboard: DashboardV2Spec) { const grafanaBuiltAnnotation: AnnotationQueryKind = { kind: 'AnnotationQuery', spec: { - datasource: { uid: '-- Grafana --', type: 'grafana' }, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana', + datasource: { + name: '-- Grafana --', + }, + spec: {}, + }, name: 'Annotations & Alerts', iconColor: DEFAULT_ANNOTATION_COLOR, enable: true, diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 0bc40f8d9d6..ad6137da1e5 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -57,6 +57,7 @@ import { validateDashboardSchemaV2, getDataQueryKind, getAutoAssignedDSRef, + getVizPanelQueries, } from './transformSceneToSaveModelSchemaV2'; // Mock dependencies @@ -410,9 +411,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { expect(result).toMatchSnapshot(); // Check that the annotation layers are correctly transformed - expect(result.annotations).toHaveLength(3); - // Check annotation layer 3 without initial data source isn't updated with runtime default - expect(result.annotations?.[2].spec.datasource?.type).toBe(undefined); + expect(result.annotations).toHaveLength(2); }); it('should transform the minimum scene to save model schema v2', () => { @@ -834,6 +833,50 @@ describe('getElementDatasource', () => { }); }); +describe('getVizPanelQueries', () => { + it('should handle panel query datasources correctly', () => { + const queryWithDS: SceneDataQuery = { + refId: 'B', + datasource: { uid: 'prometheus-uid', type: 'prometheus' }, + }; + + const queryWithoutDS: SceneDataQuery = { + refId: 'A', + }; + + // Mock query runner + const queryRunner = new SceneQueryRunner({ + queries: [queryWithoutDS, queryWithDS], + datasource: { uid: 'default-ds', type: 'default' }, + }); + // Create test elements + const vizPanel = new VizPanel({ + key: 'panel-1', + pluginId: 'timeseries', + $data: queryRunner, + }); + + // Mock dsReferencesMapping + const dsReferencesMapping = { + panels: new Map(new Set([['panel-1', new Set(['A'])]])), + variables: new Set(), + annotations: new Set(), + }; + + const result = getVizPanelQueries(vizPanel, dsReferencesMapping); + expect(result.length).toBe(2); + expect(result[0].spec.query.kind).toBe('DataQuery'); + expect(result[0].spec.query.datasource).toBeUndefined(); // ignore datasource if it wasn't provided + expect(result[0].spec.query.group).toBe('default'); + expect(result[0].spec.query.version).toBe('v0'); + + expect(result[1].spec.query.kind).toBe('DataQuery'); + expect(result[1].spec.query.datasource?.name).toBe('prometheus-uid'); + expect(result[1].spec.query.group).toBe('prometheus'); + expect(result[1].spec.query.version).toBe('v0'); + }); +}); + function getMinimalSceneState(body: DashboardLayoutManager): Partial { return { id: 1, @@ -1065,18 +1108,6 @@ function createAnnotationLayers() { isEnabled: true, isHidden: true, }), - // this could happen if a dahboard was created from code and the datasource was not defined - new DashboardAnnotationsDataLayer({ - key: 'layer3', - query: { - name: 'query3', - enable: true, - iconColor: 'green', - }, - name: 'layer3', - isEnabled: true, - isHidden: true, - }), ]; } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index b8a0bfd3b6e..a7682c4e559 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -43,6 +43,7 @@ import { DashboardCursorSync, FieldConfig, FieldColor, + defaultDataQueryKind, } from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; @@ -250,7 +251,7 @@ function getPanelLinks(panel: VizPanel): DataLink[] { return []; } -function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] { +export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] { const queries: PanelQueryKind[] = []; const queryRunner = getQueryRunnerFor(vizPanel); const vizPanelQueries = queryRunner?.state.queries; @@ -258,12 +259,22 @@ function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferenc if (vizPanelQueries) { vizPanelQueries.forEach((query) => { const queryDatasource = getElementDatasource(vizPanel, query, 'panel', queryRunner, dsReferencesMapping); + const dataQuery: DataQueryKind = { - kind: getDataQueryKind(query, queryRunner), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: getDataQueryKind(query, queryRunner), + datasource: { + name: queryDatasource?.uid, + }, spec: omit(query, 'datasource', 'refId', 'hide'), }; + + if (!dataQuery.datasource?.name) { + delete dataQuery.datasource; + } + const querySpec: PanelQuerySpec = { - datasource: queryDatasource, query: dataQuery, refId: query.refId, hidden: Boolean(query.hide), @@ -407,18 +418,36 @@ function getAnnotations(state: DashboardSceneState, dsReferencesMapping?: DSRefe if (!(layer instanceof dataLayers.AnnotationsDataLayer)) { continue; } + const datasource = getElementDatasource(layer, layer.state.query, 'annotation', undefined, dsReferencesMapping); + + const layerDs = layer.state.query.datasource; + if (!layerDs) { + throw new Error('Misconfigured AnnotationsDataLayer: Datasource is required for annotations'); + } + const result: AnnotationQueryKind = { kind: 'AnnotationQuery', spec: { builtIn: Boolean(layer.state.query.builtIn), name: layer.state.query.name, - datasource: getElementDatasource(layer, layer.state.query, 'annotation', undefined, dsReferencesMapping), enable: Boolean(layer.state.isEnabled), hide: Boolean(layer.state.isHidden), iconColor: layer.state.query.iconColor, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: layerDs.type!, // Annotation layer has a datasource type provided in runtime. + spec: {}, + }, }, }; + if (datasource) { + result.spec.query!.datasource = { + name: datasource.uid, + }; + } + // Transform v1 dashboard (using target) to v2 structure // adds extra condition to prioritize query over target // if query is defined, use it @@ -426,24 +455,37 @@ function getAnnotations(state: DashboardSceneState, dsReferencesMapping?: DSRefe // Handle built-in annotations if (layer.state.query.builtIn) { result.spec.query = { - kind: 'grafana', // built-in annotations are always of type grafana + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana', // built-in annotations are always of type grafana spec: { ...layer.state.query.target, }, }; } else { result.spec.query = { - kind: getAnnotationQueryKind(layer.state.query), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: datasource?.type!, spec: { ...layer.state.query.target, }, }; + + if (layer.state.query.datasource?.uid) { + result.spec.query.datasource = { + name: layer.state.query.datasource?.uid, + }; + } } } // For annotations without query.query defined (e.g., grafana annotations without tags) else if (layer.state.query.query?.kind) { result.spec.query = { - kind: layer.state.query.query.kind, + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: layer.state.query.query.group || getAnnotationQueryKind(layer.state.query), + datasource: layer.state.query.query.datasource, spec: { ...layer.state.query.query.spec, }, @@ -466,10 +508,15 @@ function getAnnotations(state: DashboardSceneState, dsReferencesMapping?: DSRefe // Store extra properties in the legacyOptions field instead of directly in the spec if (Object.keys(otherProps).length > 0) { - // Extract options property and get the rest of the properties + // // Extract options property and get the rest of the properties const { legacyOptions, ...restProps } = otherProps; - // Merge options with the rest of the properties - result.spec.legacyOptions = { ...legacyOptions, ...restProps }; + if (legacyOptions) { + // Merge options with the rest of the properties + result.spec.legacyOptions = { ...legacyOptions, ...restProps }; + } + result.spec.query!.spec = { + ...otherProps, + }; } // If filter is an empty array, don't save it diff --git a/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx b/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx index 0d7ccaea603..4eb8476eaa5 100644 --- a/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx +++ b/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx @@ -5,6 +5,7 @@ import { locationService, reportInteraction } from '@grafana/runtime'; import { AnnotationQueryKind, Spec as DashboardV2Spec, + defaultDataQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { Form } from 'app/core/components/Form/Form'; import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; @@ -16,6 +17,8 @@ import { ImportDashboardFormV2 } from './ImportDashboardFormV2'; const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported'; +type FormData = SaveDashboardCommand & { [key: `datasource-${string}`]: string }; + export function ImportDashboardOverviewV2() { const [uidReset, setUidReset] = useState(false); const dispatch = useDispatch(); @@ -34,24 +37,29 @@ export function ImportDashboardOverviewV2() { dispatch(clearLoadedDashboard()); } - async function onSubmit(form: SaveDashboardCommand) { + async function onSubmit(form: FormData) { reportInteraction(IMPORT_FINISHED_EVENT_NAME); const dashboardWithDataSources: DashboardV2Spec = { ...dashboard, title: form.dashboard.title, annotations: dashboard.annotations?.map((annotation: AnnotationQueryKind) => { - if (annotation.spec.query?.kind) { - const dsType = annotation.spec.query.kind; + const dsType = annotation.spec.query?.spec.group; + if (dsType) { if (form[`datasource-${dsType}` as keyof typeof form]) { const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string }; return { ...annotation, spec: { ...annotation.spec, - datasource: { - uid: ds.uid, - type: ds.type, + query: { + kind: 'DataQuery', + group: dsType, + version: defaultDataQueryKind().version, + datasource: { name: ds.uid }, + spec: { + ...annotation.spec.query?.spec, + }, }, }, }; @@ -61,18 +69,23 @@ export function ImportDashboardOverviewV2() { }), variables: dashboard.variables?.map((variable) => { if (variable.kind === 'QueryVariable') { - if (variable.spec.query?.kind) { - const dsType = variable.spec.query.kind; + const dsType = variable.spec.query?.spec.group; + if (dsType) { if (form[`datasource-${dsType}` as keyof typeof form]) { const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string }; return { ...variable, spec: { ...variable.spec, - datasource: { - ...variable.spec.datasource, - uid: ds.uid, - type: ds.type, + query: { + ...variable.spec.query, + spec: { + ...variable.spec.query.spec, + group: ds.type, + datasource: { + name: ds.uid, + }, + }, }, options: [], current: { @@ -157,7 +170,7 @@ export function ImportDashboardOverviewV2() { return ( <> - & { [key: `datasource-${string}`]: string }> + onSubmit={onSubmit} defaultValues={{ dashboard, k8s: { annotations: { 'grafana.app/folder': folder.uid } } }} validateOnMount diff --git a/public/app/features/dashboard-scene/v2schema/test-helpers.ts b/public/app/features/dashboard-scene/v2schema/test-helpers.ts index 815762fdd31..c16b8bc0a6b 100644 --- a/public/app/features/dashboard-scene/v2schema/test-helpers.ts +++ b/public/app/features/dashboard-scene/v2schema/test-helpers.ts @@ -52,7 +52,8 @@ export function validateVariable< expect(sceneVariable?.state.pluginId).toBe(variableKind.spec.pluginId); } if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') { - expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource); + expect(sceneVariable?.state.datasource?.type).toBe(variableKind.spec.query?.group); + expect(sceneVariable?.state.datasource?.uid).toBe(variableKind.spec.query?.datasource?.name); expect(sceneVariable?.state.query).toEqual(variableKind.spec.query.spec); } if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') { diff --git a/public/app/features/dashboard/api/ResponseTransformers.test.ts b/public/app/features/dashboard/api/ResponseTransformers.test.ts index eda142c38e5..f4c633f5430 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.test.ts @@ -2,6 +2,7 @@ import { AnnotationQuery, DataQuery, VariableModel, VariableRefresh, Panel } fro import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2_examples'; import { Spec as DashboardV2Spec, + defaultDataQueryKind, GridLayoutItemKind, GridLayoutKind, PanelKind, @@ -466,10 +467,14 @@ describe('ResponseTransformers', () => { { kind: 'PanelQuery', spec: { - datasource: 'datasource1', hidden: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + datasource: { + name: 'datasource1', + }, spec: { expr: 'test-query', }, @@ -973,11 +978,9 @@ describe('ResponseTransformers', () => { result.forEach((query) => { expect(query.kind).toBe('PanelQuery'); - expect(query.spec.datasource).toEqual({ - type: 'theoretical-ds', - uid: 'theoretical-uid', - }); - expect(query.spec.query.kind).toBe('theoretical-ds'); + expect(query.spec.query.group).toEqual('theoretical-ds'); + expect(query.spec.query.datasource?.name).toEqual('theoretical-uid'); + expect(query.spec.query.kind).toBe('DataQuery'); }); }); @@ -1003,11 +1006,9 @@ describe('ResponseTransformers', () => { result.forEach((query) => { expect(query.kind).toBe('PanelQuery'); - expect(query.spec.datasource).toEqual({ - type: 'theoretical-ds', - uid: 'theoretical-uid', - }); - expect(query.spec.query.kind).toBe('theoretical-ds'); + expect(query.spec.query.group).toEqual('theoretical-ds'); + expect(query.spec.query.datasource?.name).toEqual('theoretical-uid'); + expect(query.spec.query.kind).toBe('DataQuery'); }); }); }); @@ -1017,7 +1018,8 @@ describe('ResponseTransformers', () => { const { spec: v2Spec } = v2; expect(v1.name).toBe(v2Spec.name); - expect(v1.datasource).toBe(v2Spec.datasource); + expect(v1.datasource?.type).toBe(v2Spec.query?.spec.group); + expect(v1.datasource?.uid).toBe(v2Spec.query?.spec.datasource?.name); expect(v1.enable).toBe(v2Spec.enable); expect(v1.hide).toBe(v2Spec.hide); expect(v1.iconColor).toBe(v2Spec.iconColor); @@ -1043,7 +1045,10 @@ describe('ResponseTransformers', () => { return { refId: q.spec.refId, hide: q.spec.hidden, - datasource: q.spec.datasource, + datasource: { + type: q.spec.query.spec.group, + uid: q.spec.query.spec.datasource?.uid, + }, ...q.spec.query.spec, }; }) @@ -1093,18 +1098,24 @@ describe('ResponseTransformers', () => { }; expect(v2Common).toEqual(v1Common); - if (v2.kind === 'QueryVariable') { - expect(v2.spec.datasource).toEqual(v1.datasource); - - if (typeof v1.query === 'string') { - expect(v2.spec.query.spec[LEGACY_STRING_VALUE_KEY]).toEqual(v1.query); - } else { - expect(v2.spec.query).toEqual({ - kind: v1.datasource?.type, - spec: { - ...(typeof v1.query === 'object' ? v1.query : {}), + expect(v2.spec.query).toMatchObject({ + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: (v1.datasource?.type || getDefaultDataSourceRef()?.type) ?? 'grafana', + ...(v1.datasource?.uid && { + datasource: { + name: v1.datasource?.uid, }, + }), + }); + if (typeof v1.query === 'string') { + expect(v2.spec.query.spec).toEqual({ + [LEGACY_STRING_VALUE_KEY]: v1.query, + }); + } else { + expect(v2.spec.query.spec).toEqual({ + ...(typeof v1.query === 'object' ? v1.query : {}), }); } } diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index c2b56fe51cd..e440cd63b99 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -38,6 +38,7 @@ import { LibraryPanelKind, PanelKind, GridLayoutItemKind, + defaultDataQueryKind, RowsLayoutRowKind, GridLayoutKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; @@ -502,9 +503,13 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc spec: { refId: t.refId, hidden: t.hide ?? false, - datasource: t.datasource ? t.datasource : panelDatasource, query: { - kind: t.datasource?.type || panelDatasource.type!, + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: t.datasource?.type || panelDatasource.type!, + datasource: { + name: t.datasource?.uid || panelDatasource.uid!, + }, spec: { ...query, }, @@ -568,7 +573,12 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] regex: v.regex || '', sort: transformSortVariableToEnum(v.sort), query: { - kind: v.datasource?.type || getDefaultDatasourceType(), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: v.datasource?.type ?? getDefaultDatasourceType(), + datasource: { + name: v.datasource?.uid, + }, spec: query, }, allowCustomValue: v.allowCustomValue ?? true, @@ -722,7 +732,12 @@ function getAnnotations(annotations: AnnotationQuery[]): DashboardV2Spec['annota iconColor: a.iconColor, builtIn: Boolean(a.builtIn), query: { - kind: a.datasource?.type || getDefaultDatasourceType(), + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: a.datasource?.type || getDefaultDatasourceType(), + datasource: { + name: a.datasource?.uid, + }, spec: { ...a.target, }, @@ -757,7 +772,10 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] { LEGACY_STRING_VALUE_KEY in v.spec.query.spec ? v.spec.query.spec[LEGACY_STRING_VALUE_KEY] : v.spec.query.spec, - datasource: v.spec.datasource, + datasource: { + type: v.spec.query?.spec.group, + uid: v.spec.query?.spec.datasource?.name, + }, sort: transformSortVariableToEnumV1(v.spec.sort), refresh: transformVariableRefreshToEnumV1(v.spec.refresh), regex: v.spec.regex, @@ -879,7 +897,10 @@ function getAnnotationsV1(annotations: DashboardV2Spec['annotations']): Annotati return annotations.map((a) => { return { name: a.spec.name, - datasource: a.spec.datasource, + datasource: { + type: a.spec.query?.spec.group, + uid: a.spec.query?.spec.datasource?.name, + }, enable: a.spec.enable, hide: a.spec.hide, iconColor: a.spec.iconColor, @@ -950,7 +971,10 @@ function transformV2PanelToV1Panel( return { refId: q.spec.refId, hide: q.spec.hidden, - datasource: q.spec.datasource, + datasource: { + uid: q.spec.query.spec.datasource?.uid, + type: q.spec.query.spec.group, + }, ...q.spec.query.spec, }; }), diff --git a/public/app/features/manage-dashboards/state/actions.test.ts b/public/app/features/manage-dashboards/state/actions.test.ts index c4aebee06de..b6af47ee643 100644 --- a/public/app/features/manage-dashboards/state/actions.test.ts +++ b/public/app/features/manage-dashboards/state/actions.test.ts @@ -7,6 +7,7 @@ import { defaultSpec as defaultDashboardV2Spec, defaultPanelSpec, defaultQueryVariableSpec, + defaultDataQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { getLibraryPanel } from 'app/features/library-panels/state/api'; @@ -836,7 +837,9 @@ describe('processV2Datasources', () => { refId: 'A', hidden: false, query: { - kind: 'prometheus', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', spec: { expr: 'access_evaluation_duration_count', range: true, @@ -862,7 +865,9 @@ describe('processV2Datasources', () => { ...defaultQueryVariableSpec(), name: 'var1', query: { - kind: 'loki', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'loki', spec: { expr: 'access_evaluation_duration_count', range: true, @@ -880,7 +885,9 @@ describe('processV2Datasources', () => { hide: false, iconColor: 'red', query: { - kind: 'loki', + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'loki', spec: { expr: 'access_evaluation_duration_count', range: true, @@ -982,13 +989,17 @@ describe('processV2DatasourceInput', () => { ...defaultQueryVariableSpec(), name: 'var2WithGrafanaDs', query: { - kind: 'grafana', + kind: 'DataQuery' as const, + version: defaultDataQueryKind().version, + group: 'grafana', spec: { - panelId: 2, + expr: 'access_evaluation_duration_count', + range: true, }, }, }, }; + const result = await processV2DatasourceInput(queryVariable.spec, {}); expect(result).toEqual({}); }); @@ -1001,7 +1012,9 @@ describe('processV2DatasourceInput', () => { refId: 'A', hidden: false, query: { - kind: 'datasource', + kind: 'DataQuery' as const, + version: defaultDataQueryKind().version, + group: 'datasource', spec: { panelId: 2, }, diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 1b5b02e24e4..fad40fbec18 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -167,6 +167,7 @@ export function processV2Datasources(dashboard: DashboardV2Spec): ThunkResult 0) { for (const query of element.spec.data.spec.queries) { inputs = await processV2DatasourceInput(query.spec, inputs); @@ -331,43 +332,43 @@ export function getFolderByUid(uid: string): Promise<{ uid: string; title: strin } export async function processV2DatasourceInput( - obj: PanelQueryKind['spec'] | QueryVariableKind['spec'] | AnnotationQueryKind['spec'], + spec: PanelQueryKind['spec'] | QueryVariableKind['spec'] | AnnotationQueryKind['spec'], inputs: Record = {} ) { - const datasourceRef = obj?.datasource; - if (!datasourceRef && obj?.query) { - const dsType = obj.query.kind; + const datasourceRef = spec.query.datasource; + let dataSourceInput: DataSourceInput | undefined; + const dsType = spec.query.group; + + if (!datasourceRef) { // if dsType is grafana, it means we are using a built-in annotation or default grafana datasource, in those // cases we don't need to map it // "datasource" type is what we call "--Dashboard--" datasource <.-.> if (dsType === 'grafana' || dsType === 'datasource') { return inputs; } - const datasource = await getDatasourceSrv().get({ type: dsType }); - let dataSourceInput: DataSourceInput | undefined; - if (datasource) { - dataSourceInput = { - name: datasource.name, - label: datasource.name, - info: `Select a ${datasource.name} data source`, - value: datasource.uid, - type: InputType.DataSource, - pluginId: datasource.meta?.id, - }; + } - inputs[datasource.meta?.id] = dataSourceInput; - } else { - dataSourceInput = { - name: dsType, - label: dsType, - info: `No data sources of type ${dsType} found`, - value: '', - type: InputType.DataSource, - pluginId: dsType, - }; - - inputs[dsType] = dataSourceInput; - } + const datasource = await getDatasourceSrv().get({ type: dsType }); + if (datasource) { + dataSourceInput = { + name: datasource.name, + label: datasource.name, + info: `Select a ${datasource.name} data source`, + value: datasource.uid, + type: InputType.DataSource, + pluginId: datasource.meta?.id, + }; + inputs[datasource.meta?.id] = dataSourceInput; + } else { + dataSourceInput = { + name: dsType, + label: dsType, + info: `No data sources of type ${dsType} found`, + value: '', + type: InputType.DataSource, + pluginId: dsType, + }; + inputs[dsType] = dataSourceInput; } return inputs; } From cc069d301e8f91753afd21b030c43b2efedf6c0e Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Mon, 7 Jul 2025 15:32:39 +0200 Subject: [PATCH 54/67] Secrets: Skip allowlist check when decrypting if the list is empty (#107693) --- pkg/registry/apis/secret/decrypt/authorizer.go | 6 ++++-- pkg/registry/apis/secret/decrypt/authorizer_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apis/secret/decrypt/authorizer.go b/pkg/registry/apis/secret/decrypt/authorizer.go index 6350e5f80af..9f2915d708b 100644 --- a/pkg/registry/apis/secret/decrypt/authorizer.go +++ b/pkg/registry/apis/secret/decrypt/authorizer.go @@ -61,8 +61,10 @@ func (a *decryptAuthorizer) Authorize(ctx context.Context, secureValueName strin // TEMPORARY: while we can't onboard every app into secrets, we can block them from decrypting // securevalues preemptively here before even reaching out to the database. // This check can be removed once we open the gates for any service to use secrets. - if _, exists := a.allowList[serviceIdentity]; !exists || serviceIdentity == "" { - return serviceIdentity, false + if len(a.allowList) > 0 { + if _, exists := a.allowList[serviceIdentity]; !exists || serviceIdentity == "" { + return serviceIdentity, false + } } // Checks whether the token has the permission to decrypt secure values. diff --git a/pkg/registry/apis/secret/decrypt/authorizer_test.go b/pkg/registry/apis/secret/decrypt/authorizer_test.go index 3e4cd59ef31..1e8c33405b2 100644 --- a/pkg/registry/apis/secret/decrypt/authorizer_test.go +++ b/pkg/registry/apis/secret/decrypt/authorizer_test.go @@ -108,6 +108,15 @@ func TestDecryptAuthorizer(t *testing.T) { require.False(t, allowed) }) + t.Run("when the allow list is empty, it allows all identities", func(t *testing.T) { + ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) + authorizer := ProvideDecryptAuthorizer(tracer, nil) + + identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) + require.NotEmpty(t, identity) + require.True(t, allowed) + }) + t.Run("when the identity is not in the allow list, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"allowed1": {}}) From df2e1d7ef275da52558b32a31d7bc4f1a3a636c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:02:03 +0000 Subject: [PATCH 55/67] Update dependency diff to v8 (#107684) * Update dependency diff to v8 * remove @types/diff --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- package.json | 3 +-- yarn.lock | 18 +++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index c97616851d5..a82e59151e7 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,6 @@ "@types/d3-force": "^3.0.0", "@types/d3-scale-chromatic": "3.1.0", "@types/debounce-promise": "3.1.9", - "@types/diff": "^7.0.0", "@types/eslint": "9.6.1", "@types/eslint-scope": "^3.7.7", "@types/file-saver": "2.0.7", @@ -336,7 +335,7 @@ "dangerously-set-html-content": "1.1.0", "date-fns": "4.1.0", "debounce-promise": "3.1.2", - "diff": "^7.0.0", + "diff": "^8.0.0", "fast-deep-equal": "^3.1.3", "fast-json-patch": "3.1.1", "file-saver": "2.0.5", diff --git a/yarn.lock b/yarn.lock index 4f0ac2bf9ff..eee5f0b4f87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9276,13 +9276,6 @@ __metadata: languageName: node linkType: hard -"@types/diff@npm:^7.0.0": - version: 7.0.1 - resolution: "@types/diff@npm:7.0.1" - checksum: 10/ef8c5fe0ea56737e8967c3db5e78518134f704e8e2971cb4ba6f2ed46ed5e13b4bbe41a6b2b78122b8f47c618ac25550f85a681633636a3a020369042523295d - languageName: node - linkType: hard - "@types/doctrine@npm:^0.0.9": version: 0.0.9 resolution: "@types/doctrine@npm:0.0.9" @@ -15074,10 +15067,10 @@ __metadata: languageName: node linkType: hard -"diff@npm:^7.0.0": - version: 7.0.0 - resolution: "diff@npm:7.0.0" - checksum: 10/e9b8e48d054c9c0c093c65ce8e2637af94b35f2427001607b14e5e0589e534ea3413a7f91ebe6d7c5a1494ace49cb7c7c3972f442ddd96a4767ff091999a082e +"diff@npm:^8.0.0": + version: 8.0.2 + resolution: "diff@npm:8.0.2" + checksum: 10/82a2120d3418f97822e17a6044ccd4b99a91e26e145e8698353673d7146bd2d092bbebb79c112aae7badc7b9c526f9098cbe342f96174feb6beabdd2587b3c42 languageName: node linkType: hard @@ -18190,7 +18183,6 @@ __metadata: "@types/d3-force": "npm:^3.0.0" "@types/d3-scale-chromatic": "npm:3.1.0" "@types/debounce-promise": "npm:3.1.9" - "@types/diff": "npm:^7.0.0" "@types/eslint": "npm:9.6.1" "@types/eslint-scope": "npm:^3.7.7" "@types/file-saver": "npm:2.0.7" @@ -18274,7 +18266,7 @@ __metadata: dangerously-set-html-content: "npm:1.1.0" date-fns: "npm:4.1.0" debounce-promise: "npm:3.1.2" - diff: "npm:^7.0.0" + diff: "npm:^8.0.0" esbuild: "npm:0.25.0" esbuild-loader: "npm:4.3.0" esbuild-plugin-browserslist: "npm:^1.0.0" From 14e98d740bc6842e323b6478878b20bcf3f72aa8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:28:17 +0000 Subject: [PATCH 56/67] Update dependency swagger-ui-react to v5.26.2 (#107699) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a82e59151e7..076daf4da1c 100644 --- a/package.json +++ b/package.json @@ -415,7 +415,7 @@ "slate": "0.47.9", "slate-plain-serializer": "0.7.13", "slate-react": "0.22.10", - "swagger-ui-react": "5.26.1", + "swagger-ui-react": "5.26.2", "symbol-observable": "4.0.0", "systemjs": "6.15.1", "tinycolor2": "1.6.0", diff --git a/yarn.lock b/yarn.lock index eee5f0b4f87..61cb2e8fc9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18409,7 +18409,7 @@ __metadata: style-loader: "npm:4.0.0" stylelint: "npm:16.14.1" stylelint-config-sass-guidelines: "npm:12.1.0" - swagger-ui-react: "npm:5.26.1" + swagger-ui-react: "npm:5.26.2" symbol-observable: "npm:4.0.0" systemjs: "npm:6.15.1" terser-webpack-plugin: "npm:5.3.11" @@ -30242,9 +30242,9 @@ __metadata: languageName: node linkType: hard -"swagger-ui-react@npm:5.26.1": - version: 5.26.1 - resolution: "swagger-ui-react@npm:5.26.1" +"swagger-ui-react@npm:5.26.2": + version: 5.26.2 + resolution: "swagger-ui-react@npm:5.26.2" dependencies: "@babel/runtime-corejs3": "npm:^7.27.1" "@scarf/scarf": "npm:=1.4.0" @@ -30282,7 +30282,7 @@ __metadata: peerDependencies: react: ">=16.8.0 <19" react-dom: ">=16.8.0 <19" - checksum: 10/978b366b6fe39b23533d5c7781b313a1350d6007eb4fba557d3dc36ee8541a27a7b5aefd37e155adb19755a9482b4ac245ba47a8130d6791bb587ef692cbaafd + checksum: 10/4ca99974df01fda16adcfd16458ee8703f1f1df5112e80dd2373bcaca0404f188ce1b96ae8ac4838311f2ec5b6073220966ba4089cc67ea378a84990976c6d40 languageName: node linkType: hard From 14eaa3975b4f2ec7ff18a26681e69206103f2bd7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:56:40 +0000 Subject: [PATCH 57/67] Update scenes to v6.27.1 (#107703) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 61cb2e8fc9e..f205a576180 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3543,10 +3543,10 @@ __metadata: linkType: soft "@grafana/scenes-react@npm:^6.27.0": - version: 6.27.0 - resolution: "@grafana/scenes-react@npm:6.27.0" + version: 6.27.1 + resolution: "@grafana/scenes-react@npm:6.27.1" dependencies: - "@grafana/scenes": "npm:6.27.0" + "@grafana/scenes": "npm:6.27.1" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3558,13 +3558,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4e52ad816a5ca05715ed0d317ece8ba8ccf857367f37c637edef11069b8f0db28ed41266f3bc8439ccf6af390d66e2351e17f026eb5cdc5ae516ab9afad6d18b + checksum: 10/1177db57199595f23722b9059ddf21f2679199f423ddde78c41d0a037d0e9c755a47c149541ce89df3694d7c55399e76eabed6a68a02b9b344acb8d3f1d95610 languageName: node linkType: hard -"@grafana/scenes@npm:6.27.0, @grafana/scenes@npm:^6.27.0": - version: 6.27.0 - resolution: "@grafana/scenes@npm:6.27.0" +"@grafana/scenes@npm:6.27.1, @grafana/scenes@npm:^6.27.0": + version: 6.27.1 + resolution: "@grafana/scenes@npm:6.27.1" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3584,7 +3584,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/c8328cc323fe513d5bbb70eea44bcd08ff7b444e4266134151ccef1f038453f99d36dec0785f95bb0565c0c1623f463000721f9b22401d45ba79bdec14112332 + checksum: 10/7569db543bdf53483d5960e533cee61e86c25647b30d1290a28038725895dbb7e530bc9fd4520fc8c4e59b9a819f1daeeb87aecdf45c9d8870212c7ef1d65c8d languageName: node linkType: hard From 37c480def44002cfebd2fb0f4a0f260774af7a96 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:25:23 -0400 Subject: [PATCH 58/67] K8s: Add support for stored subresources in app runner (#105481) --- apps/dashboard/pkg/apis/dashboard_manifest.go | 2 + .../apiserver/builder/runner/builder.go | 6 + .../advisor.grafana.app-v0alpha1.json | 580 ++++++++++++++++++ .../investigations.grafana.app-v0alpha1.json | 580 ++++++++++++++++++ 4 files changed, 1168 insertions(+) diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 568e229d09d..7a4552f5e0a 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -16,6 +16,8 @@ import ( v2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" ) +var () + var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", diff --git a/pkg/services/apiserver/builder/runner/builder.go b/pkg/services/apiserver/builder/runner/builder.go index a732c97b5a4..6e150273878 100644 --- a/pkg/services/apiserver/builder/runner/builder.go +++ b/pkg/services/apiserver/builder/runner/builder.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/kube-openapi/pkg/common" @@ -132,6 +133,11 @@ func (b *appBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI return err } apiGroupInfo.VersionedResourcesStorageMap[version][resourceInfo.StoragePath()] = store + if registryStore, ok := store.(*genericregistry.Store); ok { + for subPath := range kind.ZeroValue().GetSubresources() { + apiGroupInfo.VersionedResourcesStorageMap[version][resourceInfo.StoragePath(subPath)] = grafanaregistry.NewRegistryStatusStore(opts.Scheme, registryStore) + } + } } } return nil diff --git a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json index 7ef15e8f82b..77879dc0fc4 100644 --- a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json @@ -865,6 +865,296 @@ } ] }, + "/apis/advisor.grafana.app/v0alpha1/namespaces/{namespace}/checks/{name}/status": { + "get": { + "tags": [ + "Check" + ], + "description": "read status of the specified Check", + "operationId": "getCheckStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "Check" + } + }, + "put": { + "tags": [ + "Check" + ], + "description": "replace status of the specified Check", + "operationId": "replaceCheckStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "Check" + } + }, + "patch": { + "tags": [ + "Check" + ], + "description": "partially update status of the specified Check", + "operationId": "updateCheckStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.Check" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "Check" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Check", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/apis/advisor.grafana.app/v0alpha1/namespaces/{namespace}/checktypes": { "get": { "tags": [ @@ -1694,6 +1984,296 @@ } } ] + }, + "/apis/advisor.grafana.app/v0alpha1/namespaces/{namespace}/checktypes/{name}/status": { + "get": { + "tags": [ + "CheckType" + ], + "description": "read status of the specified CheckType", + "operationId": "getCheckTypeStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "CheckType" + } + }, + "put": { + "tags": [ + "CheckType" + ], + "description": "replace status of the specified CheckType", + "operationId": "replaceCheckTypeStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "CheckType" + } + }, + "patch": { + "tags": [ + "CheckType" + ], + "description": "partially update status of the specified CheckType", + "operationId": "updateCheckTypeStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckType" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "advisor.grafana.app", + "version": "v0alpha1", + "kind": "CheckType" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the CheckType", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] } }, "components": { diff --git a/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json index 017323b2a1e..e1ce97830be 100644 --- a/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/investigations.grafana.app-v0alpha1.json @@ -865,6 +865,296 @@ } ] }, + "/apis/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigationindexes/{name}/status": { + "get": { + "tags": [ + "InvestigationIndex" + ], + "description": "read status of the specified InvestigationIndex", + "operationId": "getInvestigationIndexStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "InvestigationIndex" + } + }, + "put": { + "tags": [ + "InvestigationIndex" + ], + "description": "replace status of the specified InvestigationIndex", + "operationId": "replaceInvestigationIndexStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "InvestigationIndex" + } + }, + "patch": { + "tags": [ + "InvestigationIndex" + ], + "description": "partially update status of the specified InvestigationIndex", + "operationId": "updateInvestigationIndexStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.InvestigationIndex" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "InvestigationIndex" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the InvestigationIndex", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/apis/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigations": { "get": { "tags": [ @@ -1694,6 +1984,296 @@ } } ] + }, + "/apis/investigations.grafana.app/v0alpha1/namespaces/{namespace}/investigations/{name}/status": { + "get": { + "tags": [ + "Investigation" + ], + "description": "read status of the specified Investigation", + "operationId": "getInvestigationStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "Investigation" + } + }, + "put": { + "tags": [ + "Investigation" + ], + "description": "replace status of the specified Investigation", + "operationId": "replaceInvestigationStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "Investigation" + } + }, + "patch": { + "tags": [ + "Investigation" + ], + "description": "partially update status of the specified Investigation", + "operationId": "updateInvestigationStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.investigations.pkg.apis.investigations.v0alpha1.Investigation" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "investigations.grafana.app", + "version": "v0alpha1", + "kind": "Investigation" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Investigation", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] } }, "components": { From a2a28b207cb3af11361819b6c717ff6ec2a66945 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Mon, 7 Jul 2025 11:37:35 -0400 Subject: [PATCH 59/67] KBARResults: Add Cmd+Enter/Ctrl+Enter to open links in new tab (#107537) * KBARResults:add command+enter to open selected link in new tab --- public/app/features/commandPalette/KBarResults.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/public/app/features/commandPalette/KBarResults.tsx b/public/app/features/commandPalette/KBarResults.tsx index 2eb8a5aecf7..ae7604b6350 100644 --- a/public/app/features/commandPalette/KBarResults.tsx +++ b/public/app/features/commandPalette/KBarResults.tsx @@ -71,7 +71,18 @@ export const KBarResults = (props: KBarResultsProps) => { } return nextIndex; }); - } else if (event.key === 'Enter' && !event.metaKey) { + } else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { + // Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux) - open in new tab + event.preventDefault(); + + if (activeRef.current instanceof HTMLAnchorElement) { + window.open(activeRef.current.href, '_blank', 'noopener,noreferrer'); + query.toggle(); + } else { + // For action-based items (rendered as
tags), execute normally + activeRef.current?.click(); + } + } else if (event.key === 'Enter' && !event.metaKey && !event.ctrlKey) { event.preventDefault(); // storing the active dom element in a ref prevents us from // having to calculate the current action to perform based From 31b71f79dd3a1e00cd269a95d2505d5b8358c7c3 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 7 Jul 2025 16:43:19 +0100 Subject: [PATCH 60/67] Internationalisation: Manual mark up following review (#107625) * mark up and add confirmText to lint rule * mark up and add ariaLabel to lint rule * add confirmText to propertiesToCheck * mark up and add body to propsToCheck * mark up latest * mark up 'Dashboard saved' * mark up filterbyvalue options * mark up tooltip options * mark up and add lint rules for noOptionsMessage/loadingMessage * mark up 'Convert field type' * mark up placeholderText/noOptionsMessage * mark up run query * mark up variable editor fields * mark up week start options * mark up dashboard link options * mark up prom options * mark up builder/code toggles * make CI happy * kick CI --- .../no-untranslated-strings.cjs | 20 +- .../src/configuration/PromSettings.tsx | 16 +- .../src/locales/en-US/grafana-prometheus.json | 38 +++ .../components/LabelParamEditor.tsx | 11 +- .../components/PromQueryBuilderOptions.tsx | 50 ++- .../components/PromQueryEditorSelector.tsx | 7 +- .../components/PromQueryLegendEditor.tsx | 29 +- .../shared/QueryEditorModeToggle.tsx | 16 +- .../src/components/QueryHeader.tsx | 13 +- .../src/locales/en-US/grafana-sql.json | 4 + .../components/ConfirmButton/DeleteButton.tsx | 4 +- .../DateTimePickers/WeekStartPicker.tsx | 18 +- .../components/PanelChrome/PanelChrome.tsx | 17 +- .../components/FolderFilter/FolderFilter.tsx | 2 +- .../core/components/Select/MetricSelect.tsx | 3 +- .../app/core/components/Select/OrgPicker.tsx | 2 +- .../Select/ServiceAccountPicker.tsx | 5 +- .../app/core/components/Select/TeamPicker.tsx | 2 +- .../app/core/components/Select/UserPicker.tsx | 2 +- .../admin/AdminFeatureTogglesTable.tsx | 2 +- public/app/features/admin/AdminOrgsTable.tsx | 2 +- public/app/features/admin/UserOrgs.tsx | 4 +- public/app/features/admin/UserPermissions.tsx | 2 +- public/app/features/admin/UserProfile.tsx | 10 +- public/app/features/admin/UserSessions.tsx | 9 +- .../features/admin/Users/OrgUsersTable.tsx | 6 +- .../unified/components/AlertManagerPicker.tsx | 6 +- .../contact-points/ContactPointHeader.tsx | 4 +- .../mute-timings/MuteTimingActionsButtons.tsx | 6 +- .../components/receivers/TemplatesTable.tsx | 8 +- .../form/fields/TemplateSelector.tsx | 5 +- .../rule-editor/GrafanaEvaluationBehavior.tsx | 5 +- .../QueryAndExpressionsStep.tsx | 2 +- .../components/rule-viewer/DeleteModal.tsx | 2 +- .../unified/components/rules/CloneRule.tsx | 2 +- .../rules/MultipleDataSourcePicker.tsx | 5 +- .../settings/AlertmanagerConfig.tsx | 2 +- .../components/settings/VersionManager.tsx | 10 +- .../components/AnnotationResultMapper.tsx | 7 +- .../auth-config/ProviderConfigForm.tsx | 2 +- .../PanelDataTransformationsTab.tsx | 7 +- .../saving/useSaveDashboard.ts | 3 +- .../scene/GoToSnapshotOriginButton.tsx | 2 +- .../dashboard-scene/scene/UnlinkModal.tsx | 8 +- .../settings/GeneralSettingsEditView.tsx | 42 ++- .../settings/links/DashboardLinkForm.tsx | 12 +- .../settings/variables/VariableEditorForm.tsx | 13 +- .../variables/VariableEditorListRow.tsx | 11 +- .../components/AdHocVariableForm.tsx | 2 +- .../components/CustomVariableForm.tsx | 2 +- .../components/DataSourceVariableForm.tsx | 6 +- .../components/GroupByVariableForm.tsx | 2 +- .../components/IntervalVariableForm.tsx | 10 +- .../components/QueryVariableForm.tsx | 2 +- .../components/SelectionOptionsForm.tsx | 8 +- .../components/TextBoxVariableForm.tsx | 2 +- .../components/VariableHideSelect.tsx | 28 +- .../components/VariableTypeSelect.tsx | 3 +- .../variables/editors/QueryVariableEditor.tsx | 2 +- .../version-history/RevertDashboardModal.tsx | 6 +- .../version-history/VersionHistoryTable.tsx | 2 +- .../ConfigPublicDashboard.tsx | 5 +- .../dashboard/components/DashNav/DashNav.tsx | 2 +- .../DashboardSettings/GeneralSettings.tsx | 20 +- .../SaveDashboard/SaveDashboardErrorProxy.tsx | 7 +- .../SaveDashboard/useDashboardSave.tsx | 3 +- .../TransformationOperationRow.tsx | 7 +- .../TransformationsEditor.tsx | 7 +- .../VersionHistory/RevertDashboardModal.tsx | 6 +- .../VersionHistory/VersionHistoryTable.tsx | 2 +- .../editors/ColorDimensionEditor.tsx | 2 +- .../editors/ScalarDimensionEditor.tsx | 2 +- .../editors/ScaleDimensionEditor.tsx | 2 +- .../Actions/TracePageActions.tsx | 4 +- .../SpanFilters/SpanFilters.tsx | 4 +- .../ChangeLibraryPanelModal.tsx | 7 +- .../components/SnapshotListTable.tsx | 8 +- .../InstallControls/InstallControlsButton.tsx | 7 +- .../admin/components/VersionInstallButton.tsx | 6 +- .../serviceaccounts/ServiceAccountPage.tsx | 20 +- .../ServiceAccountsListPage.tsx | 38 ++- .../components/ServiceAccountProfileRow.tsx | 2 +- .../FilterByValueTransformerEditor.tsx | 32 +- .../ConvertFieldTypeTransformerEditor.tsx | 6 +- .../editors/FormatStringTransformerEditor.tsx | 30 +- .../ExtractFieldsTransformerEditor.tsx | 23 +- .../FieldLookupTransformerEditor.tsx | 30 +- .../variables/editor/ConfirmDeleteModal.tsx | 10 +- .../variables/editor/VariableEditorEditor.tsx | 6 +- .../variables/editor/VariableTypeSelect.tsx | 3 +- .../query/QueryVariableSortSelect.tsx | 64 +++- .../textbox/TextBoxVariableEditor.tsx | 2 +- .../LogsQueryEditor/AzureCheatSheet.tsx | 5 +- .../LogsQueryEditor/LogsManagement.tsx | 4 +- .../components/QueryEditor/QueryHeader.tsx | 24 +- .../grafana-azure-monitor-datasource.json | 10 + .../panel/geomap/editor/StyleEditor.tsx | 14 +- .../panel/geomap/layers/data/photosLayer.tsx | 3 +- public/locales/en-US/grafana.json | 290 ++++++++++++++++-- 99 files changed, 953 insertions(+), 297 deletions(-) diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs index 91de4620060..64ca4c5ff4a 100644 --- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs +++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs @@ -24,7 +24,21 @@ const createRule = ESLintUtils.RuleCreator( /** * JSX props to check for untranslated strings */ -const propsToCheck = ['content', 'label', 'description', 'placeholder', 'aria-label', 'title', 'text', 'tooltip']; +const propsToCheck = [ + 'content', + 'label', + 'description', + 'placeholder', + 'aria-label', + 'ariaLabel', + 'title', + 'text', + 'tooltip', + 'confirmText', + 'body', + 'noOptionsMessage', + 'loadingMessage', +]; /** * Object properties to check for untranslated strings @@ -34,11 +48,15 @@ const propertiesToCheck = [ 'description', 'placeholder', 'aria-label', + 'ariaLabel', 'title', 'subTitle', 'text', 'tooltip', 'message', + 'confirmText', + 'placeholderText', + 'noFieldsMessage', ]; /** @type {RuleDefinition} */ diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.tsx index 63642bdb592..789275e0814 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.tsx @@ -40,11 +40,6 @@ const httpOptions = [ { value: 'GET', label: 'GET' }, ]; -const editorOptions = [ - { value: QueryEditorMode.Builder, label: 'Builder' }, - { value: QueryEditorMode.Code, label: 'Code' }, -]; - const cacheValueOptions = [ { value: PrometheusCacheLevel.Low, label: 'Low' }, { value: PrometheusCacheLevel.Medium, label: 'Medium' }, @@ -86,6 +81,17 @@ export const PromSettings = (props: Props) => { const styles = overhaulStyles(theme); const { onOptionsChange } = props; + const editorOptions = [ + { + value: QueryEditorMode.Builder, + label: t('grafana-prometheus.configuration.prom-settings.editor-options.label-builder', 'Builder'), + }, + { + value: QueryEditorMode.Code, + label: t('grafana-prometheus.configuration.prom-settings.editor-options.label-code', 'Code'), + }, + ]; + const optionsWithDefaults = getOptionsWithDefaults(props.options); const [validDuration, updateValidDuration] = useState({ timeInterval: '', diff --git a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json index 5de8bc8d245..d7c9b1f6b1b 100644 --- a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json @@ -157,6 +157,10 @@ "aria-label-prom-type-type": "{{promType}} type", "aria-label-prometheus-type": "Prometheus type", "aria-label-select-http-method": "Select HTTP method", + "editor-options": { + "label-builder": "Builder", + "label-code": "Code" + }, "label-cache-level": "Cache level", "label-custom-query-parameters": "Custom query parameters", "label-default-editor": "Default editor", @@ -195,6 +199,16 @@ "tooltip-use-series-endpoint": "Checking this option will favor the series endpoint with {{exampleParameter}} parameter over the label values endpoint with {{exampleParameter}} parameter. While the label values endpoint is considered more performant, some users may prefer the series because it has a POST method while the label values endpoint only has a GET method." } }, + "prom-query-legend-editor": { + "get-legend-mode-options": { + "description-auto": "Only includes unique labels", + "description-custom": "Provide a naming template", + "description-verbose": "All label names and values", + "label-auto": "Auto", + "label-custom": "Custom", + "label-verbose": "Verbose" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "Filter metric names by regex search, using an additional call on the Prometheus API.", @@ -204,6 +218,13 @@ "give-feedback": "Give feedback", "title-give-feedback": "The metrics explorer is new, please let us know how we can improve it" }, + "get-collapsed-info": { + "exemplars": "Exemplars: {{value}}", + "format": "Format: {{value}}", + "legend": "Legend: {{value}}", + "step": "Step: {{value}}", + "type": "Type: {{value}}" + }, "handle-function": { "text": { "query-parsing-is-ambiguous": "Query parsing is ambiguous." @@ -219,6 +240,10 @@ "label-label-filters": "Label filters", "tooltip-label-filters": "Optional: used to filter the metric select for this query type." }, + "label-param-editor": { + "loadingMessage-loading-labels": "Loading labels", + "noOptionsMessage-no-labels-found": "No labels found" + }, "metric-combobox": { "async-select": { "aria-label-open-metrics-explorer": "Open metrics explorer", @@ -274,6 +299,11 @@ "prom-query-builder-options": { "aria-label-lower-limit-parameter": "Set lower limit for the step parameter", "aria-label-select-resolution": "Select resolution", + "format-options": { + "label-heatmap": "Heatmap", + "label-table": "Table", + "label-time-series": "Time series" + }, "label-exemplars": "Exemplars", "label-format": "Format", "label-min-step": "Min step", @@ -288,6 +318,8 @@ "tooltip-autocomplete-suggestions-limited": "The number of metric names exceeds the autocomplete limit. Only the {{autocompleteLimit}}-most relevant metrics are displayed. You can adjust the threshold in the data source settings." }, "prom-query-editor-selector": { + "body-syntax-error": "There is a syntax error, or the query structure cannot be visualized when switching to the builder mode. Parts of the query may be lost.", + "confirmText-continue": "Continue", "kick-start-your-query": "Kick start your query", "label-explain": "Explain", "run-queries": "Run queries", @@ -304,6 +336,12 @@ "query-editor-hints": { "hint-details": "hint: {{hintDetails}}" }, + "query-editor-mode-toggle": { + "editor-modes": { + "label-builder": "Builder", + "label-code": "Code" + } + }, "query-pattern": { "apply-query": "Apply query", "aria-label-apply-query-starter-button": "apply query starter button", diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx index ed978167f12..c51940fb3d6 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { DataSourceApi, SelectableValue, TimeRange, toOption } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; import { getOperationParamId } from '../shared/param_utils'; @@ -47,8 +48,14 @@ export function LabelParamEditor({ }} isLoading={state.isLoading} allowCustomValue - noOptionsMessage="No labels found" - loadingMessage="Loading labels" + noOptionsMessage={t( + 'grafana-prometheus.querybuilder.label-param-editor.noOptionsMessage-no-labels-found', + 'No labels found' + )} + loadingMessage={t( + 'grafana-prometheus.querybuilder.label-param-editor.loadingMessage-loading-labels', + 'Loading labels' + )} options={state.options} value={toOption(value as string)} onChange={(value) => onChange(index, value.value!)} diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx index 4be75ddf2e8..67cd36830e1 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx @@ -32,12 +32,6 @@ export interface PromQueryBuilderOptionsProps { onRunQuery: () => void; } -const FORMAT_OPTIONS: Array> = [ - { label: 'Time series', value: 'time_series' }, - { label: 'Table', value: 'table' }, - { label: 'Heatmap', value: 'heatmap' }, -]; - const INTERVAL_FACTOR_OPTIONS: Array> = map([1, 2, 3, 4, 5, 10], (value: number) => ({ value, label: '1/' + value, @@ -45,6 +39,24 @@ const INTERVAL_FACTOR_OPTIONS: Array> = map([1, 2, 3, 4, export const PromQueryBuilderOptions = React.memo( ({ query, app, onChange, onRunQuery }) => { + const FORMAT_OPTIONS: Array> = [ + { + label: t( + 'grafana-prometheus.querybuilder.prom-query-builder-options.format-options.label-time-series', + 'Time series' + ), + value: 'time_series', + }, + { + label: t('grafana-prometheus.querybuilder.prom-query-builder-options.format-options.label-table', 'Table'), + value: 'table', + }, + { + label: t('grafana-prometheus.querybuilder.prom-query-builder-options.format-options.label-heatmap', 'Heatmap'), + value: 'heatmap', + }, + ]; + const onChangeFormat = (value: SelectableValue) => { onChange({ ...query, format: value.value }); onRunQuery(); @@ -182,17 +194,25 @@ function getQueryTypeValue(query: PromQuery) { function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: string, app?: CoreApp): string[] { const items: string[] = []; - items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`Format: ${formatOption}`); - items.push(`Step: ${query.interval ?? 'auto'}`); - items.push(`Type: ${queryType}`); + items.push( + t('grafana-prometheus.querybuilder.get-collapsed-info.legend', 'Legend: {{value}}', { + value: getLegendModeLabel(query.legendFormat), + }) + ); + items.push( + t('grafana-prometheus.querybuilder.get-collapsed-info.format', 'Format: {{value}}', { value: formatOption }) + ); + items.push( + t('grafana-prometheus.querybuilder.get-collapsed-info.step', 'Step: {{value}}', { value: query.interval ?? 'auto' }) + ); + items.push(t('grafana-prometheus.querybuilder.get-collapsed-info.type', 'Type: {{value}}', { value: queryType })); if (shouldShowExemplarSwitch(query, app)) { - if (query.exemplar) { - items.push(`Exemplars: true`); - } else { - items.push(`Exemplars: false`); - } + items.push( + t('grafana-prometheus.querybuilder.get-collapsed-info.exemplars', 'Exemplars: {{value}}', { + value: query.exemplar ? 'true' : 'false', + }) + ); } return items; } diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx index 1d500669f5b..adf3afebf28 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx @@ -98,8 +98,11 @@ export const PromQueryEditorSelector = memo((props) => { 'grafana-prometheus.querybuilder.prom-query-editor-selector.title-parsing-error-switch-builder', 'Parsing error: Switch to the builder mode?' )} - body="There is a syntax error, or the query structure cannot be visualized when switching to the builder mode. Parts of the query may be lost. " - confirmText="Continue" + body={t( + 'grafana-prometheus.querybuilder.prom-query-editor-selector.body-syntax-error', + 'There is a syntax error, or the query structure cannot be visualized when switching to the builder mode. Parts of the query may be lost.' + )} + confirmText={t('grafana-prometheus.querybuilder.prom-query-editor-selector.confirmText-continue', 'Continue')} onConfirm={() => { changeEditorMode(query, QueryEditorMode.Builder, onChange); setParseModalOpen(false); diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx index a793b2df79b..da6edf4d2c4 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx @@ -16,14 +16,31 @@ export interface PromQueryLegendEditorProps { onRunQuery: () => void; } -const legendModeOptions = [ +const getLegendModeOptions = () => [ { - label: 'Auto', + label: t('grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.label-auto', 'Auto'), value: LegendFormatMode.Auto, - description: 'Only includes unique labels', + description: t( + 'grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.description-auto', + 'Only includes unique labels' + ), + }, + { + label: t('grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.label-verbose', 'Verbose'), + value: LegendFormatMode.Verbose, + description: t( + 'grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.description-verbose', + 'All label names and values' + ), + }, + { + label: t('grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.label-custom', 'Custom'), + value: LegendFormatMode.Custom, + description: t( + 'grafana-prometheus.prom-query-legend-editor.get-legend-mode-options.description-custom', + 'Provide a naming template' + ), }, - { label: 'Verbose', value: LegendFormatMode.Verbose, description: 'All label names and values' }, - { label: 'Custom', value: LegendFormatMode.Custom, description: 'Provide a naming template' }, ]; /** @@ -33,6 +50,7 @@ export const PromQueryLegendEditor = React.memo( ({ legendFormat, onChange, onRunQuery }) => { const mode = getLegendMode(legendFormat); const inputRef = useRef(null); + const legendModeOptions = getLegendModeOptions(); const onLegendFormatChanged = (evt: React.FormEvent) => { let newFormat = evt.currentTarget.value; @@ -124,6 +142,7 @@ function getLegendMode(legendFormat: string | undefined) { } export function getLegendModeLabel(legendFormat: string | undefined) { + const legendModeOptions = getLegendModeOptions(); const mode = getLegendMode(legendFormat); if (mode !== LegendFormatMode.Custom) { return legendModeOptions.find((x) => x.value === mode)?.label; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx index 0b0951cac5a..d57c654f13b 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx @@ -1,4 +1,5 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle.tsx +import { t } from '@grafana/i18n'; import { RadioButtonGroup } from '@grafana/ui'; import { QueryEditorMode } from './types'; @@ -8,12 +9,17 @@ export interface Props { onChange: (mode: QueryEditorMode) => void; } -const editorModes = [ - { label: 'Builder', value: QueryEditorMode.Builder }, - { label: 'Code', value: QueryEditorMode.Code }, -]; - export function QueryEditorModeToggle({ mode, onChange }: Props) { + const editorModes = [ + { + label: t('grafana-prometheus.querybuilder.query-editor-mode-toggle.editor-modes.label-builder', 'Builder'), + value: QueryEditorMode.Builder, + }, + { + label: t('grafana-prometheus.querybuilder.query-editor-mode-toggle.editor-modes.label-code', 'Code'), + value: QueryEditorMode.Code, + }, + ]; return (
diff --git a/packages/grafana-sql/src/components/QueryHeader.tsx b/packages/grafana-sql/src/components/QueryHeader.tsx index de27061e390..240b73a3c27 100644 --- a/packages/grafana-sql/src/components/QueryHeader.tsx +++ b/packages/grafana-sql/src/components/QueryHeader.tsx @@ -28,11 +28,6 @@ export interface QueryHeaderProps { queryRowFilter: QueryRowFilter; } -const editorModes = [ - { label: 'Builder', value: EditorMode.Builder }, - { label: 'Code', value: EditorMode.Code }, -]; - export function QueryHeader({ db, dialect, @@ -51,6 +46,14 @@ export function QueryHeader({ const htmlId = useId(); + const editorModes = [ + { + label: t('grafana-sql.components.query-header.editor-modes.label-builder', 'Builder'), + value: EditorMode.Builder, + }, + { label: t('grafana-sql.components.query-header.editor-modes.label-code', 'Code'), value: EditorMode.Code }, + ]; + const onEditorModeChange = useCallback( (newEditorMode: EditorMode) => { if (newEditorMode === EditorMode.Code) { diff --git a/packages/grafana-sql/src/locales/en-US/grafana-sql.json b/packages/grafana-sql/src/locales/en-US/grafana-sql.json index d9cb9d1fa61..bc986568f18 100644 --- a/packages/grafana-sql/src/locales/en-US/grafana-sql.json +++ b/packages/grafana-sql/src/locales/en-US/grafana-sql.json @@ -55,6 +55,10 @@ }, "query-header": { "content-invalid-query": "Your query is invalid. Check below for details. <1>However, you can still run this query.", + "editor-modes": { + "label-builder": "Builder", + "label-code": "Code" + }, "label-dataset": "Dataset", "label-filter": "Filter", "label-format": "Format", diff --git a/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx b/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx index ceebc2502b5..9122239ea0f 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx @@ -1,3 +1,5 @@ +import { t } from '@grafana/i18n'; + import { ComponentSize } from '../../types/size'; import { Button } from '../Button/Button'; @@ -18,7 +20,7 @@ export interface Props { export const DeleteButton = ({ size, disabled, onConfirm, 'aria-label': ariaLabel, closeOnConfirm }: Props) => { return ( { const { onChange, width, autoFocus = false, onBlur, value, disabled = false, inputId } = props; + const weekStarts: ComboboxOption[] = useMemo( + () => [ + { value: '', label: t('grafana-ui.week-start-picker.weekStarts-label-default', 'Default') }, + { value: 'saturday', label: t('grafana-ui.week-start-picker.weekStarts-label-saturday', 'Saturday') }, + { value: 'sunday', label: t('grafana-ui.week-start-picker.weekStarts-label-sunday', 'Sunday') }, + { value: 'monday', label: t('grafana-ui.week-start-picker.weekStarts-label-monday', 'Monday') }, + ], + [] + ); const onChangeWeekStart = useCallback( (selectable: ComboboxOption | null) => { @@ -63,7 +67,7 @@ export const WeekStartPicker = (props: Props) => { [onChange] ); - const selected = useMemo(() => weekStarts.find((item) => item.value === value)?.value ?? '', [value]); + const selected = useMemo(() => weekStarts.find((item) => item.value === value)?.value ?? '', [value, weekStarts]); return (
{loadingState === LoadingState.Loading ? ( - + ) : null}
@@ -362,7 +365,11 @@ export function PanelChrome({ {statusMessage && (
- +
)} @@ -380,7 +387,11 @@ export function PanelChrome({ > {statusMessage && (
- +
)} diff --git a/public/app/core/components/FolderFilter/FolderFilter.tsx b/public/app/core/components/FolderFilter/FolderFilter.tsx index cbd4e574235..b427eed4569 100644 --- a/public/app/core/components/FolderFilter/FolderFilter.tsx +++ b/public/app/core/components/FolderFilter/FolderFilter.tsx @@ -46,7 +46,7 @@ export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JS loadOptions={debouncedLoadOptions} maxMenuHeight={maxMenuHeight} placeholder={t('folder-filter.select-placeholder', 'Filter by folder')} - noOptionsMessage="No folders found" + noOptionsMessage={t('folder-filter.noOptionsMessage-no-folders-found', 'No folders found')} prefix={} aria-label={t('folder-filter.select-aria-label', 'Folder filter')} defaultOptions diff --git a/public/app/core/components/Select/MetricSelect.tsx b/public/app/core/components/Select/MetricSelect.tsx index 771cf1aa6d2..d1655efb3f9 100644 --- a/public/app/core/components/Select/MetricSelect.tsx +++ b/public/app/core/components/Select/MetricSelect.tsx @@ -2,6 +2,7 @@ import { flatten } from 'lodash'; import { useCallback, useMemo } from 'react'; import { SelectableValue } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; import { Variable } from 'app/types/templates'; @@ -32,7 +33,7 @@ export const MetricSelect = (props: Props) => { isSearchable={isSearchable} maxMenuHeight={500} placeholder={placeholder} - noOptionsMessage="No options found" + noOptionsMessage={t('metric-select.noOptionsMessage-no-options-found', 'No options found')} value={selected} /> ); diff --git a/public/app/core/components/Select/OrgPicker.tsx b/public/app/core/components/Select/OrgPicker.tsx index af7759901b5..bc064c83d98 100644 --- a/public/app/core/components/Select/OrgPicker.tsx +++ b/public/app/core/components/Select/OrgPicker.tsx @@ -67,7 +67,7 @@ export function OrgPicker({ onSelected, className, inputId, autoFocus, excludeOr }} value={selected} placeholder={t('org-picker.select-placeholder', 'Select organization')} - noOptionsMessage="No organizations found" + noOptionsMessage={t('org-picker.noOptionsMessage-no-organizations-found', 'No organizations found')} /> ); } diff --git a/public/app/core/components/Select/ServiceAccountPicker.tsx b/public/app/core/components/Select/ServiceAccountPicker.tsx index 09f045c544f..b86a451dea1 100644 --- a/public/app/core/components/Select/ServiceAccountPicker.tsx +++ b/public/app/core/components/Select/ServiceAccountPicker.tsx @@ -67,7 +67,10 @@ export class ServiceAccountPicker extends Component { loadOptions={this.search} onChange={onSelected} placeholder={t('service-account-picker.select-placeholder', 'Start typing to search for service accounts')} - noOptionsMessage="No service accounts found" + noOptionsMessage={t( + 'service-account-picker.noOptionsMessage-no-service-accounts-found', + 'No service accounts found' + )} aria-label={t('service-account-picker.select-aria-label', 'Service account picker')} />
diff --git a/public/app/core/components/Select/TeamPicker.tsx b/public/app/core/components/Select/TeamPicker.tsx index d189e46877c..b3608b80105 100644 --- a/public/app/core/components/Select/TeamPicker.tsx +++ b/public/app/core/components/Select/TeamPicker.tsx @@ -84,7 +84,7 @@ export class TeamPicker extends Component { onChange={onSelected} className={className} placeholder={t('team-picker.select-placeholder', 'Select a team')} - noOptionsMessage="No teams found" + noOptionsMessage={t('team-picker.noOptionsMessage-no-teams-found', 'No teams found')} aria-label={t('team-picker.select-aria-label', 'Team picker')} />
diff --git a/public/app/core/components/Select/UserPicker.tsx b/public/app/core/components/Select/UserPicker.tsx index 91158b6901f..0b1b5cb129f 100644 --- a/public/app/core/components/Select/UserPicker.tsx +++ b/public/app/core/components/Select/UserPicker.tsx @@ -67,7 +67,7 @@ export class UserPicker extends Component { loadOptions={this.search} onChange={onSelected} placeholder={t('user-picker.select-placeholder', 'Start typing to search for user')} - noOptionsMessage="No users found" + noOptionsMessage={t('user-picker.noOptionsMessage-no-users-found', 'No users found')} aria-label={t('user-picker.select-aria-label', 'User picker')} />
diff --git a/public/app/features/admin/AdminFeatureTogglesTable.tsx b/public/app/features/admin/AdminFeatureTogglesTable.tsx index 3f97bb526c3..65b6112652c 100644 --- a/public/app/features/admin/AdminFeatureTogglesTable.tsx +++ b/public/app/features/admin/AdminFeatureTogglesTable.tsx @@ -186,7 +186,7 @@ export function AdminFeatureTogglesTable({ featureToggles, allowEditing, onUpdat

} - confirmText="Save changes" + confirmText={t('admin.admin-feature-toggles-table.confirmText-save-changes', 'Save changes')} onConfirm={async () => { showSaveChangesModal(false)(); handleSaveChanges(); diff --git a/public/app/features/admin/AdminOrgsTable.tsx b/public/app/features/admin/AdminOrgsTable.tsx index e11fde93557..9feba68ae31 100644 --- a/public/app/features/admin/AdminOrgsTable.tsx +++ b/public/app/features/admin/AdminOrgsTable.tsx @@ -72,7 +72,7 @@ function AdminOrgsTableComponent({ orgs, onDelete }: Props) { } - confirmText="Delete" + confirmText={t('admin.admin-orgs-table.confirmText-delete', 'Delete')} onDismiss={() => setDeleteOrg(undefined)} onConfirm={() => { onDelete(deleteOrg.id); diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index f17cb6290d9..6aac4d1721c 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -240,7 +240,7 @@ class UnThemedOrgRow extends PureComponent { {canRemoveFromOrg && ( ) : ( {t('admin.user-permissions.change-button', 'Change')} diff --git a/public/app/features/admin/UserProfile.tsx b/public/app/features/admin/UserProfile.tsx index bed64e97a3e..9b9522a084a 100644 --- a/public/app/features/admin/UserProfile.tsx +++ b/public/app/features/admin/UserProfile.tsx @@ -142,8 +142,8 @@ export function UserProfile({ @@ -162,8 +162,8 @@ export function UserProfile({ @@ -290,7 +290,7 @@ export class UserProfileRow extends PureComponent { {canLogout && ( @@ -115,8 +115,11 @@ class BaseUserSessions extends PureComponent { diff --git a/public/app/features/admin/Users/OrgUsersTable.tsx b/public/app/features/admin/Users/OrgUsersTable.tsx index 9e2ba45de63..d83485531db 100644 --- a/public/app/features/admin/Users/OrgUsersTable.tsx +++ b/public/app/features/admin/Users/OrgUsersTable.tsx @@ -261,8 +261,10 @@ export const OrgUsersTable = ({ {Boolean(userToRemove) && ( { setUserToRemove(null); diff --git a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx index 90cbb059d9f..5a2a7ddc83e 100644 --- a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx +++ b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import { ComponentProps, useMemo } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { InlineField, Select, SelectMenuOptions, useStyles2 } from '@grafana/ui'; import { useAlertmanager } from '../state/AlertmanagerContext'; @@ -44,7 +45,10 @@ export const AlertManagerPicker = ({ disabled = false }: Props) => { } }} options={options} - noOptionsMessage="No datasources found" + noOptionsMessage={t( + 'alerting.alert-manager-picker.noOptionsMessage-no-datasources-found', + 'No datasources found' + )} value={selectedAlertmanager} getOptionLabel={(o) => o.label} components={{ Option: CustomOption }} diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx index 57a7a4f48a6..40a7afeddeb 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx @@ -100,7 +100,7 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade openExportDrawer(name)} @@ -159,7 +159,7 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade > { await deleteMuteTiming.execute({ diff --git a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx index 963ee48e6e0..ea00e22b280 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx @@ -102,8 +102,12 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => { setTemplateToDelete(undefined)} /> diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx index e77a425b6d5..de3cd0e3503 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx @@ -142,7 +142,10 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe 'alerting.template-selector.template-options.label.select-notification-template', 'Select notification template' ), - ariaLabel: 'Select notification template', + ariaLabel: t( + 'alerting.template-selector.template-options.ariaLabel.select-notification-template', + 'Select notification template' + ), value: 'Existing', description: `Select an existing notification template and preview it, or copy it to paste it in the custom tab. ${templateOption === 'Existing' ? 'Clicking Save saves your changes to the selected template.' : ''}`, }, diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx index 38dda76f142..50163827bdb 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx @@ -213,7 +213,10 @@ export function GrafanaEvaluationBehaviorStep({ isLoading={loadingGroups} invalid={Boolean(folder?.uid) && !group && Boolean(fieldState.error)} cacheOptions - loadingMessage={'Loading groups...'} + loadingMessage={t( + 'alerting.grafana-evaluation-behavior-step.loadingMessage-loading-groups', + 'Loading groups...' + )} defaultValue={defaultGroupValue} options={groupOptions} getOptionLabel={(option: SelectableValue) => ( diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 8f60198e042..43579acbcc7 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -718,7 +718,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod
} - confirmText="Deactivate" + confirmText={t('alerting.query-and-expressions-step.confirmText-deactivate', 'Deactivate')} icon="exclamation-triangle" onConfirm={() => { setValue('editorSettings.simplifiedQueryEditor', true); diff --git a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx index e6b3f93bde4..a26a9709d38 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx @@ -82,7 +82,7 @@ export const useDeleteModal = (redirectToListView = false): DeleteModalHook => { 'Deleting this rule will permanently remove it from your alert rule list. Are you sure you want to delete this rule?' ) } - confirmText="Yes, delete" + confirmText={t('alerting.use-delete-modal.modal.confirmText-yes-delete', 'Yes, delete')} icon="exclamation-triangle" onConfirm={deleteRule} onDismiss={dismissModal} diff --git a/public/app/features/alerting/unified/components/rules/CloneRule.tsx b/public/app/features/alerting/unified/components/rules/CloneRule.tsx index 175612dde1b..a5be76cf102 100644 --- a/public/app/features/alerting/unified/components/rules/CloneRule.tsx +++ b/public/app/features/alerting/unified/components/rules/CloneRule.tsx @@ -56,7 +56,7 @@ export function RedirectToCloneRule({

} - confirmText="Copy" + confirmText={t('alerting.redirect-to-clone-rule.confirmText-copy', 'Copy')} onConfirm={() => setStage('redirect')} onDismiss={onDismiss} /> diff --git a/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx b/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx index a12cc9aae21..2d30594b2e2 100644 --- a/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx +++ b/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx @@ -177,7 +177,10 @@ export const MultipleDataSourcePicker = (props: MultipleDataSourcePickerProps) = openMenuOnFocus={openMenuOnFocus} maxMenuHeight={500} placeholder={placeholder} - noOptionsMessage="No datasources found" + noOptionsMessage={t( + 'alerting.multiple-data-source-picker.noOptionsMessage-no-datasources-found', + 'No datasources found' + )} value={value ?? []} invalid={Boolean(state?.error) || Boolean(props.invalid)} getOptionLabel={(o) => { diff --git a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.tsx b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.tsx index b70c903024f..c42566d7610 100644 --- a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.tsx +++ b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.tsx @@ -209,7 +209,7 @@ export default function AlertmanagerConfig({ alertmanagerName, onDismiss, onSave 'Reset Alertmanager configuration' )} body={confirmationText} - confirmText="Yes, reset configuration" + confirmText={t('alerting.alertmanager-config.confirmText-yes-reset-configuration', 'Yes, reset configuration')} onConfirm={() => { onReset(alertmanagerName); setShowResetConfirmation(false); diff --git a/public/app/features/alerting/unified/components/settings/VersionManager.tsx b/public/app/features/alerting/unified/components/settings/VersionManager.tsx index 981af90cee4..10b09a4809d 100644 --- a/public/app/features/alerting/unified/components/settings/VersionManager.tsx +++ b/public/app/features/alerting/unified/components/settings/VersionManager.tsx @@ -242,8 +242,14 @@ const AlertmanagerConfigurationVersionManager = ({ { if (activeRestoreVersion) { restoreVersion(activeRestoreVersion); diff --git a/public/app/features/annotations/components/AnnotationResultMapper.tsx b/public/app/features/annotations/components/AnnotationResultMapper.tsx index a78711d465a..475659c7620 100644 --- a/public/app/features/annotations/components/AnnotationResultMapper.tsx +++ b/public/app/features/annotations/components/AnnotationResultMapper.tsx @@ -10,7 +10,7 @@ import { AnnotationEventFieldSource, getValueFormat, } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; +import { Trans, t } from '@grafana/i18n'; import { Select, Tooltip, Icon } from '@grafana/ui'; import { annotationEventNames, AnnotationFieldInfo } from '../standardAnnotationSupport'; @@ -171,7 +171,10 @@ export class AnnotationFieldMapper extends PureComponent { onChange={(v: SelectableValue) => { this.onFieldNameChange(row.key, v); }} - noOptionsMessage="Unknown field names" + noOptionsMessage={t( + 'annotations.annotation-field-mapper.noOptionsMessage-unknown-field-names', + 'Unknown field names' + )} allowCustomValue={true} isClearable /> diff --git a/public/app/features/auth-config/ProviderConfigForm.tsx b/public/app/features/auth-config/ProviderConfigForm.tsx index 8f1e3f80b91..861fe6acde9 100644 --- a/public/app/features/auth-config/ProviderConfigForm.tsx +++ b/public/app/features/auth-config/ProviderConfigForm.tsx @@ -263,7 +263,7 @@ export const ProviderConfigForm = ({ config, provider, isLoading }: ProviderConf } - confirmText="Reset" + confirmText={t('auth-config.provider-config-form.confirmText-reset', 'Reset')} onDismiss={() => setResetConfig(false)} onConfirm={async () => { await onResetConfig(); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx index e186910de28..27f434da0a9 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx @@ -135,8 +135,11 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro 'dashboard-scene.panel-data-transformations-tab-rendered.title-delete-all-transformations', 'Delete all transformations?' )} - body="By deleting all transformations, you will go back to the main selection screen." - confirmText="Delete all" + body={t( + 'dashboard-scene.panel-data-transformations-tab-rendered.body-delete-all-transformations', + 'By deleting all transformations, you will go back to the main selection screen.' + )} + confirmText={t('dashboard-scene.panel-data-transformations-tab-rendered.confirmText-delete-all', 'Delete all')} onConfirm={() => { model.onChangeTransformations([]); setConfirmModalOpen(false); diff --git a/public/app/features/dashboard-scene/saving/useSaveDashboard.ts b/public/app/features/dashboard-scene/saving/useSaveDashboard.ts index 8977abc2365..97a50aa3621 100644 --- a/public/app/features/dashboard-scene/saving/useSaveDashboard.ts +++ b/public/app/features/dashboard-scene/saving/useSaveDashboard.ts @@ -1,6 +1,7 @@ import { useAsyncFn } from 'react-use'; import { locationUtil } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { locationService, reportInteraction } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; @@ -59,7 +60,7 @@ export function useSaveDashboard(isCopy = false) { // important that these happen before location redirect below appEvents.publish(new DashboardSavedEvent()); - notifyApp.success('Dashboard saved'); + notifyApp.success(t('dashboard-scene.use-save-dashboard.message-dashboard-saved', 'Dashboard saved')); //Update local storage dashboard to handle things like last used datasource updateDashboardUidLastUsedDatasource(resultData.uid); diff --git a/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.tsx b/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.tsx index 735c81c8401..b384ecacbbf 100644 --- a/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.tsx +++ b/public/app/features/dashboard-scene/scene/GoToSnapshotOriginButton.tsx @@ -48,7 +48,7 @@ const onOpenSnapshotOriginalDashboard = (originalUrl: string) => { ), confirmVariant: 'primary', - confirmText: 'Proceed', + confirmText: t('dashboard-scene.on-open-snapshot-original-dashboard.confirmText.proceed', 'Proceed'), onConfirm: () => { window.location.href = sanitizedAppUrl.href; }, diff --git a/public/app/features/dashboard-scene/scene/UnlinkModal.tsx b/public/app/features/dashboard-scene/scene/UnlinkModal.tsx index d092436c6d5..51dbd4b045d 100644 --- a/public/app/features/dashboard-scene/scene/UnlinkModal.tsx +++ b/public/app/features/dashboard-scene/scene/UnlinkModal.tsx @@ -12,9 +12,11 @@ export const UnlinkModal = ({ isOpen, onConfirm, onDismiss }: Props) => { { onConfirm(); onDismiss(); diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index ed7c74ee3e0..228240f721b 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -35,17 +35,6 @@ import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } fr export interface GeneralSettingsEditViewState extends DashboardEditViewState {} -const EDITABLE_OPTIONS = [ - { label: 'Editable', value: true }, - { label: 'Read-only', value: false }, -]; - -const GRAPH_TOOLTIP_OPTIONS = [ - { value: 0, label: 'Default' }, - { value: 1, label: 'Shared crosshair' }, - { value: 2, label: 'Shared Tooltip' }, -]; - export class GeneralSettingsEditView extends SceneObjectBase implements DashboardEditView @@ -176,6 +165,37 @@ export class GeneralSettingsEditView const { intervals } = model.getRefreshPicker().useState(); const { hideTimeControls } = model.getDashboardControls().useState(); const { enabled: liveNow } = model.getLiveNowTimer().useState(); + const EDITABLE_OPTIONS = [ + { + label: t('dashboard-scene.general-settings-edit-view.editable_options.label.editable', 'Editable'), + value: true, + }, + { + label: t('dashboard-scene.general-settings-edit-view.editable_options.label.readonly', 'Read-only'), + value: false, + }, + ]; + + const GRAPH_TOOLTIP_OPTIONS = [ + { + value: 0, + label: t('dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.default', 'Default'), + }, + { + value: 1, + label: t( + 'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-crosshair', + 'Shared crosshair' + ), + }, + { + value: 2, + label: t( + 'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-tooltip', + 'Shared tooltip' + ), + }, + ]; return ( diff --git a/public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx b/public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx index fd784a09c8f..f82a9e78e61 100644 --- a/public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx +++ b/public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx @@ -7,11 +7,6 @@ import { CollapsableSection, TagsInput, Select, Field, Input, Checkbox, Button } import { LINK_ICON_MAP, NEW_LINK } from './utils'; -const linkTypeOptions = [ - { value: 'dashboards', label: 'Dashboards' }, - { value: 'link', label: 'Link' }, -]; - const linkIconOptions = Object.keys(LINK_ICON_MAP).map((key) => ({ label: key, value: key })); interface DashboardLinkFormProps { @@ -21,6 +16,13 @@ interface DashboardLinkFormProps { } export function DashboardLinkForm({ link, onUpdate, onGoBack }: DashboardLinkFormProps) { + const linkTypeOptions = [ + { + value: 'dashboards', + label: t('dashboard-scene.dashboard-link-form.link-type-options.label.dashboards', 'Dashboards'), + }, + { value: 'link', label: t('dashboard-scene.dashboard-link-form.link-type-options.label.link', 'Link') }, + ]; const onTagsChange = (tags: string[]) => { onUpdate({ ...link, tags: tags }); }; diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx index a783a201324..a261cd00852 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx @@ -85,7 +85,7 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete General ) : ( - `Run query` + t('dashbaord-scene.variable-editor-form.run-query', 'Run query') )} )} diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx index 56a1ef550ea..1340fe923c0 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx @@ -122,8 +122,15 @@ export function VariableEditorListRow({ diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx index 2d2beb67ff4..fcdf7881fce 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx @@ -119,7 +119,7 @@ export function AdHocVariableForm({ {datasourceSupported && onAllowCustomValueChange && ( Data source options diff --git a/public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx index 56858da935e..7043e029d1b 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx @@ -31,7 +31,7 @@ export function SelectionOptionsForm({ )} diff --git a/public/app/features/dashboard-scene/settings/variables/components/TextBoxVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/TextBoxVariableForm.tsx index 57158f8820a..d5dd3579124 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/TextBoxVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/TextBoxVariableForm.tsx @@ -25,7 +25,7 @@ export function TextBoxVariableForm({ defaultValue, value, onChange, onBlur, inl ) { - const value = useMemo(() => HIDE_OPTIONS.find((o) => o.value === hide)?.value ?? HIDE_OPTIONS[0].value, [hide]); + const HIDE_OPTIONS = useMemo( + () => [ + { + label: t('dashboard-scene.variable-hide-select.hide_options.label.nothing', 'Nothing'), + value: VariableHide.dontHide, + }, + { + label: t('dashboard-scene.variable-hide-select.hide_options.label.variable', 'Variable'), + value: VariableHide.hideVariable, + }, + { + label: t('dashboard-scene.variable-hide-select.hide_options.label.label', 'Label'), + value: VariableHide.hideLabel, + }, + ], + [] + ); + const value = useMemo( + () => HIDE_OPTIONS.find((o) => o.value === hide)?.value ?? HIDE_OPTIONS[0].value, + [hide, HIDE_OPTIONS] + ); if (type === 'constant') { return null; diff --git a/public/app/features/dashboard-scene/settings/variables/components/VariableTypeSelect.tsx b/public/app/features/dashboard-scene/settings/variables/components/VariableTypeSelect.tsx index 69a111f1cf2..3408469a6e2 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/VariableTypeSelect.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/VariableTypeSelect.tsx @@ -2,6 +2,7 @@ import { PropsWithChildren, useMemo } from 'react'; import { SelectableValue, VariableType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { t } from '@grafana/i18n'; import { VariableSelectField } from 'app/features/dashboard-scene/settings/variables/components/VariableSelectField'; import { EditableVariableType, getVariableTypeSelectOptions } from '../utils'; @@ -20,7 +21,7 @@ export function VariableTypeSelect({ onChange, type }: PropsWithChildren) return ( diff --git a/public/app/features/dashboard-scene/settings/version-history/RevertDashboardModal.tsx b/public/app/features/dashboard-scene/settings/version-history/RevertDashboardModal.tsx index ac45683edbc..535f191c393 100644 --- a/public/app/features/dashboard-scene/settings/version-history/RevertDashboardModal.tsx +++ b/public/app/features/dashboard-scene/settings/version-history/RevertDashboardModal.tsx @@ -44,7 +44,11 @@ export const RevertDashboardModal = ({ hideModal, onRestore, version }: RevertDa

} - confirmText={`Yes, restore to version ${version.version}`} + confirmText={t( + 'dashboard-scene.revert-dashboard-modal.confirmText-restore-version', + 'Yes, restore to version {{version}}', + { version: version.version } + )} /> ); }; diff --git a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx index ba750d48f7d..f95912f333d 100644 --- a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx +++ b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx @@ -65,7 +65,7 @@ export const VersionHistoryTable = ({ versions, canCompare, onCheck, onRestore } {version.message} {idx === 0 ? ( - + ) : ( {({ showModal, hideModal }) => ( diff --git a/public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx b/public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx index 1c2ab358818..303492f492f 100644 --- a/public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx +++ b/public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx @@ -47,7 +47,10 @@ export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }: isOpen: true, title: t('dashboard-scene.config-public-dashboard.title.revoke-public-url', 'Revoke public URL'), icon: 'trash-alt', - confirmText: 'Revoke public URL', + confirmText: t( + 'dashboard-scene.config-public-dashboard.confirmText.revoke-public-url', + 'Revoke public URL' + ), body: (

diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index c7aef4cb7ac..b4a6e81d6cd 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -117,7 +117,7 @@ export const DashNav = memo((props) => { ), confirmVariant: 'primary', - confirmText: 'Proceed', + confirmText: t('dashboard.dash-nav.on-open-snapshot-original.confirmText.proceed', 'Proceed'), onConfirm: gotoSnapshotOrigin, }, }) diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index 317b5734635..d7b32cfabb2 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -29,12 +29,6 @@ import { SettingsPageProps } from './types'; export type Props = SettingsPageProps & ConnectedProps; -const GRAPH_TOOLTIP_OPTIONS = [ - { value: 0, label: 'Default' }, - { value: 1, label: 'Shared crosshair' }, - { value: 2, label: 'Shared Tooltip' }, -]; - export function GeneralSettingsUnconnected({ dashboard, updateTimeZone, @@ -44,6 +38,20 @@ export function GeneralSettingsUnconnected({ const [renderCounter, setRenderCounter] = useState(0); const [dashboardTitle, setDashboardTitle] = useState(dashboard.title); const [dashboardDescription, setDashboardDescription] = useState(dashboard.description); + const GRAPH_TOOLTIP_OPTIONS = [ + { value: 0, label: t('dashboard.general-settings-unconnected.graph_tooltip_options.label.default', 'Default') }, + { + value: 1, + label: t( + 'dashboard.general-settings-unconnected.graph_tooltip_options.label.shared-crosshair', + 'Shared crosshair' + ), + }, + { + value: 2, + label: t('dashboard.general-settings-unconnected.graph_tooltip_options.label.shared-tooltip', 'Shared tooltip'), + }, + ]; const pageNav = sectionNav.node.parentItem; diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index 72df57aeb96..ddf13763d79 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -49,7 +49,7 @@ export const SaveDashboardErrorProxy = ({ } - confirmText="Save and overwrite" + confirmText={t('dashboard.save-dashboard-error-proxy.confirmText-save-and-overwrite', 'Save and overwrite')} onConfirm={async () => { await onDashboardSave(dashboardSaveModel, { overwrite: true }, dashboard); onDismiss(); @@ -90,7 +90,10 @@ export const SaveDashboardErrorProxy = ({ } - confirmText="Save and overwrite" + confirmText={t( + 'dashboard.save-dashboard-error-proxy.confirmText-save-and-overwrite', + 'Save and overwrite' + )} onConfirm={async () => { await onDashboardSave(dashboardSaveModel, { overwrite: true }, dashboard); onDismiss(); diff --git a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx index bf03c12b87b..18435bc1e31 100644 --- a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx @@ -2,6 +2,7 @@ import { cloneDeep } from 'lodash'; import { useAsyncFn } from 'react-use'; import { locationUtil } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { locationService, reportInteraction } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import appEvents from 'app/core/app_events'; @@ -54,7 +55,7 @@ export const useDashboardSave = (isCopy = false) => { // important that these happen before location redirect below appEvents.publish(new DashboardSavedEvent()); - notifyApp.success('Dashboard saved'); + notifyApp.success(t('dashboard.save-dashboard.message-dashboard-saved', 'Dashboard saved')); //Update local storage dashboard to handle things like last used datasource updateDashboardUidLastUsedDatasource(result.uid); diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index 70910408020..b77e0f41620 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -197,8 +197,11 @@ export const TransformationOperationRow = ({ title={t('dashboard.transformation-operation-row.title-delete', 'Delete {{name}}?', { name: uiConfig.name, })} - body="Note that removing one transformation may break others. If there is only a single transformation, you will go back to the main selection screen." - confirmText="Delete" + body={t( + 'dashboard.transformation-operation-row.body-delete', + 'Note that removing one transformation may break others. If there is only a single transformation, you will go back to the main selection screen.' + )} + confirmText={t('dashboard.transformation-operation-row.render-actions.confirmText-delete', 'Delete')} onConfirm={() => { setShowDeleteModal(false); onRemove(index); diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index f3cd3798045..17d18efba74 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -397,8 +397,11 @@ class UnThemedTransformationsEditor extends React.PureComponent this.onTransformationRemoveAll()} onDismiss={() => this.setState({ showRemoveAllModal: false })} /> diff --git a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx index 61ee6af4cf6..9650d46f822 100644 --- a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx +++ b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx @@ -34,7 +34,11 @@ export const RevertDashboardModal = ({ hideModal, id, version }: RevertDashboard

} - confirmText={`Yes, restore to version ${version}`} + confirmText={t( + 'dashboard.revert-dashboard-modal.confirmText-restore-version', + 'Yes, restore to version {{version}}', + { version } + )} /> ); }; diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx index 7c25150f7b8..59c74d1d308 100644 --- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx +++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx @@ -63,7 +63,7 @@ export const VersionHistoryTable = ({ versions, canCompare, onCheck }: VersionsT {version.message} {idx === 0 ? ( - + ) : ( {({ showModal, hideModal }) => ( diff --git a/public/app/features/dimensions/editors/ColorDimensionEditor.tsx b/public/app/features/dimensions/editors/ColorDimensionEditor.tsx index 70f750c3cf3..a8c7d5af8c5 100644 --- a/public/app/features/dimensions/editors/ColorDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ColorDimensionEditor.tsx @@ -74,7 +74,7 @@ export const ColorDimensionEditor = (props: StandardEditorProps diff --git a/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx b/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx index 89b8f319c8e..a88c7334571 100644 --- a/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx @@ -107,7 +107,7 @@ export const ScalarDimensionEditor = ({ value, context, onChange, item }: Props) value={selectedOption} options={selectOptions} onChange={onSelectChange} - noOptionsMessage="No fields found" + noOptionsMessage={t('dimensions.scalar-dimension-editor.noOptionsMessage-no-fields-found', 'No fields found')} />
diff --git a/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx b/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx index 098ac6e038b..d5eb712d47d 100644 --- a/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx @@ -104,7 +104,7 @@ export const ScaleDimensionEditor = (props: StandardEditorProps
diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/Actions/TracePageActions.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/Actions/TracePageActions.tsx index 48b0b0f8f69..e1448a77f69 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/Actions/TracePageActions.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/Actions/TracePageActions.tsx @@ -88,7 +88,7 @@ export default function TracePageActions(props: TracePageActionsProps) { diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx index 9110b547f6e..0708a574c17 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -211,7 +211,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { />
setSpanFiltersSearch({ ...search, from: val })} isInvalidError="Invalid duration" // eslint-disable-next-line @grafana/i18n/no-untranslated-strings @@ -228,7 +228,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { value={search.toOperator} /> setSpanFiltersSearch({ ...search, to: val })} isInvalidError="Invalid duration" // eslint-disable-next-line @grafana/i18n/no-untranslated-strings diff --git a/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx b/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx index 3e48d672a3f..c90a9fb2128 100644 --- a/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx @@ -1,3 +1,4 @@ +import { t } from '@grafana/i18n'; import { ConfirmModal } from '@grafana/ui'; import { PanelModel } from '../../../dashboard/state/PanelModel'; @@ -19,7 +20,11 @@ export const ChangeLibraryPanelModal = ({ onConfirm, onDismiss, panel }: ChangeL { isOpen={!!removeSnapshot} icon="trash-alt" title={t('manage-dashboards.snapshot-list-table.title-delete', 'Delete')} - body={`Are you sure you want to delete '${removeSnapshot?.name}'?`} - confirmText="Delete" + body={t( + 'manage-dashboards.snapshot-list-table.body-delete', + "Are you sure you want to delete '{{snapshotToRemove}}'?", + { snapshotToRemove: removeSnapshot?.name } + )} + confirmText={t('manage-dashboards.snapshot-list-table.confirmText-delete', 'Delete')} onDismiss={() => setRemoveSnapshot(undefined)} onConfirm={() => { doRemoveSnapshot(removeSnapshot!); diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx index 288eba13443..b4774e0dadd 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx @@ -139,8 +139,11 @@ export function InstallControlsButton({ title={t('plugins.install-controls-button.title-uninstall-modal', 'Uninstall {{plugin}}', { plugin: plugin.name, })} - body="Are you sure you want to uninstall this plugin?" - confirmText="Confirm" + body={t( + 'plugins.install-controls-button.uninstall-controls.body-uninstall-plugin', + 'Are you sure you want to uninstall this plugin?' + )} + confirmText={t('plugins.install-controls-button.uninstall-controls.confirmText-confirm', 'Confirm')} icon="exclamation-triangle" onConfirm={onUninstall} onDismiss={hideConfirmModal} diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.tsx index 056ce1fa22f..188ecccf65b 100644 --- a/public/app/features/plugins/admin/components/VersionInstallButton.tsx +++ b/public/app/features/plugins/admin/components/VersionInstallButton.tsx @@ -121,7 +121,11 @@ export const VersionInstallButton = ({ @@ -239,8 +245,14 @@ export const ServiceAccountPageUnconnected = ({ 'serviceaccounts.service-account-page-unconnected.title-disable-service-account', 'Disable service account' )} - body="Are you sure you want to disable this service account?" - confirmText="Disable service account" + body={t( + 'serviceaccounts.service-account-page-unconnected.body-disable-service-account', + 'Are you sure you want to disable this service account?' + )} + confirmText={t( + 'serviceaccounts.service-account-page-unconnected.confirmText-disable-service-account', + 'Disable service account' + )} onConfirm={handleServiceAccountDisable} onDismiss={showDisableServiceAccountModal(false)} /> diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx index 6a8292abd9c..ff6218e43e4 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx @@ -1,5 +1,4 @@ import { css } from '@emotion/css'; -import pluralize from 'pluralize'; import { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; @@ -268,15 +267,25 @@ export const ServiceAccountsListPageUnconnected = ({ <> diff --git a/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx b/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx index a855b936e61..428c78175a8 100644 --- a/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx +++ b/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx @@ -85,7 +85,7 @@ export const ServiceAccountProfileRow = ({ label, value, inputType, disabled, on {onChange && ( > = [ - { label: 'Include', value: FilterByValueType.include }, - { label: 'Exclude', value: FilterByValueType.exclude }, -]; - -const filterMatch: Array> = [ - { label: 'Match all', value: FilterByValueMatch.all }, - { label: 'Match any', value: FilterByValueMatch.any }, -]; - export const FilterByValueTransformerEditor = (props: TransformerUIProps) => { const { input, options, onChange } = props; const fieldsInfo = useFieldsInfo(input); + const filterTypes: Array> = [ + { + label: t('transformers.filter-by-value-transformer-editor.filter-types.label.include', 'Include'), + value: FilterByValueType.include, + }, + { + label: t('transformers.filter-by-value-transformer-editor.filter-types.label.exclude', 'Exclude'), + value: FilterByValueType.exclude, + }, + ]; + + const filterMatch: Array> = [ + { + label: t('transformers.filter-by-value-transformer-editor.filter-match.label.match-all', 'Match all'), + value: FilterByValueMatch.all, + }, + { + label: t('transformers.filter-by-value-transformer-editor.filter-match.label.match-any', 'Match any'), + value: FilterByValueMatch.any, + }, + ]; + const onAddFilter = useCallback(() => { const frame = input[0]; const field = frame.fields.find((f) => f.type !== FieldType.time); diff --git a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx index 2385bc72fd4..9508dfee1e7 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -13,7 +13,7 @@ import { getTimeZones, } from '@grafana/data'; import { ConvertFieldTypeOptions, ConvertFieldTypeTransformerOptions } from '@grafana/data/internal'; -import { t } from '@grafana/i18n'; +import { t, Trans } from '@grafana/i18n'; import { Button, InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; import { allFieldTypeIconOptions, FieldNamePicker } from '@grafana/ui/internal'; import { findField } from 'app/features/dimensions/utils'; @@ -259,7 +259,9 @@ export const ConvertFieldTypeTransformerEditor = ({ 'Add a convert field type transformer' )} > - {'Convert field type'} + + Convert field type + ); diff --git a/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx b/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx index 5bfaaf40d71..4e23322f9ed 100644 --- a/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx +++ b/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx @@ -18,23 +18,29 @@ import { Select, InlineFieldRow, InlineField } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/internal'; import { NumberInput } from 'app/core/components/OptionsUI/NumberInput'; -const fieldNamePickerSettings: StandardEditorsRegistryItem = { - settings: { - width: 30, - filter: (f) => f.type === FieldType.string, - placeholderText: 'Select text field', - noFieldsMessage: 'No text fields found', - }, - name: '', - id: '', - editor: () => null, -}; - function FormatStringTransfomerEditor({ input, options, onChange, }: TransformerUIProps) { + const fieldNamePickerSettings: StandardEditorsRegistryItem = { + settings: { + width: 30, + filter: (f) => f.type === FieldType.string, + placeholderText: t( + 'transformers.format-string-transfomer-editor.field-name-picker-settings.placeholderText.select-text-field', + 'Select text field' + ), + noFieldsMessage: t( + 'transformers.format-string-transfomer-editor.field-name-picker-settings.noFieldsMessage.no-text-fields-found', + 'No text fields found' + ), + }, + name: '', + id: '', + editor: () => null, + }; + const onSelectField = useCallback( (value: string | undefined) => { const val = value ?? ''; diff --git a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx index d49720810e0..e03cfb22c7b 100644 --- a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx @@ -20,21 +20,24 @@ import { extractFieldsTransformer } from './extractFields'; import { fieldExtractors } from './fieldExtractors'; import { ExtractFieldsOptions, FieldExtractorID, JSONPath } from './types'; -const fieldNamePickerSettings: StandardEditorsRegistryItem = { - settings: { - width: 30, - placeholderText: 'Select field', - }, - name: '', - id: '', - editor: () => null, -}; - export const extractFieldsTransformerEditor = ({ input, options = { delimiter: ',' }, onChange, }: TransformerUIProps) => { + const fieldNamePickerSettings: StandardEditorsRegistryItem = { + settings: { + width: 30, + placeholderText: t( + 'transformers.extract-fields-transformer-editor.field-name-picker-settings.placeholderText.select-field', + 'Select field' + ), + }, + name: '', + id: '', + editor: () => null, + }; + const onPickSourceField = (source?: string) => { onChange({ ...options, diff --git a/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx b/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx index 054bb6874e1..cc6390d543a 100644 --- a/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx +++ b/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx @@ -19,23 +19,29 @@ import { getTransformationContent } from '../docs/getTransformationContent'; import { FieldLookupOptions, fieldLookupTransformer } from './fieldLookup'; -const fieldNamePickerSettings: StandardEditorsRegistryItem = { - settings: { - width: 30, - filter: (f) => f.type === FieldType.string, - placeholderText: 'Select text field', - noFieldsMessage: 'No text fields found', - }, - name: '', - id: '', - editor: () => null, -}; - const fieldLookupSettings = { settings: {}, } as StandardEditorsRegistryItem; export const FieldLookupTransformerEditor = ({ input, options, onChange }: TransformerUIProps) => { + const fieldNamePickerSettings: StandardEditorsRegistryItem = { + settings: { + width: 30, + filter: (f) => f.type === FieldType.string, + placeholderText: t( + 'transformers.field-lookup-transformer-editor.field-name-picker-settings.placeholderText.select-text-field', + 'Select text field' + ), + noFieldsMessage: t( + 'transformers.field-lookup-transformer-editor.field-name-picker-settings.noFieldsMessage.no-text-fields-found', + 'No text fields found' + ), + }, + name: '', + id: '', + editor: () => null, + }; + const onPickLookupField = useCallback( (value: string | undefined) => { onChange({ diff --git a/public/app/features/variables/editor/ConfirmDeleteModal.tsx b/public/app/features/variables/editor/ConfirmDeleteModal.tsx index 45f7da8c1b0..dbe8a48ea14 100644 --- a/public/app/features/variables/editor/ConfirmDeleteModal.tsx +++ b/public/app/features/variables/editor/ConfirmDeleteModal.tsx @@ -17,11 +17,13 @@ export function ConfirmDeleteModal({ varName, isOpen = false, onConfirm, onDismi isOpen={isOpen} onConfirm={onConfirm} onDismiss={onDismiss} - body={` - Are you sure you want to delete variable "${varName}"? - `} + body={t( + 'variables.confirm-delete-modal.body-delete-variable', + 'Are you sure you want to delete variable "{{variableToDelete}}"?', + { variableToDelete: varName } + )} modalClass={styles.modal} - confirmText="Delete" + confirmText={t('variables.confirm-delete-modal.confirmText-delete', 'Delete')} /> ); } diff --git a/public/app/features/variables/editor/VariableEditorEditor.tsx b/public/app/features/variables/editor/VariableEditorEditor.tsx index a145ab0c72b..07cc03bac26 100644 --- a/public/app/features/variables/editor/VariableEditorEditor.tsx +++ b/public/app/features/variables/editor/VariableEditorEditor.tsx @@ -174,7 +174,7 @@ export class VariableEditorEditorUnConnected extends PureComponent /> testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2} /> ) return ( ) { - const value = useMemo(() => SORT_OPTIONS.find((o) => o.value === sort) ?? SORT_OPTIONS[0], [sort]); + const SORT_OPTIONS = useMemo( + () => [ + { + label: t('variables.query-variable-sort-select.sort_options.label.disabled', 'Disabled'), + value: VariableSort.disabled, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.alphabetical-asc', 'Alphabetical (asc)'), + value: VariableSort.alphabeticalAsc, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.alphabetical-desc', 'Alphabetical (desc)'), + value: VariableSort.alphabeticalDesc, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.numerical-asc', 'Numerical (asc)'), + value: VariableSort.numericalAsc, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.numerical-desc', 'Numerical (desc)'), + value: VariableSort.numericalDesc, + }, + { + label: t( + 'variables.query-variable-sort-select.sort_options.label.alphabetical-caseinsensitive-asc', + 'Alphabetical (case-insensitive, asc)' + ), + value: VariableSort.alphabeticalCaseInsensitiveAsc, + }, + { + label: t( + 'variables.query-variable-sort-select.sort_options.label.alphabetical-caseinsensitive-desc', + 'Alphabetical (case-insensitive, desc)' + ), + value: VariableSort.alphabeticalCaseInsensitiveDesc, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.natural-asc', 'Natural (asc)'), + value: VariableSort.naturalAsc, + }, + { + label: t('variables.query-variable-sort-select.sort_options.label.natural-desc', 'Natural (desc)'), + value: VariableSort.naturalDesc, + }, + ], + [] + ); + + const value = useMemo(() => SORT_OPTIONS.find((o) => o.value === sort) ?? SORT_OPTIONS[0], [sort, SORT_OPTIONS]); return ( { backspaceRemovesValue={true} placeholder={t('components.azure-cheat-sheet.placeholder-all-categories', 'All categories')} isClearable={true} - noOptionsMessage="Unable to list all categories" + noOptionsMessage={t( + 'components.azure-cheat-sheet.noOptionsMessage-unable-to-list-categories', + 'Unable to list all categories' + )} formatCreateLabel={(input: string) => `Category: ${input}`} isSearchable={true} isMulti={true} diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx index adfe26927be..a54949c3187 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx @@ -15,12 +15,12 @@ export function LogsManagement({ query, onQueryChange: onChange }: AzureQueryEdi { setBasicLogsAckOpen(false); let updatedBasicLogsQuery = setBasicLogsQuery(query, true); diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx index 9e871cb419c..035a918fc8d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx @@ -19,11 +19,6 @@ interface QueryTypeFieldProps { app: CoreApp | undefined; } -const EDITOR_MODES = [ - { label: 'Builder', value: LogsEditorMode.Builder }, - { label: 'KQL', value: LogsEditorMode.Raw }, -]; - export const QueryHeader = ({ query, onQueryChange, @@ -37,6 +32,11 @@ export const QueryHeader = ({ const [showModeSwitchWarning, setShowModeSwitchWarning] = useState(false); const [pendingModeChange, setPendingModeChange] = useState(null); + const EDITOR_MODES = [ + { label: t('components.query-header.editor-modes.label-builder', 'Builder'), value: LogsEditorMode.Builder }, + { label: t('components.query-header.editor-modes.label-kql', 'KQL'), value: LogsEditorMode.Raw }, + ]; + const currentMode = query.azureLogAnalytics?.mode; const queryTypes: Array<{ value: AzureQueryType; label: string }> = [ @@ -113,10 +113,18 @@ export const QueryHeader = ({ title={t('components.query-header.title-switch-mode', 'Switch editor mode?')} body={ pendingModeChange === LogsEditorMode.Builder - ? 'Switching to Builder will discard your current KQL query and clear the KQL editor. Are you sure?' - : 'Switching to KQL will discard your current builder settings. Are you sure?' + ? t( + 'components.query-header.body-switching-to-builder', + 'Switching to Builder will discard your current KQL query and clear the KQL editor. Are you sure?' + ) + : t( + 'components.query-header.body-switching-to-kql', + 'Switching to KQL will discard your current builder settings. Are you sure?' + ) } - confirmText={`Switch to ${pendingModeChange === LogsEditorMode.Builder ? 'Builder' : 'KQL'}`} + confirmText={t('components.query-header.confirmText-switch-to', 'Switch to {{newMode}}', { + newMode: pendingModeChange === LogsEditorMode.Builder ? 'Builder' : 'KQL', + })} onConfirm={() => { if (pendingModeChange) { applyModeChange(pendingModeChange); diff --git a/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json index 1031611467c..933c052938c 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json @@ -52,6 +52,7 @@ "button-use-query": "Use this query", "label-categories": "Categories", "label-query-results": "Query results: {{numResults}}", + "noOptionsMessage-unable-to-list-categories": "Unable to list all categories", "placeholder-all-categories": "All categories", "placeholder-search-logs": "Search Logs queries", "text-loading": "Loading..." @@ -161,6 +162,8 @@ "tooltip-limit": "Restrict the number of rows returned (default is 1000)." }, "logs-management": { + "body-basic-logs-queries": "Are you sure you want to switch to Basic Logs?", + "confirmText-confirm": "Confirm", "description-basic-logs-queries": "Basic Logs queries incur cost based on the amount of data scanned.", "label-logs": "Logs", "title-basic-logs-queries": "Basic Logs Queries", @@ -194,8 +197,15 @@ }, "query-header": { "aria-label-kick-start": "Azure logs kick start your query button", + "body-switching-to-builder": "Switching to Builder will discard your current KQL query and clear the KQL editor. Are you sure?", + "body-switching-to-kql": "Switching to KQL will discard your current builder settings. Are you sure?", "button-kick-start-your-query": "Kick start your query", "button-run-query": "Run query", + "confirmText-switch-to": "Switch to {{newMode}}", + "editor-modes": { + "label-builder": "Builder", + "label-kql": "KQL" + }, "label-service": "Service", "placeholder-service": "Service...", "title-switch-mode": "Switch editor mode?" diff --git a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx index 517ba553513..c7f07fb2dba 100644 --- a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx @@ -138,7 +138,12 @@ export const StyleEditor = (props: Props) => { settings: { resourceType: 'icon', folderName: ResourceFolderName.Marker, - placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label', + placeholderText: hasTextLabel + ? t('geomap.style-editor.placeholderText-select-symbol', 'Select a symbol') + : t( + 'geomap.style-editor.placeholderText-select-symbol-or-add-text', + 'Select a symbol or add a text label' + ), placeholderValue: defaultStyleConfig.symbol.fixed, showSourceRadio: false, maxFiles, @@ -228,7 +233,12 @@ export const StyleEditor = (props: Props) => { settings: { resourceType: MediaType.Icon, folderName: ResourceFolderName.Marker, - placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label', + placeholderText: hasTextLabel + ? t('geomap.style-editor.placeholderText-select-symbol', 'Select a symbol') + : t( + 'geomap.style-editor.placeholderText-select-symbol-or-add-text', + 'Select a symbol or add a text label' + ), placeholderValue: defaultStyleConfig.symbol.fixed, showSourceRadio: false, maxFiles, diff --git a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx index 2fbe96abb83..ffd64aa03cd 100644 --- a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx @@ -5,6 +5,7 @@ import { Stroke, Style } from 'ol/style'; import Photo from 'ol-ext/style/Photo'; import { MapLayerRegistryItem, PanelData, GrafanaTheme2, EventBus, PluginState, FieldType, Field } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { FrameGeometrySourceMode, MapLayerOptions } from '@grafana/schema'; import { findField } from 'app/features/dimensions/utils'; import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource'; @@ -187,7 +188,7 @@ export const photosLayer: MapLayerRegistryItem = { name: 'Image Source field', settings: { filter: (f: Field) => f.type === FieldType.string, - noFieldsMessage: 'No string fields found', + noFieldsMessage: t('geomap.photos-layer.noFieldsMessage-no-string-fields', 'No string fields found'), }, }) .addRadio({ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index fc392dece19..e8c08c32dd0 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -84,6 +84,7 @@ "admin-feature-toggles-table": { "confirm-modal-body-1": "Some features are stable (GA) and enabled by default, whereas some are currently in their preliminary Beta phase, available for early adoption.", "confirm-modal-body-2": "We advise understanding the implications of each feature change before making modifications.", + "confirmText-save-changes": "Save changes", "get-stage-cell": { "beta": "Beta", "content-general-availability": "General availability", @@ -96,14 +97,21 @@ }, "admin-orgs-table": { "aria-label-delete-org": "Delete org", + "confirmText-delete": "Delete", "title-delete": "Delete" }, "anon-users": { "not-found": "No anonymous users found." }, "base-user-sessions": { + "body-force-logout-from-all-devices": "Are you sure you want to force logout from all devices?", + "confirmText-confirm-logout": "Confirm logout", + "confirmText-force-logout": "Force logout", "title-force-logout-from-all-devices": "Force logout from all devices" }, + "change-org-button": { + "confirmText-save": "Save" + }, "edit-org": { "access-denied": "You do not have permission to see users in this organization. To update this organization, contact your server administrator.", "heading": "Edit Organization", @@ -208,9 +216,11 @@ "not-editable": "This user's role is not editable because it is synchronized from your auth provider. Refer to the <1>Grafana authentication docs for details." }, "org-users-table": { + "body-delete": "Are you sure you want to delete user {{user}}?", "columns": { "aria-label-role": "Role" }, + "confirmText-delete": "Delete", "delete-aria-label": "Delete user: {{name}}", "title-delete": "Delete" }, @@ -241,6 +251,9 @@ "settings": { "info-description": "These system settings are defined in grafana.ini or custom.ini (or overridden in ENV variables). To change these you currently need to restart Grafana." }, + "un-themed-org-row": { + "confirmText-confirm-removal": "Confirm removal" + }, "upgrade-info": { "title": "Enterprise license" }, @@ -293,12 +306,17 @@ }, "user-permissions": { "change-button": "Change", + "confirmText-change": "Change", "grafana-admin-key": "Grafana Admin", "grafana-admin-no": "No", "grafana-admin-yes": "Yes", "title": "Permissions" }, "user-profile": { + "body-delete": "Are you sure you want to delete this user?", + "body-disable": "Are you sure you want to disable this user?", + "confirmText-delete-user": "Delete user", + "confirmText-disable-user": "Disable user", "delete-button": "Delete user", "disable-button": "Disable user", "edit-button": "Edit", @@ -312,6 +330,9 @@ "title-delete-user": "Delete user", "title-disable-user": "Disable user" }, + "user-profile-row": { + "confirmText-save": "Save" + }, "user-sessions": { "browser-column": "Browser and OS", "force-logout-all-button": "Force logout from all devices", @@ -461,6 +482,9 @@ "label-muting-grouping-and-timings-optional": "Muting, grouping and timings (optional)", "title-muting-grouping-and-timings": "Muting, grouping, and timings" }, + "alert-manager-picker": { + "noOptionsMessage-no-datasources-found": "No datasources found" + }, "alert-menu": { "copy-link": "Copy link", "duplicate": "Duplicate", @@ -550,6 +574,7 @@ "view-configuration": "View configuration" }, "alertmanager-config": { + "confirmText-yes-reset-configuration": "Yes, reset configuration", "gma-manual-configuration-description": "The internal Grafana Alertmanager configuration cannot be manually changed. To change this configuration, edit the individual resources through the UI.", "gma-manual-configuration-is-not-supported": "Manual configuration changes not supported", "message": { @@ -564,11 +589,13 @@ "title-resetting-alertmanager-configuration": "Resetting Alertmanager configuration" }, "alertmanager-configuration-version-manager": { + "body-restore-configuration-version-unsaved-changes": "Are you sure you want to restore the configuration to this version? All unsaved changes will be lost.", "columns": { "compare": "Compare", "restore": "Restore", "text-latest": "Latest" }, + "confirmText-yes-restore-configuration": "Yes, restore configuration", "loading": "Loading...", "no-previous-configurations": "No previous configurations", "this-might-take-a-while": "This might take a while...", @@ -848,8 +875,10 @@ }, "contact-point-header": { "aria-label-more-actions": "More actions for contact point \"{{contactPointName}}\"", + "ariaLabel-delete": "Delete", "button-edit": "Edit", "button-view": "View", + "export-ariaLabel-export": "Export", "export-label-export": "Export", "label-delete": "Delete", "label-manage-permissions": "Manage permissions", @@ -1384,6 +1413,7 @@ "label-disable-resolved-message": "Disable resolved message" }, "grafana-evaluation-behavior-step": { + "loadingMessage-loading-groups": "Loading groups...", "message": { "must-be-a-positive-integer": "Must be a positive integer.", "must-enter-a-group-name": "Must enter a group name" @@ -1842,7 +1872,11 @@ "other-data-sources": "Other data sources" } } - } + }, + "noOptionsMessage-no-datasources-found": "No datasources found" + }, + "mute-timing-actions-button": { + "body-delete-mute-timing": "Are you sure you would like to delete \"{{muteTiming}}\"?" }, "mute-timing-actions-buttons": { "text-disabled": "Disabled", @@ -2153,6 +2187,7 @@ "query-and-expressions-step": { "add-query": "Add query", "body-queries-expressions-configured": "Create at least one query or expression to be alerted on", + "confirmText-deactivate": "Deactivate", "expressions": "Expressions", "loading-data-sources": "Loading data sources...", "manipulate-returned-queries-other-operations": "Manipulate data returned from queries with math and other operations.", @@ -2220,6 +2255,7 @@ "redirect-to-clone-rule": { "body-evaluation-group": "You will need to set a new evaluation group for the copied rule because the original one has been provisioned and cannot be used for rules created in the UI.", "body-not-provisioned": "The new rule will <1>not be marked as a provisioned rule.", + "confirmText-copy": "Copy", "title-copy-provisioned-alert-rule": "Copy provisioned alert rule" }, "redirect-to-rule-viewer": { @@ -2766,6 +2802,9 @@ "existing-templates-selector-placeholder-choose-notification-template": "Choose notification template", "loading": "Loading...", "template-options": { + "ariaLabel": { + "select-notification-template": "Select notification template" + }, "label": { "select-notification-template": "Select notification template" } @@ -2792,6 +2831,8 @@ }, "templates-table": { "actions": "Actions", + "body-delete-template-group": "Are you sure you want to delete template group \"{{template}}\"?", + "confirmText-yes-delete": "Yes, delete", "no-templates-defined": "No templates defined.", "template-group": "Template group", "title-delete-template-group": "Delete template group" @@ -2919,6 +2960,11 @@ "title-delete-contact-point": "Delete contact point" } }, + "use-delete-modal": { + "modal": { + "confirmText-yes-delete": "Yes, delete" + } + }, "use-delete-policy-modal": { "modal-element": { "title-delete-notification-policy": "Delete notification policy" @@ -3075,7 +3121,8 @@ "annotation-field-mapper": { "annotation": "Annotation", "first-value": "First value", - "from": "From" + "from": "From", + "noOptionsMessage-unknown-field-names": "Unknown field names" }, "empty-state": { "button-title": "Add annotation query", @@ -3209,7 +3256,7 @@ "team-ids-github": "Integer list of Team IDs.", "team-ids-label": "Team IDs", "team-ids-numbers": "Team IDs must be numbers.", - "team-ids-other": "String list of Team Ids.", + "team-ids-other": "String list of Team IDs.", "team-ids-placeholder": "Enter Team IDs and press Enter to add", "teams-url-description": "The URL used to query for Team IDs. If not set, the default value is /teams.", "teams-url-description-oauth": "If you configure \"{{ teamsURLLabel }}\", you must also configure \"{{ teamIDsAttributePathLabel }}\".", @@ -3253,6 +3300,7 @@ "additional-actions-menu": { "label-reset-to-default-values": "Reset to default values" }, + "confirmText-reset": "Reset", "disable": "Disable", "disabling": "Disabling...", "discard": "Discard", @@ -4180,8 +4228,8 @@ } }, "dashbaord-scene": { - "interval-variable-form": { - "description-auto-option": "Dynamically calculates interval by dividing time range by the count specified" + "variable-editor-form": { + "run-query": "Run query" } }, "dashboard": { @@ -4343,6 +4391,9 @@ }, "dash-nav": { "on-open-snapshot-original": { + "confirmText": { + "proceed": "Proceed" + }, "title": { "proceed-to-external-site": "Proceed to external site?" } @@ -4557,6 +4608,13 @@ "editable": "Editable", "readonly": "Read-only" } + }, + "graph_tooltip_options": { + "label": { + "default": "Default", + "shared-crosshair": "Shared crosshair", + "shared-tooltip": "Shared tooltip" + } } }, "get-debug-dashboard": { @@ -4861,6 +4919,7 @@ }, "revert-dashboard-modal": { "body-restore-version": "Are you sure you want to restore the dashboard to version {{version}}? All unsaved changes will be lost.", + "confirmText-restore-version": "Yes, restore to version {{version}}", "title-restore-version": "Restore version" }, "row-options-button": { @@ -4911,6 +4970,9 @@ "title-not-unique": "This title is not unique" } }, + "save-dashboard": { + "message-dashboard-saved": "Dashboard saved" + }, "save-dashboard-as-button": { "save-as": "Save as" }, @@ -4945,6 +5007,7 @@ "save-dashboard-error-proxy": { "body-name-exists": "A dashboard with the same name in selected folder already exists.<1><2>Would you still like to save this dashboard?", "body-version-mismatch": "Someone else has updated this dashboard<1><2>Would you still like to save this dashboard?", + "confirmText-save-and-overwrite": "Save and overwrite", "title-name-exists": "Conflict", "title-version-mismatch": "Conflict" }, @@ -5141,7 +5204,9 @@ "label-apply-transformation-to": "Apply transformation to" }, "transformation-operation-row": { + "body-delete": "Note that removing one transformation may break others. If there is only a single transformation, you will go back to the main selection screen.", "render-actions": { + "confirmText-delete": "Delete", "title-debug": "Debug", "title-disable-transformation": "Disable transformation", "title-filter": "Filter", @@ -5163,10 +5228,14 @@ "show-images": "Show images", "title-add-another-transformation": "Add another transformation" }, + "un-theme-transformations-editor": { + "body-delete-all-transformations": "By deleting all transformations, you will go back to the main selection screen." + }, "un-themed-transformations-editor": { "actions": { "add-another-transformation": "Add another transformation" }, + "confirmText-delete-all": "Delete all", "delete-all-transformations": "Delete all transformations", "title-delete-all-transformations": "Delete all transformations?", "tooltip-clear-search": "Clear search", @@ -5203,6 +5272,7 @@ "version-history-table": { "aria-label-toggle-selection": "Toggle selection of version {{version}}", "date": "Date", + "name-latest": "Latest", "notes": "Notes", "restore": "Restore", "updated-by": "Updated by", @@ -5279,7 +5349,8 @@ "description-enables-users-custom-values": "Enables users to add custom values to the list", "description-provide-dimensions-as-csv-dimension-name-dimension-id": "Provide dimensions as CSV: {{name}}, {{value}}", "label-data-source": "Data source", - "label-use-static-key-dimensions": "Use static key dimensions" + "label-use-static-key-dimensions": "Use static key dimensions", + "name-allow-custom-values": "Allow custom values" }, "add-to-dashboard": { "message": { @@ -5352,6 +5423,9 @@ } }, "config-public-dashboard": { + "confirmText": { + "revoke-public-url": "Revoke public URL" + }, "title": { "revoke-public-url": "Revoke public URL" } @@ -5363,6 +5437,7 @@ }, "custom-variable-form": { "custom-options": "Custom options", + "name-values-separated-comma": "Values separated by comma", "selection-options": "Selection options" }, "dashboard-edit-pane-renderer": { @@ -5381,6 +5456,12 @@ "label-type": "Type", "label-url": "URL", "label-with-tags": "With tags", + "link-type-options": { + "label": { + "dashboards": "Dashboards", + "link": "Link" + } + }, "placeholder-open-dashboard": "Open dashboard" }, "dashboard-link-list": { @@ -5427,6 +5508,8 @@ "data-source-options": "Data source options", "description-instance-name-filter": "Regex filter for which data source instances to choose from in the variable value list. Leave empty for all.", "example-instance-name-filter": "Example: ", + "name-instance-name-filter": "Instance name filter", + "name-type": "Type", "selection-options": "Selection options" }, "default-grid-layout-manager": { @@ -5472,6 +5555,21 @@ "empty-transformations-message": { "add-transformation": "Add transformation" }, + "general-settings-edit-view": { + "editable_options": { + "label": { + "editable": "Editable", + "readonly": "Read-only" + } + }, + "graph_tooltip_options": { + "label": { + "default": "Default", + "shared-crosshair": "Shared crosshair", + "shared-tooltip": "Shared tooltip" + } + } + }, "get-edit-options": { "title": { "column-options": "Column options", @@ -5502,7 +5600,8 @@ "description-provide-dimensions-as-csv-dimension-name-dimension-id": "Provide dimensions as CSV: {{name}}, {{value}}", "group-by-options": "Group by options", "label-data-source": "Data source", - "label-use-static-group-by-dimensions": "Use static group dimensions" + "label-use-static-group-by-dimensions": "Use static group dimensions", + "name-allow-custom-values": "Allow custom values" }, "help-wizard": { "copy-to-clipboard": "Copy to clipboard", @@ -5538,9 +5637,14 @@ "apply": "Apply" }, "interval-variable-form": { + "description-auto-option": "Dynamically calculates interval by dividing time range by the count specified", "description-calculated-value-below-threshold": "The calculated value will not go below this threshold", "description-step-count": "How many times the current time range should be divided to calculate the value", - "interval-options": "Interval options" + "interval-options": "Interval options", + "name-auto-option": "Auto option", + "name-min-interval": "Min interval", + "name-step-count": "Step count", + "name-values": "Values" }, "json-model-edit-view": { "cancel-button": { @@ -5565,6 +5669,9 @@ "title-name-already-exists": "Name already exists" }, "on-open-snapshot-original-dashboard": { + "confirmText": { + "proceed": "Proceed" + }, "title": { "proceed-to-external-site": "Proceed to external site?" } @@ -5600,6 +5707,8 @@ }, "panel-data-transformations-tab-rendered": { "add-another-transformation": "Add another transformation", + "body-delete-all-transformations": "By deleting all transformations, you will go back to the main selection screen.", + "confirmText-delete-all": "Delete all", "delete-all-transformations": "Delete all transformations", "title-delete-all-transformations": "Delete all transformations?" }, @@ -5653,6 +5762,7 @@ "description-optional": "Optional, if you want to extract part of a series name or metric node segment.", "label-data-source": "Data source", "label-target-data-source": "Target data source", + "name-regex": "Regex", "query-options": "Query options", "selection-options": "Selection options" }, @@ -5667,6 +5777,7 @@ }, "revert-dashboard-modal": { "body-restore-version": "Are you sure you want to restore the dashboard to version {{version}}? All unsaved changes will be lost.", + "confirmText-restore-version": "Yes, restore to version {{version}}", "title-restore-version": "Restore version" }, "save-button": { @@ -5760,7 +5871,11 @@ "selection-options-form": { "description-enables-multiple-values-selected": "Enables multiple values to be selected at the same time", "description-enables-option-include-variables": "Enables an option to include all values", - "description-enables-users-custom-values": "Enables users to add custom values to the list" + "description-enables-users-custom-values": "Enables users to add custom values to the list", + "name-allow-custom-values": "Allow custom values", + "name-custom-all-value": "Custom all value", + "name-include-all-option": "Include All option", + "name-multi-value": "Multi-value" }, "share-button": { "aria-label-sharedropdownmenu": "Toggle share menu" @@ -5780,6 +5895,9 @@ "copy-to-clipboard-failed": "Copy to clipboard failed" } }, + "text-box-variable": { + "name-default-value": "Default value" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "(optional)", "text-options": "Text options" @@ -5803,6 +5921,8 @@ } }, "unlink-modal": { + "body-unlink-panel": "If you unlink this panel, you will be able to edit it without affecting any other dashboards. However, once you make a change you will not be able to revert to its original reusable panel.", + "confirmText-yes-unlink": "Yes, unlink", "title-really-unlink-panel": "Do you really want to unlink this panel?" }, "unsaved-changes-modal": { @@ -5819,6 +5939,9 @@ } } }, + "use-save-dashboard": { + "message-dashboard-saved": "Dashboard saved" + }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "This variable is not referenced by any variable or dashboard.", "aria-label-variable-referenced-other-variables-dashboard": "This variable is referenced by other variables or dashboard.", @@ -5828,10 +5951,16 @@ "variable-editor-form": { "aria-label-variable-editor-form": "Variable editor form", "back-to-list": "Back to list", + "confirmText": { + "delete-variable": "Delete variable" + }, "delete": "Delete", "description-optional-display-name": "Optional display name", "description-template-variable-characters": "The name of the template variable. (Max. 50 characters)", "general": "General", + "name-description": "Description", + "name-label": "Label", + "name-name": "Name", "placeholder-descriptive-text": "Descriptive text", "placeholder-label-name": "Label name", "placeholder-variable-name": "Variable name", @@ -5846,13 +5975,25 @@ "variable": "Variable" }, "variable-editor-list-row": { + "body-delete-variable": "Are you sure you want to delete: {{variable}}?", + "confirmText-delete-variable": "Delete variable", "title-delete-variable": "Delete variable", "tooltip-duplicate-variable": "Duplicate variable", "tooltip-remove-variable": "Remove variable" }, "variable-hide-select": { + "hide_options": { + "label": { + "label": "Label", + "nothing": "Nothing", + "variable": "Variable" + } + }, "label": "Hide" }, + "variable-type-select": { + "name-variable-type": "Variable type" + }, "variable-usages-button": { "title-show-usages": "Showing usages for: {{variableId}}", "tooltip-show-usages": "Show usages" @@ -5879,6 +6020,7 @@ "version-history-table": { "aria-label-toggle-selection": "Toggle selection of version {{version}}", "date": "Date", + "name-latest": "Latest", "notes": "Notes", "restore": "Restore", "updated-by": "Updated by", @@ -6266,7 +6408,8 @@ } }, "color-dimension-editor": { - "label-fixed-color": "Fixed color" + "label-fixed-color": "Fixed color", + "noOptionsMessage-no-fields-found": "No fields found" }, "file-dropzone-custom-children": { "upload": "Upload" @@ -6304,6 +6447,7 @@ }, "label-limit": "Limit", "label-value": "Value", + "noOptionsMessage-no-fields-found": "No fields found", "scalar-options": { "description-clamped": "Use field values, clamped to max and min", "description-mod": "Use field values, mod from max", @@ -6319,7 +6463,8 @@ }, "label-max": "Max", "label-min": "Min", - "label-value": "Value" + "label-value": "Value", + "noOptionsMessage-no-fields-found": "No fields found" }, "text-dimension-editor": { "description-field": "Display field value", @@ -6891,6 +7036,8 @@ "aria-label-select-service-name-operator": "Select service name operator", "aria-label-select-span-name": "Select span name", "aria-label-select-span-name-operator": "Select span name operator", + "ariaLabel-select-max-span-duration": "Select max span duration", + "ariaLabel-select-min-span-duration": "Select min span duration", "label-collapse": "Span Filters", "label-duration": "Duration", "label-service-name": "Service name", @@ -6961,6 +7108,8 @@ "split-widen": "Widen pane" }, "trace-page-actions": { + "ariaLabel-copy-trace-id": "Copy Trace ID", + "ariaLabel-export-trace": "Export Trace", "give-feedback": "Give feedback", "label-copied": "Copied!", "label-export": "Export", @@ -7098,6 +7247,7 @@ }, "folder-filter": { "clear-folder-button": "Clear folders", + "noOptionsMessage-no-folders-found": "No folders found", "select-aria-label": "Folder filter", "select-placeholder": "Filter by folder" }, @@ -7289,6 +7439,9 @@ "name-show-scale": "Show scale", "name-show-zoom": "Show zoom control", "name-tooltip": "Tooltip", + "photos-layer": { + "noFieldsMessage-no-string-fields": "No string fields found" + }, "plugin": { "basemap-layer-configured-server-admin": "The basemap layer is configured by the server admin." }, @@ -7312,6 +7465,8 @@ "label-text-label": "Text label", "label-x-offset": "X offset", "label-y-offset": "Y offset", + "placeholderText-select-symbol": "Select a symbol", + "placeholderText-select-symbol-or-add-text": "Select a symbol or add a text label", "vertical-align-options": { "label-bottom": "Bottom", "label-center": "Center", @@ -7585,7 +7740,8 @@ "aria-label-selected-color": "{{colorLabel}} color" }, "confirm-button": { - "cancel": "Cancel" + "cancel": "Cancel", + "confirmText-delete": "Delete" }, "confirm-content": { "placeholder": "Type \"{{confirmPromptText}}\" to confirm" @@ -7767,6 +7923,8 @@ }, "panel-chrome": { "aria-label-toggle-collapse": "toggle collapse panel", + "ariaLabel-panel-loading": "Panel loading bar", + "ariaLabel-panel-status": "Panel status", "tooltip-cancel": "Cancel query", "tooltip-cancel-loading": "Cancel query", "tooltip-stop-streaming": "Stop streaming", @@ -7934,6 +8092,12 @@ "footer-click-to-action": "Click to {{actionTitle}}", "footer-click-to-navigate": "Click to open {{linkTitle}}", "timestamp": "Timestamp" + }, + "week-start-picker": { + "weekStarts-label-default": "Default", + "weekStarts-label-monday": "Monday", + "weekStarts-label-saturday": "Saturday", + "weekStarts-label-sunday": "Sunday" } }, "graph": { @@ -8312,6 +8476,10 @@ "add-library-panel-modal": { "title-create-library-panel": "Create library panel" }, + "change-library-panel-modal": { + "confirmText-change": "Change", + "confirmText-replace": "Replace" + }, "confirm": { "delete-panel": "Do you want to delete this panel?" }, @@ -8756,6 +8924,8 @@ "updated-on": "Updated on" }, "snapshot-list-table": { + "body-delete": "Are you sure you want to delete '{{snapshotToRemove}}'?", + "confirmText-delete": "Delete", "title-delete": "Delete" }, "unthemed-dashboard-import": { @@ -8767,6 +8937,9 @@ } } }, + "metric-select": { + "noOptionsMessage-no-options-found": "No options found" + }, "migrate-to-cloud": { "build-snapshot": { "description": "This tool can migrate some resources from this installation to your cloud stack. To get started, you'll need to create a snapshot of this installation. Creating a snapshot typically takes less than two minutes. The snapshot is stored alongside this Grafana installation.", @@ -9465,7 +9638,7 @@ "marker": { "100-node-count": ">100 nodes", "aria-label-hidden-marker": "Hidden nodes marker: {{marker}}", - "node-count_one": "{{count}} node", + "node-count_one": "{{count}} nodes", "node-count_other": "{{count}} nodes" }, "node": { @@ -9475,10 +9648,10 @@ "aria-label-layered-layout-performance-warning": "Layered layout performance warning", "aria-label-nodes-hidden-warning": "Nodes hidden warning", "computing-layout": "Computing layout", - "hidden-nodes_one": "<0> {{count}} node is hidden for performance reasons.", + "hidden-nodes_one": "<0> {{count}} nodes are hidden for performance reasons.", "hidden-nodes_other": "<0> {{count}} nodes are hidden for performance reasons.", "no-data": "No data", - "processed-nodes_one": "<0> Layered layout may be slow with {{count}} node.", + "processed-nodes_one": "<0> Layered layout may be slow with {{count}} nodes.", "processed-nodes_other": "<0> Layered layout may be slow with {{count}} nodes." }, "node-graph-panel": { @@ -9605,6 +9778,7 @@ } }, "org-picker": { + "noOptionsMessage-no-organizations-found": "No organizations found", "select-placeholder": "Select organization" }, "page": { @@ -9871,8 +10045,7 @@ "update-status-text": "plugins updated" }, "versions": { - "confirmation-text-1": "Are you really sure you want to downgrade to version", - "confirmation-text-2": "You should normally not be doing this", + "confirmation-text": "Are you really sure you want to downgrade to version {{version}}? You should normally not be doing this", "downgrade-confirm": "Downgrade", "downgrade-title": "Downgrade plugin version" } @@ -9963,7 +10136,11 @@ "updating": "Updating" }, "install-controls-button": { - "title-uninstall-modal": "Uninstall {{plugin}}" + "title-uninstall-modal": "Uninstall {{plugin}}", + "uninstall-controls": { + "body-uninstall-plugin": "Are you sure you want to uninstall this plugin?", + "confirmText-confirm": "Confirm" + } }, "install-controls-warning": { "body-not-published": "This plugin is not published to <2>grafana.com/plugins and can't be managed via the catalog.", @@ -10999,6 +11176,7 @@ } }, "service-account-picker": { + "noOptionsMessage-no-service-accounts-found": "No service accounts found", "select-aria-label": "Service account picker", "select-placeholder": "Start typing to search for service accounts" }, @@ -11044,6 +11222,10 @@ }, "service-account-page-unconnected": { "add-service-account-token": "Add service account token", + "body-delete-service-account": "Are you sure you want to delete this service account?", + "body-disable-service-account": "Are you sure you want to disable this service account?", + "confirmText-delete-service-account": "Delete service account", + "confirmText-disable-service-account": "Disable service account", "delete-service-account": "Delete service account", "disable-service-account": "Disable service account", "enable-service-account": "Enable service account", @@ -11070,6 +11252,7 @@ "used-by": "Used by" }, "service-account-profile-row": { + "confirmText-save": "Save", "edit": "Edit" }, "service-account-role-row": { @@ -11083,6 +11266,12 @@ }, "service-accounts-list-page-unconnected": { "add-service-account": "Add service account", + "body-delete_one": "Are you sure you want to delete {{serviceAccountName}} and {{count}} accompanying tokens?", + "body-delete_other": "Are you sure you want to delete {{serviceAccountName}} and {{count}} accompanying tokens?", + "body-delete-with-tokens": "Are you sure you want to delete {{serviceAccountName}}?", + "body-disable-service-account": "Are you sure you want to disable '{{accountToDisable}}'?", + "confirmText-delete": "Delete", + "confirmText-disable-service-account": "Disable service account", "placeholder-search-service-account-by-name": "Search service account by name", "sub-title": "Service accounts and their tokens can be used to authenticate against the Grafana API. Find out more in our <2>documentation.", "title-delete-service-account": "Delete service account", @@ -11478,7 +11667,7 @@ "label-never": "Never" }, "status-history-panel": { - "too-many-points_one": "Too many points to visualize properly. <1>Update the query to return fewer points. <3>({{count}} point received)", + "too-many-points_one": "Too many points to visualize properly. <1>Update the query to return fewer points. <3>({{count}} points received)", "too-many-points_other": "Too many points to visualize properly. <1>Update the query to return fewer points. <3>({{count}} points received)" } }, @@ -11623,6 +11812,7 @@ "tag-option-label": "Tag option" }, "team-picker": { + "noOptionsMessage-no-teams-found": "No teams found", "select-aria-label": "Team picker", "select-placeholder": "Select a team" }, @@ -11948,6 +12138,7 @@ "convert-field-type-transformer-editor": { "aria-label-add-a-convert-field-type-transformer": "Add a convert field type transformer", "aria-label-remove-convert-field-type-transformer": "Remove convert field type transformer", + "convert-field-type": "Convert field type", "label": { "browser": "Browser", "utc": "UTC" @@ -11990,6 +12181,11 @@ "remove-enum-row-tooltip-delete": "Delete" }, "extract-fields-transformer-editor": { + "field-name-picker-settings": { + "placeholderText": { + "select-field": "Select field" + } + }, "label-delimiter": "Delimiter", "label-format": "Format", "label-keep-time": "Keep time", @@ -12003,6 +12199,14 @@ "aria-label-threshold-color": "Threshold color" }, "field-lookup-transformer-editor": { + "field-name-picker-settings": { + "noFieldsMessage": { + "no-text-fields-found": "No text fields found" + }, + "placeholderText": { + "select-text-field": "Select text field" + } + }, "label-field": "Field", "label-lookup": "Lookup" }, @@ -12028,10 +12232,30 @@ }, "filter-by-value-transformer-editor": { "add-condition": "Add condition", + "filter-match": { + "label": { + "match-all": "Match all", + "match-any": "Match any" + } + }, + "filter-types": { + "label": { + "exclude": "Exclude", + "include": "Include" + } + }, "label-conditions": "Conditions", "label-filter-type": "Filter type" }, "format-string-transfomer-editor": { + "field-name-picker-settings": { + "noFieldsMessage": { + "no-text-fields-found": "No text fields found" + }, + "placeholderText": { + "select-text-field": "Select text field" + } + }, "label-field": "Field", "label-format": "Format", "label-substring-range": "Substring range" @@ -12342,6 +12566,7 @@ "title": "Organizations" }, "user-picker": { + "noOptionsMessage-no-users-found": "No users found", "select-aria-label": "User picker", "select-placeholder": "Start typing to search for user" }, @@ -12427,6 +12652,8 @@ } }, "confirm-delete-modal": { + "body-delete-variable": "Are you sure you want to delete variable \"{{variableToDelete}}\"?", + "confirmText-delete": "Delete", "title-delete-variable": "Delete variable" }, "create-ad-hoc-variable-adapter": { @@ -12475,9 +12702,24 @@ "label-refresh": "Refresh" }, "query-variable-sort-select": { - "description-values-variable": "How to sort the values of this variable" + "description-values-variable": "How to sort the values of this variable", + "name-sort": "Sort", + "sort_options": { + "label": { + "alphabetical-asc": "Alphabetical (asc)", + "alphabetical-caseinsensitive-asc": "Alphabetical (case-insensitive, asc)", + "alphabetical-caseinsensitive-desc": "Alphabetical (case-insensitive, desc)", + "alphabetical-desc": "Alphabetical (desc)", + "disabled": "Disabled", + "natural-asc": "Natural (asc)", + "natural-desc": "Natural (desc)", + "numerical-asc": "Numerical (asc)", + "numerical-desc": "Numerical (desc)" + } + } }, "text-box-variable-editor": { + "name-default-value": "Default value", "placeholder-default-value-if-any": "default value, if any", "text-options": "Text options" }, @@ -12506,6 +12748,8 @@ "description-optional-display-name": "Optional display name", "description-template-variable-characters": "The name of the template variable. (Max. 50 characters)", "general": "General", + "name-label": "Label", + "name-name": "Name", "placeholder-descriptive-text": "Descriptive text", "placeholder-label-name": "Label name", "placeholder-variable-name": "Variable name", @@ -12520,9 +12764,15 @@ "tooltip-duplicate-variable": "Duplicate variable", "tooltip-remove-variable": "Remove variable" }, + "variable-editor-un-connected": { + "name-description": "Description" + }, "variable-options": { "aria-label-toggle-all-values": "Toggle all values" }, + "variable-type-select": { + "name-select-variable-type": "Select variable type" + }, "variable-usages-button": { "tooltip-show-usages": "Show usages" }, From 381cd95932114017bffc31d5dadaff87a8df0265 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:48:05 +0100 Subject: [PATCH 61/67] Update dependency i18next to v25 (#107707) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-i18n/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 28 ++++++++++++++----- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 076daf4da1c..734881e8cde 100644 --- a/package.json +++ b/package.json @@ -340,7 +340,7 @@ "fast-json-patch": "3.1.1", "file-saver": "2.0.5", "history": "4.10.1", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "i18next-browser-languagedetector": "^8.0.0", "i18next-pseudo": "^2.2.1", "immer": "10.1.1", diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index 167cacd8e3a..9f2fd298de1 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -56,7 +56,7 @@ "@formatjs/intl-durationformat": "^0.7.0", "@typescript-eslint/utils": "^8.33.1", "fast-deep-equal": "^3.1.3", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "i18next-browser-languagedetector": "^8.0.0", "i18next-pseudo": "^2.2.1", "micro-memoize": "^4.1.2", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 0b91608ea70..9dface55147 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -88,7 +88,7 @@ "date-fns": "4.1.0", "downshift": "^9.0.6", "hoist-non-react-statics": "3.3.2", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "i18next-browser-languagedetector": "^8.0.0", "immutable": "5.1.3", "is-hotkey": "0.2.0", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index fcdc79473e9..22636f35f21 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -13,7 +13,7 @@ "@grafana/ui": "12.1.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "immer": "10.1.1", "lodash": "4.17.21", "monaco-editor": "0.34.1", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 95a21319137..d6eebbbe1ba 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -13,7 +13,7 @@ "@grafana/ui": "12.1.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "immer": "10.1.1", "lodash": "4.17.21", "monaco-editor": "0.34.1", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 9a3e6e4a211..a9851c8d91b 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -21,7 +21,7 @@ "@opentelemetry/semantic-conventions": "1.34.0", "buffer": "6.0.3", "events": "3.3.0", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "lodash": "4.17.21", "lru-cache": "11.1.0", "monaco-editor": "0.34.1", diff --git a/yarn.lock b/yarn.lock index f205a576180..8297472910f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2540,7 +2540,7 @@ __metadata: "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" fast-deep-equal: "npm:^3.1.3" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" i18next-parser: "npm:9.3.0" immer: "npm:10.1.1" lodash: "npm:4.17.21" @@ -2877,7 +2877,7 @@ __metadata: "@types/react-dom": "npm:18.3.5" debounce-promise: "npm:3.1.2" fast-deep-equal: "npm:^3.1.3" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" immer: "npm:10.1.1" lodash: "npm:4.17.21" monaco-editor: "npm:0.34.1" @@ -2932,7 +2932,7 @@ __metadata: buffer: "npm:6.0.3" events: "npm:3.3.0" glob: "npm:11.0.1" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" lodash: "npm:4.17.21" lru-cache: "npm:11.1.0" monaco-editor: "npm:0.34.1" @@ -3270,7 +3270,7 @@ __metadata: "@types/react": "npm:18.3.18" "@typescript-eslint/utils": "npm:^8.33.1" fast-deep-equal: "npm:^3.1.3" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" i18next-browser-languagedetector: "npm:^8.0.0" i18next-pseudo: "npm:^2.2.1" micro-memoize: "npm:^4.1.2" @@ -3751,7 +3751,7 @@ __metadata: expose-loader: "npm:5.0.0" fs-extra: "npm:^11.2.0" hoist-non-react-statics: "npm:3.3.2" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" i18next-browser-languagedetector: "npm:^8.0.0" immutable: "npm:5.1.3" is-hotkey: "npm:0.2.0" @@ -18296,7 +18296,7 @@ __metadata: html-loader: "npm:5.1.0" html-webpack-plugin: "npm:5.6.3" http-server: "npm:14.1.1" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" i18next-browser-languagedetector: "npm:^8.0.0" i18next-parser: "npm:9.3.0" i18next-pseudo: "npm:^2.2.1" @@ -19222,7 +19222,7 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.5.1 || ^24.2.0, i18next@npm:^24.0.0": +"i18next@npm:^23.5.1 || ^24.2.0": version: 24.2.2 resolution: "i18next@npm:24.2.2" dependencies: @@ -19236,6 +19236,20 @@ __metadata: languageName: node linkType: hard +"i18next@npm:^25.0.0": + version: 25.3.1 + resolution: "i18next@npm:25.3.1" + dependencies: + "@babel/runtime": "npm:^7.27.6" + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/f5f38af0f7b7c0055e607d4017dade5aedd1c75c44e091473b4d01537f0849e21a7ae7f66397719a2557fc53767cf9ea96a4da34127b906d3fe5ce21db895224 + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" From 757886c70493a85e9603b862effe5a025055d907 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 7 Jul 2025 17:02:59 +0100 Subject: [PATCH 62/67] Chore: ignore /mocks/** from the betterer results (#107708) ignore /mocks/** from the betterer results --- .betterer.eslint.config.js | 1 + .betterer.results | 180 ------------------------------------- 2 files changed, 1 insertion(+), 180 deletions(-) diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 8d742ae5187..bbd5842a3c8 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -104,6 +104,7 @@ module.exports = [ '**/__mocks__/**', '**/public/test/**', '**/mocks.{ts,tsx}', + '**/mocks/**/*.{ts,tsx}', '**/spec/**/*.{ts,tsx}', ], rules: { diff --git a/.betterer.results b/.betterer.results index 5b4e262dced..e84c90caa42 100644 --- a/.betterer.results +++ b/.betterer.results @@ -489,9 +489,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], - "packages/grafana-prometheus/src/test/mocks/datasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "packages/grafana-prometheus/src/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -961,9 +958,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`profiler\`)", "6"], [0, 0, 0, "Do not re-export imported variable (\`updateLegendValues\`)", "7"] ], - "public/app/core/navigation/mocks/routeProps.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/core/navigation/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1555,9 +1549,6 @@ exports[`better eslint`] = { "public/app/features/correlations/components/EmptyCorrelationsCTA.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], - "public/app/features/correlations/mocks/useCorrelations.mocks.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/correlations/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -2522,16 +2513,6 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], - "public/app/features/plugins/admin/mocks/localPlugin.mock.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/features/plugins/admin/mocks/remotePlugin.mock.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/plugins/admin/pages/Browse.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"], @@ -2882,12 +2863,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Do not use any type assertions.", "3"] ], - "public/app/features/users/mocks/userMocks.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"] - ], "public/app/features/variables/adapters.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3108,21 +3083,6 @@ exports[`better eslint`] = { [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.", "5"], [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.", "6"] ], - "public/app/plugins/datasource/azuremonitor/mocks/datasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/azuremonitor/mocks/panelData.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] - ], - "public/app/plugins/datasource/azuremonitor/mocks/utils.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], - "public/app/plugins/datasource/azuremonitor/mocks/variables.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/datasource/azuremonitor/types/query.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], @@ -3165,14 +3125,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringDatasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] - ], - "public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringInstanceSettings.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/datasource/cloud-monitoring/types/query.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], @@ -3221,124 +3173,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "public/app/plugins/datasource/cloudwatch/mocks/CloudWatchVariables.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/whitespaceQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/commentOnlyQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/newCommandQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/singleLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/sortQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/whitespaceQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/multilineQueries.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/newCommandQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/singleLineQueries.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Do not use any type assertions.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"], - [0, 0, 0, "Do not use any type assertions.", "7"], - [0, 0, 0, "Do not use any type assertions.", "8"], - [0, 0, 0, "Do not use any type assertions.", "9"], - [0, 0, 0, "Do not use any type assertions.", "10"], - [0, 0, 0, "Do not use any type assertions.", "11"], - [0, 0, 0, "Do not use any type assertions.", "12"], - [0, 0, 0, "Do not use any type assertions.", "13"], - [0, 0, 0, "Do not use any type assertions.", "14"], - [0, 0, 0, "Do not use any type assertions.", "15"], - [0, 0, 0, "Do not use any type assertions.", "16"], - [0, 0, 0, "Do not use any type assertions.", "17"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineEmptyQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineFullQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineTwoQueries.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/singleLineEmptyQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/thirdArgAfterSearchQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/withinStringQuery.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/cloudwatch/mocks/monarch/Monaco.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], "public/app/plugins/datasource/cloudwatch/types.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3411,9 +3245,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/plugins/datasource/elasticsearch/configuration/mocks/configOptions.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/datasource/elasticsearch/datasource.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3623,10 +3454,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"] ], - "public/app/plugins/datasource/influxdb/mocks/datasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], "public/app/plugins/datasource/influxdb/query_part.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3694,13 +3521,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/plugins/datasource/loki/mocks/datasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], - "public/app/plugins/datasource/loki/mocks/metadataRequest.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], From d15e1ad8d06b3092f358d7965122593d296fc3e1 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 7 Jul 2025 12:16:31 -0400 Subject: [PATCH 63/67] Alerting: Fix route validation in provisioning service (#107550) * introduce ExtraConfigsCrypto * delete getLastConfiguration and move all code into method Get * update legacy store to encrypt ExtraConfigs same way we do in MultiOrgAlertmanager * update legacy store init * delete PersistConfig * remove unnecessary unmarshalling * add better error * add tests for legacy store * add tests that cover extra config validation * add integration test for conflicting routes --------- Signed-off-by: Yuri Tseretyan --- .../ngalert/api/api_provisioning_test.go | 2 +- pkg/services/ngalert/ngalert.go | 2 +- pkg/services/ngalert/notifier/crypto.go | 22 +++-- .../ngalert/notifier/legacy_storage/config.go | 53 +++++------ .../notifier/legacy_storage/config_test.go | 71 ++++++++++++--- .../notifier/legacy_storage/testing.go | 37 ++++++++ .../ngalert/notifier/receiver_svc_test.go | 6 +- .../provisioning/contactpoints_test.go | 4 +- pkg/services/ngalert/provisioning/errors.go | 12 +++ .../provisioning/notification_policies.go | 8 +- .../notification_policies_test.go | 87 +++++++++++++++++++ pkg/services/provisioning/provisioning.go | 4 +- .../routingtree/routing_tree_test.go | 63 ++++++++++++++ 13 files changed, 317 insertions(+), 54 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 1cdacebed4e..00f57988df9 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -2034,7 +2034,7 @@ func createProvisioningSrvSut(t *testing.T) ProvisioningSrv { func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) ProvisioningSrv { t.Helper() tracer := tracing.InitializeTracerForTest() - configStore := legacy_storage.NewAlertmanagerConfigStore(env.configs) + configStore := legacy_storage.NewAlertmanagerConfigStore(env.configs, notifier.NewExtraConfigsCrypto(env.secrets)) receiverSvc := notifier.NewReceiverService( ac.NewReceiverAccess[*models.Receiver](env.ac, true), configStore, diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index ed2709cc995..c923f39fb6a 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -412,7 +412,7 @@ func (ng *AlertNG) init() error { ng.stateManager = stateManager ng.schedule = scheduler - configStore := legacy_storage.NewAlertmanagerConfigStore(ng.store) + configStore := legacy_storage.NewAlertmanagerConfigStore(ng.store, notifier.NewExtraConfigsCrypto(ng.SecretsService)) receiverService := notifier.NewReceiverService( ac.NewReceiverAccess[*models.Receiver](ng.accesscontrol, false), configStore, diff --git a/pkg/services/ngalert/notifier/crypto.go b/pkg/services/ngalert/notifier/crypto.go index 7a80a70c27a..3ac7b8e9ba0 100644 --- a/pkg/services/ngalert/notifier/crypto.go +++ b/pkg/services/ngalert/notifier/crypto.go @@ -28,16 +28,16 @@ type Crypto interface { // alertmanagerCrypto implements decryption of Alertmanager configuration and encryption of arbitrary payloads based on Grafana's encryptions. type alertmanagerCrypto struct { - secrets secrets.Service + *ExtraConfigsCrypto configs configurationStore log log.Logger } func NewCrypto(secrets secrets.Service, configs configurationStore, log log.Logger) Crypto { return &alertmanagerCrypto{ - secrets: secrets, - configs: configs, - log: log, + ExtraConfigsCrypto: NewExtraConfigsCrypto(secrets), + configs: configs, + log: log, } } @@ -241,7 +241,17 @@ func (c *alertmanagerCrypto) Decrypt(ctx context.Context, payload []byte) ([]byt return c.secrets.Decrypt(ctx, payload) } -func (c *alertmanagerCrypto) EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { +type ExtraConfigsCrypto struct { + secrets secretService +} + +func NewExtraConfigsCrypto(secrets secretService) *ExtraConfigsCrypto { + return &ExtraConfigsCrypto{ + secrets: secrets, + } +} + +func (c *ExtraConfigsCrypto) EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { for i := range config.ExtraConfigs { encryptedValue, err := c.secrets.Encrypt(ctx, []byte(config.ExtraConfigs[i].AlertmanagerConfig), secrets.WithoutScope()) if err != nil { @@ -254,7 +264,7 @@ func (c *alertmanagerCrypto) EncryptExtraConfigs(ctx context.Context, config *de return nil } -func (c *alertmanagerCrypto) DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { +func (c *ExtraConfigsCrypto) DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { for i := range config.ExtraConfigs { // Check if the config is encrypted by trying to base64 decode it encryptedValue, err := base64.StdEncoding.DecodeString(config.ExtraConfigs[i].AlertmanagerConfig) diff --git a/pkg/services/ngalert/notifier/legacy_storage/config.go b/pkg/services/ngalert/notifier/legacy_storage/config.go index 0a7e904544f..026d48f5139 100644 --- a/pkg/services/ngalert/notifier/legacy_storage/config.go +++ b/pkg/services/ngalert/notifier/legacy_storage/config.go @@ -9,6 +9,11 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" ) +type crypto interface { + EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error + DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error +} + type amConfigStore interface { GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error) UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error @@ -31,9 +36,17 @@ type ConfigRevision struct { ConcurrencyToken string Version string } +type alertmanagerConfigStoreImpl struct { + store amConfigStore + crypto crypto +} -func getLastConfiguration(ctx context.Context, orgID int64, store amConfigStore) (*ConfigRevision, error) { - alertManagerConfig, err := store.GetLatestAlertmanagerConfiguration(ctx, orgID) +func NewAlertmanagerConfigStore(store amConfigStore, crypto crypto) *alertmanagerConfigStoreImpl { + return &alertmanagerConfigStoreImpl{store: store, crypto: crypto} +} + +func (a alertmanagerConfigStoreImpl) Get(ctx context.Context, orgID int64) (*ConfigRevision, error) { + alertManagerConfig, err := a.store.GetLatestAlertmanagerConfiguration(ctx, orgID) if err != nil { return nil, err } @@ -48,6 +61,11 @@ func getLastConfiguration(ctx context.Context, orgID int64, store amConfigStore) return nil, err } + err = a.crypto.DecryptExtraConfigs(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("failed to decrypt extra configurations: %w", err) + } + return &ConfigRevision{ Config: cfg, ConcurrencyToken: concurrencyToken, @@ -55,38 +73,21 @@ func getLastConfiguration(ctx context.Context, orgID int64, store amConfigStore) }, nil } -type alertmanagerConfigStoreImpl struct { - store amConfigStore -} - -func NewAlertmanagerConfigStore(store amConfigStore) *alertmanagerConfigStoreImpl { - return &alertmanagerConfigStoreImpl{store: store} -} - -func (a alertmanagerConfigStoreImpl) Get(ctx context.Context, orgID int64) (*ConfigRevision, error) { - return getLastConfiguration(ctx, orgID, a.store) -} - func (a alertmanagerConfigStoreImpl) Save(ctx context.Context, revision *ConfigRevision, orgID int64) error { + err := a.crypto.EncryptExtraConfigs(ctx, revision.Config) + if err != nil { + return fmt.Errorf("failed to encrypt extra configurations: %w", err) + } + serialized, err := SerializeAlertmanagerConfig(*revision.Config) if err != nil { return err } - cmd := models.SaveAlertmanagerConfigurationCmd{ + return a.store.UpdateAlertmanagerConfiguration(ctx, &models.SaveAlertmanagerConfigurationCmd{ AlertmanagerConfiguration: string(serialized), ConfigurationVersion: revision.Version, FetchedConfigurationHash: revision.ConcurrencyToken, Default: false, OrgID: orgID, - } - return a.PersistConfig(ctx, &cmd) -} - -// PersistConfig validates to config before eventually persisting it if no error occurs -func (a alertmanagerConfigStoreImpl) PersistConfig(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error { - cfg := &definitions.PostableUserConfig{} - if err := json.Unmarshal([]byte(cmd.AlertmanagerConfiguration), cfg); err != nil { - return fmt.Errorf("change would result in an invalid configuration state: %w", err) - } - return a.store.UpdateAlertmanagerConfiguration(ctx, cmd) + }) } diff --git a/pkg/services/ngalert/notifier/legacy_storage/config_test.go b/pkg/services/ngalert/notifier/legacy_storage/config_test.go index 549fdab12ea..7019ad04e37 100644 --- a/pkg/services/ngalert/notifier/legacy_storage/config_test.go +++ b/pkg/services/ngalert/notifier/legacy_storage/config_test.go @@ -22,8 +22,9 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) { orgID := int64(1) t.Run("should read the latest config for giving organization", func(t *testing.T) { + cryptoMock := newFakeCrypto() storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + store := NewAlertmanagerConfigStore(storeMock, cryptoMock) expected := models.AlertConfiguration{ ID: 1, @@ -48,12 +49,18 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) { require.Equal(t, expectedCfg, *revision.Config) storeMock.AssertCalled(t, "GetLatestAlertmanagerConfiguration", mock.Anything, orgID) + + t.Run("should decrypt extra configs ", func(t *testing.T) { + require.Len(t, cryptoMock.Calls, 1) + require.Equal(t, "DecryptExtraConfigs", cryptoMock.Calls[0].Method) + require.Equal(t, &expectedCfg, cryptoMock.Calls[0].Args[1]) + }) }) t.Run("propagate errors", func(t *testing.T) { t.Run("when underlying store fails", func(t *testing.T) { storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + store := NewAlertmanagerConfigStore(storeMock, newFakeCrypto()) expectedErr := errors.New("test=err") storeMock.EXPECT().GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil, expectedErr) @@ -63,7 +70,7 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) { t.Run("return ErrNoAlertmanagerConfiguration config does not exist", func(t *testing.T) { storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + store := NewAlertmanagerConfigStore(storeMock, newFakeCrypto()) storeMock.EXPECT().GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil, nil) _, err := store.Get(context.Background(), orgID) @@ -72,7 +79,7 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) { t.Run("when config cannot be unmarshalled", func(t *testing.T) { storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + store := NewAlertmanagerConfigStore(storeMock, newFakeCrypto()) storeMock.EXPECT().GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(&models.AlertConfiguration{ AlertmanagerConfiguration: "invalid-json", }, nil) @@ -80,6 +87,23 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) { _, err := store.Get(context.Background(), orgID) require.Truef(t, ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error()) }) + + t.Run("when decrypting extra configs fails", func(t *testing.T) { + cryptoMock := newFakeCrypto() + storeMock := &MockAMConfigStore{} + store := NewAlertmanagerConfigStore(storeMock, cryptoMock) + + expectedErr := errors.New("test-err") + cryptoMock.DecryptExtraConfigsFunc = func(ctx context.Context, config *definitions.PostableUserConfig) error { + return expectedErr + } + storeMock.EXPECT().GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(&models.AlertConfiguration{ + AlertmanagerConfiguration: defaultConfig, + }, nil) + + _, err := store.Get(context.Background(), orgID) + require.ErrorIs(t, err, expectedErr) + }) }) } @@ -98,8 +122,9 @@ func TestAlertmanagerConfigStoreSave(t *testing.T) { } t.Run("should save the config to store", func(t *testing.T) { + cryptoMock := newFakeCrypto() storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + store := NewAlertmanagerConfigStore(storeMock, cryptoMock) storeMock.EXPECT().UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error { assert.Equal(t, string(expectedCfg), cmd.AlertmanagerConfiguration) @@ -114,17 +139,39 @@ func TestAlertmanagerConfigStoreSave(t *testing.T) { require.NoError(t, err) storeMock.AssertCalled(t, "UpdateAlertmanagerConfiguration", mock.Anything, mock.Anything) + + t.Run("should encrypt extra configs ", func(t *testing.T) { + require.Len(t, cryptoMock.Calls, 1) + require.Equal(t, "EncryptExtraConfigs", cryptoMock.Calls[0].Method) + require.Equal(t, &cfg, cryptoMock.Calls[0].Args[1]) + }) }) - t.Run("propagates errors when underlying storage returns error", func(t *testing.T) { - storeMock := &MockAMConfigStore{} - store := &alertmanagerConfigStoreImpl{store: storeMock} + t.Run("propagates errors", func(t *testing.T) { + t.Run("when underlying storage returns error", func(t *testing.T) { + storeMock := &MockAMConfigStore{} + store := NewAlertmanagerConfigStore(storeMock, newFakeCrypto()) - expectedErr := errors.New("test-err") - storeMock.EXPECT().UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(expectedErr) + expectedErr := errors.New("test-err") + storeMock.EXPECT().UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(expectedErr) - err := store.Save(context.Background(), &revision, orgID) + err := store.Save(context.Background(), &revision, orgID) - require.ErrorIs(t, err, expectedErr) + require.ErrorIs(t, err, expectedErr) + }) + + t.Run("when encrypting extra configs fails", func(t *testing.T) { + cryptoMock := newFakeCrypto() + storeMock := &MockAMConfigStore{} + store := NewAlertmanagerConfigStore(storeMock, cryptoMock) + + expectedErr := errors.New("test-err") + cryptoMock.EncryptExtraConfigsFunc = func(ctx context.Context, config *definitions.PostableUserConfig) error { + return expectedErr + } + + err := store.Save(context.Background(), &revision, orgID) + require.ErrorIs(t, err, expectedErr) + }) }) } diff --git a/pkg/services/ngalert/notifier/legacy_storage/testing.go b/pkg/services/ngalert/notifier/legacy_storage/testing.go index a4b5d9d5eba..59ead936575 100644 --- a/pkg/services/ngalert/notifier/legacy_storage/testing.go +++ b/pkg/services/ngalert/notifier/legacy_storage/testing.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/mock" + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" ) @@ -59,3 +60,39 @@ func (a *AlertmanagerConfigStoreFake) Save(ctx context.Context, revision *Config } return nil } + +type fakeCrypto struct { + Calls []methodCall + EncryptExtraConfigsFunc func(context.Context, *definitions.PostableUserConfig) error + DecryptExtraConfigsFunc func(context.Context, *definitions.PostableUserConfig) error +} + +func newFakeCrypto() *fakeCrypto { + return &fakeCrypto{ + Calls: []methodCall{}, + } +} + +func (f *fakeCrypto) EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { + f.Calls = append(f.Calls, methodCall{ + Method: "EncryptExtraConfigs", + Args: []interface{}{ctx, config}, + }) + + if f.EncryptExtraConfigsFunc != nil { + return f.EncryptExtraConfigsFunc(ctx, config) + } + return nil +} + +func (f *fakeCrypto) DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { + f.Calls = append(f.Calls, methodCall{ + Method: "DecryptExtraConfigs", + Args: []interface{}{ctx, config}, + }) + + if f.DecryptExtraConfigsFunc != nil { + return f.DecryptExtraConfigsFunc(ctx, config) + } + return nil +} diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index 7cab4c03442..5ed24d2db2f 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -275,7 +275,7 @@ func TestReceiverService_Delete(t *testing.T) { deleteUID: baseReceiver.UID, callerProvenance: definitions.Provenance(models.ProvenanceFile), existing: util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI))), - //expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceAPI, models.ProvenanceFile), + // expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceAPI, models.ProvenanceFile), }, { name: "delete receiver with optimistic version mismatch fails", @@ -673,7 +673,7 @@ func TestReceiverService_Update(t *testing.T) { user: writer, receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI)), existing: util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceFile))), - //expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceFile, models.ProvenanceAPI), + // expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceFile, models.ProvenanceAPI), expectedUpdate: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI), rm.Encrypted(models.Base64Enrypt)), @@ -1529,7 +1529,7 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secretService) *ReceiverS return NewReceiverService( ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), false), - legacy_storage.NewAlertmanagerConfigStore(store), + legacy_storage.NewAlertmanagerConfigStore(store, NewExtraConfigsCrypto(encryptSvc)), provisioningStore, &fakeAlertRuleNotificationStore{}, encryptSvc, diff --git a/pkg/services/ngalert/provisioning/contactpoints_test.go b/pkg/services/ngalert/provisioning/contactpoints_test.go index e042272a063..b7c2803f3c0 100644 --- a/pkg/services/ngalert/provisioning/contactpoints_test.go +++ b/pkg/services/ngalert/provisioning/contactpoints_test.go @@ -493,7 +493,7 @@ func createContactPointServiceSutWithConfigStore(t *testing.T, secretService sec receiverService := notifier.NewReceiverService( ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), true), - legacy_storage.NewAlertmanagerConfigStore(configStore), + legacy_storage.NewAlertmanagerConfigStore(configStore, notifier.NewExtraConfigsCrypto(secretService)), provisioningStore, &fakeAlertRuleNotificationStore{}, secretService, @@ -504,7 +504,7 @@ func createContactPointServiceSutWithConfigStore(t *testing.T, secretService sec ) return NewContactPointService( - legacy_storage.NewAlertmanagerConfigStore(configStore), + legacy_storage.NewAlertmanagerConfigStore(configStore, notifier.NewExtraConfigsCrypto(secretService)), secretService, provisioningStore, xact, diff --git a/pkg/services/ngalert/provisioning/errors.go b/pkg/services/ngalert/provisioning/errors.go index 3d8b4c85608..407f45efed6 100644 --- a/pkg/services/ngalert/provisioning/errors.go +++ b/pkg/services/ngalert/provisioning/errors.go @@ -37,6 +37,10 @@ var ( "Invalid format of the submitted route.", errutil.WithPublic("Invalid format of the submitted route: {{.Public.Error}}. Correct the payload and try again."), ) + + ErrRouteConflictingMatchers = errutil.BadRequest("alerting.notifications.routes.conflictingMatchers").MustTemplate("Routing tree conflicts with the external configuration", + errutil.WithPublic("Cannot add\\update route: matchers conflict with an external routing tree merging matchers {{ .Public.Matchers }}, making the added\\updated route unreachable."), + ) ) // MakeErrTimeIntervalInvalid creates an error with the ErrTimeIntervalInvalid template @@ -109,6 +113,14 @@ func MakeErrRouteInvalidFormat(err error) error { }) } +func MakeErrRouteConflictingMatchers(matchers string) error { + return ErrRouteConflictingMatchers.Build(errutil.TemplateData{ + Public: map[string]any{ + "Matchers": matchers, + }, + }) +} + func MakeErrContactPointUidExists(uid, name string) error { return ErrContactPointUidExists.Build(errutil.TemplateData{ Public: map[string]any{ diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index f6563fe45ac..220783b962c 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -3,12 +3,14 @@ package provisioning import ( "context" "encoding/binary" + "errors" "fmt" "hash" "hash/fnv" "slices" "unsafe" + "github.com/grafana/alerting/definition" "github.com/prometheus/common/model" "golang.org/x/exp/maps" @@ -113,7 +115,11 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI _, err = revision.Config.GetMergedAlertmanagerConfig() if err != nil { - return definitions.Route{}, "", fmt.Errorf("new routing tree is not compatible with extra configuration: %w", err) + if errors.Is(err, definition.ErrSubtreeMatchersConflict) { + // TODO temporarily get the conflicting matchers + return definitions.Route{}, "", MakeErrRouteConflictingMatchers(fmt.Sprintf("%s", revision.Config.ExtraConfigs[0].MergeMatchers)) + } + nps.log.Warn("Unable to validate the combined routing tree because of an error during merging. This could be a sign of broken external configuration. Skipping", "error", err) } err = nps.xact.InTransaction(ctx, func(ctx context.Context) error { diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index e52460c5c04..6a2804087bb 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -189,6 +189,93 @@ func TestUpdatePolicyTree(t *testing.T) { assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) }) + t.Run("ErrRouteConflictingMatchers if the new route has conflicting matchers ", func(t *testing.T) { + rev := getDefaultConfigRevision() + rev.Config.ExtraConfigs = append(rev.Config.ExtraConfigs, definitions.ExtraConfiguration{ + Identifier: "test", + MergeMatchers: config.Matchers{ + { + Type: labels.MatchEqual, + Name: "imported", + Value: "true", + }, + }, + AlertmanagerConfig: `{"route":{"receiver":"mimir-receiver"},"receivers":[{"name":"mimir-receiver"}]}`, + }) + + route := definitions.Route{ + Receiver: rev.Config.AlertmanagerConfig.Receivers[0].Name, + Routes: []*definitions.Route{ + { + ObjectMatchers: definitions.ObjectMatchers{ + { + Type: labels.MatchEqual, + Name: "imported", + Value: "true", + }, + { + Type: labels.MatchEqual, + Name: "label", + Value: "value", + }, + }, + }, + }, + } + + sut, store, _ := createNotificationPolicyServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return &rev, nil + } + + _, _, err := sut.UpdatePolicyTree(context.Background(), orgID, route, models.ProvenanceAPI, defaultVersion) + require.ErrorIs(t, err, ErrRouteConflictingMatchers) + }) + + t.Run("should ignore extra config validation if it is invalid", func(t *testing.T) { + extra := definitions.ExtraConfiguration{ + MergeMatchers: config.Matchers{ + { + Type: labels.MatchEqual, + Name: "imported", + Value: "true", + }, + }, + } + rev := getDefaultConfigRevision() + rev.Config.ExtraConfigs = append(rev.Config.ExtraConfigs, extra) + + route := definitions.Route{ + Receiver: rev.Config.AlertmanagerConfig.Receivers[0].Name, + Routes: []*definitions.Route{ + { + ObjectMatchers: definitions.ObjectMatchers{ + { + Type: labels.MatchEqual, + Name: "imported", + Value: "true", + }, + { + Type: labels.MatchEqual, + Name: "label", + Value: "value", + }, + }, + }, + }, + } + + sut, store, _ := createNotificationPolicyServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return &rev, nil + } + + result, version, err := sut.UpdatePolicyTree(context.Background(), orgID, route, models.ProvenanceAPI, defaultVersion) + require.NoError(t, err) + assert.Equal(t, route, result) + assert.Equal(t, calculateRouteFingerprint(route), version) + }) + t.Run("updates Route and sets provenance in transaction if route is valid and version matches", func(t *testing.T) { sut, store, prov := createNotificationPolicyServiceSut() store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 8edca88d855..6972eb22213 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -294,7 +294,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error ps.alertingStore, ps.alertingStore, ps.folderService, - //ps.dashboardService, + // ps.dashboardService, ps.quotaService, ps.SQLStore, int64(ps.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()), @@ -304,7 +304,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error notifier.NewCachedNotificationSettingsValidationService(ps.alertingStore), alertingauthz.NewRuleService(ps.ac), ) - configStore := legacy_storage.NewAlertmanagerConfigStore(ps.alertingStore) + configStore := legacy_storage.NewAlertmanagerConfigStore(ps.alertingStore, notifier.NewExtraConfigsCrypto(ps.secretService)) receiverSvc := notifier.NewReceiverService( alertingauthz.NewReceiverAccess[*ngmodels.Receiver](ps.ac, true), configStore, diff --git a/pkg/tests/apis/alerting/notifications/routingtree/routing_tree_test.go b/pkg/tests/apis/alerting/notifications/routingtree/routing_tree_test.go index 56a9ecf2706..37a982a65f5 100644 --- a/pkg/tests/apis/alerting/notifications/routingtree/routing_tree_test.go +++ b/pkg/tests/apis/alerting/notifications/routingtree/routing_tree_test.go @@ -663,3 +663,66 @@ func TestIntegrationDataConsistency(t *testing.T) { assert.ElementsMatch(t, expected, tree.Spec.Routes[0].Matchers) }) } + +func TestIntegrationExtraConfigsConflicts(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx := context.Background() + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + EnableFeatureToggles: []string{"alertingImportAlertmanagerAPI"}, + }) + + cliCfg := helper.Org1.Admin.NewRestConfig() + legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password) + + client := common.NewRoutingTreeClient(t, helper.Org1.Admin) + + // Now upload a new extra config + testAlertmanagerConfigYAML := ` +route: + receiver: default + +receivers: +- name: default + webhook_configs: + - url: 'http://localhost/webhook' +` + + headers := map[string]string{ + "Content-Type": "application/yaml", + "X-Grafana-Alerting-Config-Identifier": "external-system", + "X-Grafana-Alerting-Merge-Matchers": "imported=true", + } + + // Post the configuration to Grafana + response := legacyCli.ConvertPrometheusPostAlertmanagerConfig(t, definitions.AlertmanagerUserConfig{ + AlertmanagerConfig: testAlertmanagerConfigYAML, + }, headers) + require.Equal(t, "success", response.Status) + + current, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{}) + require.NoError(t, err) + updated := current.Copy().(*v0alpha1.RoutingTree) + updated.Spec.Routes = append(updated.Spec.Routes, v0alpha1.RoutingTreeRoute{ + Matchers: []v0alpha1.RoutingTreeMatcher{ + { + Label: "imported", + Type: v0alpha1.RoutingTreeMatcherTypeEqual, + Value: "true", + }, + }, + }) + + _, err = client.Update(ctx, updated, v1.UpdateOptions{}) + require.Error(t, err) + require.Truef(t, errors.IsBadRequest(err), "Should get BadRequest error but got: %s", err) + + // Now delete extra config + legacyCli.ConvertPrometheusDeleteAlertmanagerConfig(t, headers) + + // and try again + _, err = client.Update(ctx, updated, v1.UpdateOptions{}) + require.NoError(t, err) +} From c93fd3ee9eb652cc2afbc72f6c4ad341828ebe92 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 7 Jul 2025 12:17:41 -0400 Subject: [PATCH 64/67] Alerting: idempotent decrypt\encrypt operations on extra configs (#107592) * add prefix to encrypted string to distinguish between unencrypted and encrypted ones --------- Co-authored-by: Alexander Akhmetov --- pkg/services/ngalert/notifier/crypto.go | 29 ++++- pkg/services/ngalert/notifier/crypto_test.go | 106 +++++++++++++++++++ 2 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 pkg/services/ngalert/notifier/crypto_test.go diff --git a/pkg/services/ngalert/notifier/crypto.go b/pkg/services/ngalert/notifier/crypto.go index 3ac7b8e9ba0..5d682ad8f62 100644 --- a/pkg/services/ngalert/notifier/crypto.go +++ b/pkg/services/ngalert/notifier/crypto.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -14,6 +15,16 @@ import ( "github.com/grafana/grafana/pkg/services/secrets" ) +const ( + // encryptedContentPrefix is a marker that identifies encrypted Alertmanager configurations. + // When this prefix is present at the beginning of a configuration string: + // 1. During encryption: It indicates the content is already encrypted and should be skipped + // 2. During decryption: It indicates the content (minus this prefix) should be base64 decoded + // and then decrypted using the secrets service + // This prefix helps maintain idempotency in encryption/decryption operations. + cryptoPrefix = "crypto_" +) + // Crypto allows decryption of Alertmanager Configuration and encryption of arbitrary payloads. type Crypto interface { LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver) error @@ -253,12 +264,17 @@ func NewExtraConfigsCrypto(secrets secretService) *ExtraConfigsCrypto { func (c *ExtraConfigsCrypto) EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { for i := range config.ExtraConfigs { + // If it has prefix, consider it encrypted already + if strings.HasPrefix(config.ExtraConfigs[i].AlertmanagerConfig, cryptoPrefix) { + continue + } + encryptedValue, err := c.secrets.Encrypt(ctx, []byte(config.ExtraConfigs[i].AlertmanagerConfig), secrets.WithoutScope()) if err != nil { return fmt.Errorf("failed to encrypt extra configuration: %w", err) } - config.ExtraConfigs[i].AlertmanagerConfig = base64.StdEncoding.EncodeToString(encryptedValue) + config.ExtraConfigs[i].AlertmanagerConfig = cryptoPrefix + base64.StdEncoding.EncodeToString(encryptedValue) } return nil @@ -266,12 +282,15 @@ func (c *ExtraConfigsCrypto) EncryptExtraConfigs(ctx context.Context, config *de func (c *ExtraConfigsCrypto) DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error { for i := range config.ExtraConfigs { - // Check if the config is encrypted by trying to base64 decode it - encryptedValue, err := base64.StdEncoding.DecodeString(config.ExtraConfigs[i].AlertmanagerConfig) - if err != nil { - // If it can't be base64 decoded, assume it's already decrypted and skip + // If it does not have prefix, consider it decrypted already + if !strings.HasPrefix(config.ExtraConfigs[i].AlertmanagerConfig, cryptoPrefix) { continue } + // Check if the config is encrypted by trying to base64 decode it + encryptedValue, err := base64.StdEncoding.DecodeString(config.ExtraConfigs[i].AlertmanagerConfig[len(cryptoPrefix):]) + if err != nil { + return fmt.Errorf("failed to decode extra configuration: %w", err) + } decryptedValue, err := c.secrets.Decrypt(ctx, encryptedValue) if err != nil { diff --git a/pkg/services/ngalert/notifier/crypto_test.go b/pkg/services/ngalert/notifier/crypto_test.go new file mode 100644 index 00000000000..c50d3d2967a --- /dev/null +++ b/pkg/services/ngalert/notifier/crypto_test.go @@ -0,0 +1,106 @@ +package notifier + +import ( + "context" + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/secrets/fakes" +) + +func TestEncryptExtraConfigs(t *testing.T) { + config := "plain-text-config" + encryptedConfig := base64.StdEncoding.EncodeToString([]byte(config)) + tests := []struct { + name string + inputConfig string + expectedConfig string + }{ + { + name: "Encrypts unencrypted configs", + inputConfig: config, + expectedConfig: cryptoPrefix + encryptedConfig, + }, + { + name: "Skips already encrypted configs", + inputConfig: cryptoPrefix + "very-encrypted-data", + expectedConfig: cryptoPrefix + "very-encrypted-data", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := fakes.NewFakeSecretsService() + + c := &alertmanagerCrypto{ + secrets: m, + } + + cfg := &definitions.PostableUserConfig{ + ExtraConfigs: []definitions.ExtraConfiguration{ + {AlertmanagerConfig: tt.inputConfig}, + }, + } + + err := c.EncryptExtraConfigs(context.Background(), cfg) + + require.NoError(t, err) + require.Equal(t, tt.expectedConfig, cfg.ExtraConfigs[0].AlertmanagerConfig) + }) + } +} + +func TestDecryptExtraConfigs(t *testing.T) { + decryptedData := "derypted-data" + decryptedDataBase64 := base64.StdEncoding.EncodeToString([]byte(decryptedData)) + tests := []struct { + name string + inputConfig string + expectedError string + expectedConfig string + }{ + { + name: "Decrypts encrypted configs", + inputConfig: cryptoPrefix + decryptedDataBase64, + expectedConfig: decryptedData, + }, + { + name: "Skips already encrypted configs", + inputConfig: "very-decrypted-data", + expectedConfig: "very-decrypted-data", + }, + { + name: "Fails if not base64 encoded", + inputConfig: cryptoPrefix + "plain-text-config", + expectedError: "failed to decode extra configuration", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := fakes.NewFakeSecretsService() + c := &alertmanagerCrypto{ + secrets: m, + } + + cfg := &definitions.PostableUserConfig{ + ExtraConfigs: []definitions.ExtraConfiguration{ + {AlertmanagerConfig: tt.inputConfig}, + }, + } + + err := c.DecryptExtraConfigs(context.Background(), cfg) + + if tt.expectedError != "" { + require.ErrorContains(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expectedConfig, cfg.ExtraConfigs[0].AlertmanagerConfig) + }) + } +} From 34c7ea9c2833c4f2978ce64eb95fce542ee3d856 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:18:37 +0000 Subject: [PATCH 65/67] Update dependency marked to v16 (#107717) * Update dependency marked to v16 * mark marked as esmodule for jest --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- jest.config.js | 1 + package.json | 2 +- packages/grafana-data/package.json | 2 +- yarn.lock | 12 ++++++------ 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/jest.config.js b/jest.config.js index a57ea5c2067..60d5a141652 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,6 +14,7 @@ const esModules = [ 'robust-predicates', 'leven', 'nanoid', + 'marked', 'monaco-promql', '@kusto/monaco-kusto', 'monaco-editor', diff --git a/package.json b/package.json index 734881e8cde..5bc553728f6 100644 --- a/package.json +++ b/package.json @@ -358,7 +358,7 @@ "lru-cache": "11.1.0", "lru-memoize": "^1.1.0", "lucene": "^2.1.1", - "marked": "15.0.12", + "marked": "16.0.0", "memoize-one": "6.0.0", "micro-memoize": "^4.1.2", "ml-regression-polynomial": "^3.0.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index cf92ac3299f..56263ed62a2 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -68,7 +68,7 @@ "fast_array_intersect": "1.1.0", "history": "4.10.1", "lodash": "4.17.21", - "marked": "15.0.12", + "marked": "16.0.0", "marked-mangle": "1.1.11", "moment": "2.30.1", "moment-timezone": "0.5.47", diff --git a/yarn.lock b/yarn.lock index 8297472910f..896049380f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3083,7 +3083,7 @@ __metadata: fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" lodash: "npm:4.17.21" - marked: "npm:15.0.12" + marked: "npm:16.0.0" marked-mangle: "npm:1.1.11" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" @@ -18327,7 +18327,7 @@ __metadata: lru-cache: "npm:11.1.0" lru-memoize: "npm:^1.1.0" lucene: "npm:^2.1.1" - marked: "npm:15.0.12" + marked: "npm:16.0.0" memoize-one: "npm:6.0.0" micro-memoize: "npm:^4.1.2" mini-css-extract-plugin: "npm:2.9.2" @@ -22423,12 +22423,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:15.0.12": - version: 15.0.12 - resolution: "marked@npm:15.0.12" +"marked@npm:16.0.0": + version: 16.0.0 + resolution: "marked@npm:16.0.0" bin: marked: bin/marked.js - checksum: 10/deeb619405c0c46af00c99b18b3365450abeb309104b24e3658f46142344f6b7c4117608c3b5834084d8738e92f81240c19f596e6ee369260f96e52b3457eaee + checksum: 10/aed6501ff5d49def83f0c79fab698666338a555cae32f83ba52369c9a04aa8be4fa4c908367d12f81f7d1c1a83d89ffe47aa1e7b023da308591e03ea82d5ca6d languageName: node linkType: hard From 7181f8d89d91817aae374b76286caed2ae40b279 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Mon, 7 Jul 2025 11:36:41 -0500 Subject: [PATCH 66/67] TableNG: Update description for wrapHeaderText (#107722) --- packages/grafana-schema/src/common/common.gen.ts | 2 +- packages/grafana-schema/src/common/table.cue | 2 +- public/app/plugins/panel/table/table-new/module.tsx | 5 +---- public/locales/en-US/grafana.json | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index 9e1d0fd99a8..4c1fd34f48e 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -965,7 +965,7 @@ export interface TableFieldOptions { minWidth?: number; width?: number; /** - * Enables text wrapping for the display name in the table header. + * Enables text wrapping for column headers */ wrapHeaderText?: boolean; } diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue index bdb537c8504..ca61617aedb 100644 --- a/packages/grafana-schema/src/common/table.cue +++ b/packages/grafana-schema/src/common/table.cue @@ -105,7 +105,7 @@ TableFieldOptions: { filterable?: bool // Hides any header for a column, useful for columns that show some static content or buttons. hideHeader?: bool - // Enables text wrapping for the display name in the table header. + // Enables text wrapping for column headers wrapHeaderText?: bool } @cuetsy(kind="interface") diff --git a/public/app/plugins/panel/table/table-new/module.tsx b/public/app/plugins/panel/table/table-new/module.tsx index ddd3c7480eb..a40d4c0b047 100644 --- a/public/app/plugins/panel/table/table-new/module.tsx +++ b/public/app/plugins/panel/table/table-new/module.tsx @@ -105,10 +105,7 @@ export const plugin = new PanelPlugin(TablePanel) .addBooleanSwitch({ path: 'wrapHeaderText', name: t('table.name-wrap-header-text', 'Wrap header text'), - description: t( - 'table.description-wrap-header-text', - 'Enables text wrapping for the field name in the table header' - ), + description: t('table.description-wrap-header-text', 'Enables text wrapping for column headers'), category, defaultValue: defaultTableFieldOptions.wrapHeaderText, }) diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index e8c08c32dd0..2a3a370bb07 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11740,7 +11740,7 @@ "description-count-rows": "Display a single count for all data rows", "description-fields": "Select the fields that should be calculated", "description-min-column-width": "The minimum width for column auto resizing", - "description-wrap-header-text": "Enables text wrapping for the field name in the table header", + "description-wrap-header-text": "Enables text wrapping for column headers", "image-cell-options-editor": { "description-alt-text": "Alternative text that will be displayed if an image can't be displayed or for users who use a screen reader", "description-title-text": "Text that will be displayed when the image is hovered by a cursor", From 0f2284d5e6cc130ba4a7cf449aa00fddd8b93b82 Mon Sep 17 00:00:00 2001 From: Kristina Date: Mon, 7 Jul 2025 12:05:55 -0500 Subject: [PATCH 67/67] Transformations: Reduce > Reduce fields should not persist labels between reducers (#107590) Reduce fields should not persist labels updates between reducers --- .../transformers/reduce.test.ts | 60 ++++++++++++++++++- .../transformations/transformers/reduce.ts | 3 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index bae31b5b8f9..f50072e0f30 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -8,7 +8,7 @@ import { notTimeFieldMatcher } from '../matchers/predicates'; import { transformDataFrame } from '../transformDataFrame'; import { DataTransformerID } from './ids'; -import { reduceFields, reduceTransformer, ReduceTransformerOptions } from './reduce'; +import { reduceFields, reduceTransformer, ReduceTransformerMode, ReduceTransformerOptions } from './reduce'; const seriesAWithSingleField = toDataFrame({ name: 'A', @@ -582,4 +582,62 @@ describe('Reducer Transformer', () => { `); }); }); + + it('reduce fields mode keeps distinct label values with multiple reducers', async () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.reduce, + options: { + mode: ReduceTransformerMode.ReduceFields, + reducers: [ReducerID.max, ReducerID.count], + labelsToFields: false, + }, + }; + + const seriesA = toDataFrame({ + length: 3, + fields: [{ name: 'value', config: {}, labels: { category: 'apple' }, type: FieldType.number, values: [3, 4, 5] }], + }); + + const seriesB = toDataFrame({ + fields: [ + { name: 'value', config: {}, labels: { category: 'orange' }, type: FieldType.number, values: [6, 7, 8] }, + ], + }); + + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith((received) => { + const processed = received[0]; + + expect(processed.length).toEqual(2); + expect(processed[0].fields).toMatchInlineSnapshot(` + [ + { + "config": {}, + "labels": { + "category": "apple", + "reducer": "Max", + }, + "name": "value", + "state": undefined, + "type": "number", + "values": [ + 5, + ], + }, + { + "config": {}, + "labels": { + "category": "apple", + "reducer": "Count", + }, + "name": "value", + "state": undefined, + "type": "number", + "values": [ + 3, + ], + }, + ] + `); + }); + }); }); diff --git a/packages/grafana-data/src/transformations/transformers/reduce.ts b/packages/grafana-data/src/transformations/transformers/reduce.ts index dba2bc286e0..f9d2cd01d4c 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.ts @@ -230,10 +230,11 @@ export function reduceFields(data: DataFrame[], matcher: FieldMatcher, reducerId const value = results[reducer]; const copy = { ...field, + labels: { ...field.labels }, type: getFieldType(reducer, field), values: [value], + state: undefined, }; - copy.state = undefined; if (reducers.length > 1) { if (!copy.labels) { copy.labels = {};