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 bfa5bfe7558..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"] ], @@ -1499,22 +1493,9 @@ exports[`better eslint`] = { "public/app/features/browse-dashboards/components/NewFolderForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], - "public/app/features/browse-dashboards/state/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] - ], "public/app/features/connections/components/ConnectionsRedirectNotice/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] ], - "public/app/features/connections/pages/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./AddNewConnectionPage\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./DataSourceDashboardsPage\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./DataSourceDetailsPage\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./DataSourcesListPage\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./EditDataSourcePage\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./NewDataSourcePage\`)", "5"] - ], "public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.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"] ], @@ -1568,13 +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/components/Wizard/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] - ], - "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"] @@ -1730,10 +1704,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.", "4"], [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"] ], - "public/app/features/dashboard-scene/settings/annotations/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"] - ], "public/app/features/dashboard-scene/settings/links/DashboardLinkForm.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"], @@ -1781,13 +1751,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/features/dashboard-scene/settings/version-history/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./HistorySrv\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryHeader\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryTable\`)", "4"] - ], "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/ConfigEmailSharing.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"] ], @@ -1870,10 +1833,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/features/dashboard/components/AnnotationSettings/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"] - ], "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -1956,10 +1915,6 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/Inspector/PanelInspector.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/dashboard/components/LinksSettings/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./LinkSettingsEdit\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./LinkSettingsList\`)", "1"] - ], "public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "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"] @@ -2228,10 +2183,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], - "public/app/features/dataframe-import/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] - ], "public/app/features/datasources/components/DataSourceTypeCard.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"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] @@ -2251,14 +2202,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/features/datasources/state/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"] - ], "public/app/features/datasources/state/navModel.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -2299,24 +2242,6 @@ exports[`better eslint`] = { "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "public/app/features/dimensions/editors/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"] - ], - "public/app/features/dimensions/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "7"] - ], "public/app/features/dimensions/scale.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -2362,17 +2287,6 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/demo/trace-generators.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/explore/TraceView/components/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./TraceTimelineViewer/SpanDetail/DetailState\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./TraceTimelineViewer\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./model/transform-trace-data\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./settings/SpanBarSettings\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./utils/filter-spans\`)", "5"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "7"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "8"] - ], "public/app/features/explore/TraceView/components/model/ddg/types.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./PathElem\`)", "0"] ], @@ -2393,21 +2307,9 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/model/transform-trace-data.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/explore/TraceView/components/types/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`../settings/SpanBarSettings\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./TNil\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./TTraceTimeline\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./links\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "4"] - ], "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DraggableManagerDemo\`)", "0"] ], - "public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./DraggableManager\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./EUpdateTypes\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] - ], "public/app/features/explore/TraceView/createSpanLink.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -2602,36 +2504,15 @@ 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.", "1"], [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.", "2"] ], - "public/app/features/plugins/admin/components/Badges/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "4"] - ], "public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./GetStartedWithPlugin\`)", "0"] ], - "public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsButton\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "1"] - ], "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"], @@ -2982,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"], @@ -3142,10 +3017,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/azuremonitor/azureMetadata/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] - ], "public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./ArgQueryEditor\`)", "0"] ], @@ -3212,26 +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/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] - ], "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"], @@ -3263,23 +3114,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/plugins/datasource/cloud-monitoring/components/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./Aggregation\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./AliasBy\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./AlignmentFunction\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./Alignment\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./AnnotationsHelp\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./Fields\`)", "5"], - [0, 0, 0, "Do not re-export imported variable (\`./GroupBy\`)", "6"], - [0, 0, 0, "Do not re-export imported variable (\`./LabelFilter\`)", "7"], - [0, 0, 0, "Do not re-export imported variable (\`./MQLQueryEditor\`)", "8"], - [0, 0, 0, "Do not re-export imported variable (\`./MetricQueryEditor\`)", "9"], - [0, 0, 0, "Do not re-export imported variable (\`./PeriodSelect\`)", "10"], - [0, 0, 0, "Do not re-export imported variable (\`./Preprocessor\`)", "11"], - [0, 0, 0, "Do not re-export imported variable (\`./Project\`)", "12"], - [0, 0, 0, "Do not re-export imported variable (\`./SLOQueryEditor\`)", "13"], - [0, 0, 0, "Do not re-export imported variable (\`./VisualMetricQueryEditor\`)", "14"] - ], "public/app/plugins/datasource/cloud-monitoring/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -3291,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"], @@ -3347,153 +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/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./commentOnlyQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./empty\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./filterQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./newCommandQuery\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "5"], - [0, 0, 0, "Do not re-export imported variable (\`./sortQuery\`)", "6"], - [0, 0, 0, "Do not re-export imported variable (\`./whitespaceQuery\`)", "7"] - ], - "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/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./multiLineIncompleteQueryWithoutNamespace\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineTwoQueries\`)", "4"] - ], - "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/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./afterLabelValue\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./insideLabelValue\`)", "1"] - ], - "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/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./afterFunctionQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./secondArgAfterSearchQuery\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./secondArgQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./thirdArgAfterSearchQuery\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./withinStringQuery\`)", "5"] - ], - "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"], @@ -3566,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"], @@ -3582,11 +3258,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/plugins/datasource/elasticsearch/types.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./dataquery.gen\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`ElasticsearchQuery\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] - ], "public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.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"], @@ -3627,10 +3298,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./RandomWalkEditor\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./StreamingClientEditor\`)", "1"] - ], "public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3787,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"], @@ -3858,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"] ], 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/.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: 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/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/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/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..667b7a10354 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -83,6 +83,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 +145,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/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/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/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/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/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. diff --git a/docs/sources/dashboards/build-dashboards/_index.md b/docs/sources/dashboards/build-dashboards/_index.md index 4c228c3458c..4978be9dd35 100644 --- a/docs/sources/dashboards/build-dashboards/_index.md +++ b/docs/sources/dashboards/build-dashboards/_index.md @@ -28,7 +28,3 @@ refs: This section includes the following topics: {{< section >}} - -## Dynamic dashboards - -You can create more interactive and dynamic dashboards by adding and using [variables](ref:variables). Instead of hard-coding things like server, application, and sensor names in your metric queries, you can use variables in their place. Read more about variables [here](ref:variables). 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` diff --git a/e2e/cloud-plugins-suite/azure-monitor.spec.ts b/e2e/cloud-plugins-suite/azure-monitor.spec.ts index d2e1a074b27..469bdbfc9ed 100644 --- a/e2e/cloud-plugins-suite/azure-monitor.spec.ts +++ b/e2e/cloud-plugins-suite/azure-monitor.spec.ts @@ -5,11 +5,11 @@ import { v4 as uuidv4 } from 'uuid'; import { selectors as rawSelectors } from '@grafana/e2e-selectors'; import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors'; +import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/types/query'; import { AzureMonitorDataSourceJsonData, AzureMonitorDataSourceSecureJsonData, - AzureQueryType, -} from '../../public/app/plugins/datasource/azuremonitor/types'; +} from '../../public/app/plugins/datasource/azuremonitor/types/types'; import { e2e } from '../utils'; const provisioningPath = `provisioning/datasources/azmonitor-ds.yaml`; 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 22b9737d24d..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 @@ -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..88a184a171a 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= @@ -1607,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= @@ -3572,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= @@ -3592,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 3b95e2eaabf..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= @@ -349,10 +382,13 @@ 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= @@ -373,6 +409,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP 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= @@ -414,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= @@ -446,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= @@ -520,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= @@ -530,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= @@ -554,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= @@ -581,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= @@ -642,14 +708,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= @@ -667,6 +725,8 @@ github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPF 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= @@ -716,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= @@ -826,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= @@ -930,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= @@ -1023,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= @@ -1139,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= @@ -1205,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/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 5f5bf7d2fe3..5bc553728f6 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", @@ -163,8 +162,8 @@ "@types/webpack-assets-manifest": "^5", "@types/webpack-env": "^1.18.4", "@types/yargs": "17.0.33", - "@typescript-eslint/eslint-plugin": "8.22.0", - "@typescript-eslint/parser": "8.22.0", + "@typescript-eslint/eslint-plugin": "8.35.1", + "@typescript-eslint/parser": "8.35.1", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "blob-polyfill": "9.0.20240710", @@ -220,7 +219,7 @@ "jsdom-testing-mocks": "^1.13.1", "lerna": "8.2.1", "mini-css-extract-plugin": "2.9.2", - "msw": "2.10.2", + "msw": "2.10.3", "mutationobserver-shim": "0.3.7", "node-notifier": "10.0.1", "nx": "20.7.1", @@ -249,7 +248,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", @@ -268,7 +267,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:*", @@ -336,12 +335,12 @@ "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", "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", @@ -359,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", @@ -380,7 +379,7 @@ "react": "18.3.1", "react-diff-viewer-continued": "^3.4.0", "react-dom": "18.3.1", - "react-draggable": "4.4.6", + "react-draggable": "4.5.0", "react-dropzone": "^14.2.3", "react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch", "react-highlight-words": "0.21.0", @@ -416,7 +415,7 @@ "slate": "0.47.9", "slate-plain-serializer": "0.7.13", "slate-react": "0.22.10", - "swagger-ui-react": "5.25.2", + "swagger-ui-react": "5.26.2", "symbol-observable": "4.0.0", "systemjs": "6.15.1", "tinycolor2": "1.6.0", @@ -444,7 +443,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/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..56263ed62a2 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -68,8 +68,8 @@ "fast_array_intersect": "1.1.0", "history": "4.10.1", "lodash": "4.17.21", - "marked": "15.0.12", - "marked-mangle": "1.1.10", + "marked": "16.0.0", + "marked-mangle": "1.1.11", "moment": "2.30.1", "moment-timezone": "0.5.47", "ol": "7.4.0", @@ -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/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/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 = {}; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ca54c10faa2..4a5de4fd928 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -979,7 +979,7 @@ export interface FeatureToggles { restoreDashboards?: boolean; /** * Skip token rotation if it was already rotated less than 5 seconds ago - * @default false + * @default true */ skipTokenRotationIfRecent?: boolean; /** 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-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-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..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", @@ -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-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-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..091d6fab771 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", @@ -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", @@ -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-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; } 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-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-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index ccfe41b0c9d..ca7bc045f9c 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -181,6 +181,7 @@ class DataSourceWithBackend< if (datasource.uid?.length) { dsUIDs.add(datasource.uid); } + return { ...(shouldApplyTemplateVariables ? this.applyTemplateVariables(q, request.scopedVars, request.filters) : q), datasource, @@ -225,9 +226,6 @@ class DataSourceWithBackend< if (!(config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs)) { console.warn('feature toggle queryServiceFromUI also requires the queryService to be running'); } else { - if (!hasExpr && dsUIDs.size === 1) { - // TODO? can we talk directly to the apiserver? - } url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=${this.type}`; } } 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-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/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/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-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-test-utils/package.json b/packages/grafana-test-utils/package.json index a74497f16f8..6ae0652dcf3 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -46,11 +46,11 @@ "typecheck": "tsc --emitDeclarationOnly false --noEmit" }, "dependencies": { - "msw": "2.10.2" + "msw": "2.10.3" }, "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/.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', }, }); diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 443b8cc01ae..9dface55147 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", @@ -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", @@ -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/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/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 ( 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/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 79ed1581344..a7f5cea3f05 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -344,7 +344,10 @@ export function PanelChrome({ >
{loadingState === LoadingState.Loading ? ( - + ) : null}
@@ -362,7 +365,11 @@ export function PanelChrome({ {statusMessage && (
- +
)} @@ -380,7 +387,11 @@ export function PanelChrome({ > {statusMessage && (
- +
)} 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 ( { 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; 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/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/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/login/social/connectors/gitlab_oauth.go b/pkg/login/social/connectors/gitlab_oauth.go index 8545040aa0f..917a4503218 100644 --- a/pkg/login/social/connectors/gitlab_oauth.go +++ b/pkg/login/social/connectors/gitlab_oauth.go @@ -302,6 +302,8 @@ func (s *SocialGitlab) extractFromToken(ctx context.Context, client *http.Client data.Groups = userInfo.Groups } + data.raw = rawJSON + s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data)) return &data, nil } diff --git a/pkg/login/social/connectors/gitlab_oauth_test.go b/pkg/login/social/connectors/gitlab_oauth_test.go index 09a621f645f..60fdc27d6ee 100644 --- a/pkg/login/social/connectors/gitlab_oauth_test.go +++ b/pkg/login/social/connectors/gitlab_oauth_test.go @@ -37,6 +37,8 @@ const ( rootUserRespBody = `{"id":1,"username":"root","name":"Administrator","state":"active","email":"root@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":true,"namespace_id":1}` editorUserRespBody = `{"id":3,"username":"gitlab-editor","name":"Gitlab Editor","state":"active","email":"gitlab-editor@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":false,"namespace_id":1}` + editorUserIDToken = `{"sub":"3","preferred_username":"gitlab-editor","name":"Gitlab Editor","email":"gitlab-editor@example.org","email_verified":true,"groups_direct":["editors", "viewers"]}` // #nosec G101 not a hardcoded credential + adminGroup = `{"id":4,"web_url":"http://grafana-gitlab.local/groups/admins","name":"Admins","path":"admins","project_creation_level":"developer","full_name":"Admins","full_path":"admins","created_at":"2022-09-13T19:38:04.891Z"}` editorGroup = `{"id":5,"web_url":"http://grafana-gitlab.local/groups/editors","name":"Editors","path":"editors","project_creation_level":"developer","full_name":"Editors","full_path":"editors","created_at":"2022-09-13T19:38:15.074Z"}` viewerGroup = `{"id":6,"web_url":"http://grafana-gitlab.local/groups/viewers","name":"Viewers","path":"viewers","project_creation_level":"developer","full_name":"Viewers","full_path":"viewers","created_at":"2022-09-13T19:38:25.777Z"}` @@ -61,6 +63,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) { GroupsRespBody string GroupHeaders map[string]string RoleAttributePath string + IDToken string ExpectedLogin string ExpectedEmail string ExpectedRoles map[int64]org.RoleType @@ -180,6 +183,24 @@ func TestSocialGitlab_UserInfo(t *testing.T) { ExpectedEmail: "gitlab-editor@example.org", ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"}, }, + { + Name: "Maps roles from ID token attributes if available", + RoleAttributePath: `email=='gitlab-editor@example.org' && 'Editor' || 'Viewer'`, + IDToken: editorUserIDToken, + ExpectedLogin: "gitlab-editor", + ExpectedEmail: "gitlab-editor@example.org", + ExpectedRoles: map[int64]org.RoleType{1: "Editor"}, + ExpectedGrafanaAdmin: nilPointer, + }, + { + Name: "Maps groups from ID token groups if available", + RoleAttributePath: gitlabAttrPath, + IDToken: editorUserIDToken, + ExpectedLogin: "gitlab-editor", + ExpectedEmail: "gitlab-editor@example.org", + ExpectedRoles: map[int64]org.RoleType{1: "Editor"}, + ExpectedGrafanaAdmin: nilPointer, + }, { Name: "Should return error when neither role attribute path nor org mapping evaluates to a role and role attribute strict is enabled", Cfg: conf{RoleAttributeStrict: true, OrgMapping: []string{"other:Org4:Editor"}}, @@ -230,8 +251,17 @@ func TestSocialGitlab_UserInfo(t *testing.T) { require.Fail(t, "unexpected request URI: "+r.RequestURI) } })) + + token := &oauth2.Token{} + if tt.IDToken != "" { + emptyJWTHeader := base64.RawURLEncoding.EncodeToString([]byte("{}")) + JWTBody := base64.RawURLEncoding.EncodeToString([]byte(tt.IDToken)) + idToken := fmt.Sprintf("%s.%s.signature", emptyJWTHeader, JWTBody) + token = token.WithExtra(map[string]any{"id_token": idToken}) + } + provider.info.ApiUrl = ts.URL + apiURI - actualResult, err := provider.UserInfo(context.Background(), ts.Client(), &oauth2.Token{}) + actualResult, err := provider.UserInfo(context.Background(), ts.Client(), token) if tt.ExpectedError != nil { require.ErrorIs(t, err, tt.ExpectedError) return @@ -382,6 +412,9 @@ func TestSocialGitlab_extractFromToken(t *testing.T) { } for _, tc := range testCases { + if tc.wantUser != nil { + tc.wantUser.raw = []byte(tc.payload) + } t.Run(tc.name, func(t *testing.T) { // Create a test client with a dummy token client := oauth2.NewClient(context.Background(), &tokenSource{accessToken: "dummy_access_token"}) 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/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/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": {}}) 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 406497bcf9f..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" @@ -424,16 +426,20 @@ var wireBasicSet = wire.NewSet( // Secrets Manager secretmetadata.ProvideSecureValueMetadataStorage, secretmetadata.ProvideKeeperMetadataStorage, - secretmetadata.ProvideOutboxQueue, + 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 9fba20aa8c6..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.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(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/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/services/apiserver/options/storage.go b/pkg/services/apiserver/options/storage.go index d6c700303b6..2ff900037fd 100644 --- a/pkg/services/apiserver/options/storage.go +++ b/pkg/services/apiserver/options/storage.go @@ -22,10 +22,11 @@ import ( type StorageType string const ( - StorageTypeFile StorageType = "file" - StorageTypeEtcd StorageType = "etcd" - StorageTypeUnified StorageType = "unified" - StorageTypeUnifiedGrpc StorageType = "unified-grpc" + StorageTypeFile StorageType = "file" + StorageTypeEtcd StorageType = "etcd" + StorageTypeUnified StorageType = "unified" + StorageTypeUnifiedGrpc StorageType = "unified-grpc" + StorageTypeUnifiedKVGrpc StorageType = "unified-kv-grpc" // Deprecated: legacy is a shim that is no longer necessary StorageTypeLegacy StorageType = "legacy" @@ -95,6 +96,8 @@ func (o *StorageOptions) Validate() []error { // nolint:staticcheck case StorageTypeLegacy: // no-op + case StorageTypeUnifiedKVGrpc: + // no-op (enterprise only) case StorageTypeFile, StorageTypeEtcd, StorageTypeUnified, StorageTypeUnifiedGrpc: // no-op default: 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 { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5ee6c7d0e5f..cb275abd33a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1678,11 +1678,11 @@ var ( { Name: "skipTokenRotationIfRecent", Description: "Skip token rotation if it was already rotated less than 5 seconds ago", - Stage: FeatureStagePrivatePreview, + Stage: FeatureStageGeneralAvailability, Owner: identityAccessTeam, HideFromAdminPage: true, HideFromDocs: true, - Expression: "false", + Expression: "true", // enabled by default }, { Name: "alertEnrichment", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 3583570d940..dffcd8bcad4 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -219,7 +219,7 @@ alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false, alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true kubernetesAuthzApis,experimental,@grafana/identity-access-team,false,false,false restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false -skipTokenRotationIfRecent,privatePreview,@grafana/identity-access-team,false,false,false +skipTokenRotationIfRecent,GA,@grafana/identity-access-team,false,false,false alertEnrichment,experimental,@grafana/alerting-squad,false,false,false alertingImportAlertmanagerAPI,experimental,@grafana/alerting-squad,false,false,false preferLibraryPanelTitle,privatePreview,@grafana/dashboards-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 92bf71778ba..004b9c5e01b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2805,16 +2805,19 @@ { "metadata": { "name": "skipTokenRotationIfRecent", - "resourceVersion": "1750434297879", - "creationTimestamp": "2025-06-03T06:59:40Z" + "resourceVersion": "1751872762065", + "creationTimestamp": "2025-06-03T06:59:40Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-07-07 07:19:22.065046 +0000 UTC" + } }, "spec": { "description": "Skip token rotation if it was already rotated less than 5 seconds ago", - "stage": "privatePreview", + "stage": "GA", "codeowner": "@grafana/identity-access-team", "hideFromAdminPage": true, "hideFromDocs": true, - "expression": "false" + "expression": "true" } }, { 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..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 @@ -28,16 +39,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,27 +252,45 @@ 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 { + // 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 } -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) - 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) + }) + } +} 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/services/sqlstore/database_config.go b/pkg/services/sqlstore/database_config.go index 171ff4972b9..f5ba069df15 100644 --- a/pkg/services/sqlstore/database_config.go +++ b/pkg/services/sqlstore/database_config.go @@ -39,6 +39,7 @@ type DatabaseConfig struct { WALEnabled bool UrlQueryParams map[string][]string SkipMigrations bool + EnsureDefaultOrgAndUser bool MigrationLock bool MigrationLockAttemptTimeout int LogQueries bool @@ -114,6 +115,7 @@ func (dbCfg *DatabaseConfig) readConfig(cfg *setting.Cfg) error { dbCfg.CacheMode = sec.Key("cache_mode").MustString("private") dbCfg.WALEnabled = sec.Key("wal").MustBool(false) dbCfg.SkipMigrations = sec.Key("skip_migrations").MustBool() + dbCfg.EnsureDefaultOrgAndUser = sec.Key("ensure_default_org_and_user").MustBool(true) dbCfg.MigrationLock = sec.Key("migration_locking").MustBool(true) dbCfg.MigrationLockAttemptTimeout = sec.Key("locking_attempt_timeout_sec").MustInt() diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 0cfb63fb9e9..b54a177cf02 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -47,7 +47,6 @@ type SQLStore struct { engine *xorm.Engine log log.Logger dialect migrator.Dialect - skipEnsureDefaultOrgAndUser bool migrations registry.DatabaseMigrator tracer tracing.Tracer recursiveQueriesAreSupported *bool @@ -62,7 +61,7 @@ func ProvideService(cfg *setting.Cfg, // by that mimic the functionality of how it was functioning before // xorm's changes above. xorm.DefaultPostgresSchema = "" - s, err := newStore(cfg, nil, features, migrations, bus, tracer, false) + s, err := newStore(cfg, nil, features, migrations, bus, tracer) if err != nil { return nil, err } @@ -80,7 +79,7 @@ func ProvideService(cfg *setting.Cfg, } func ProvideServiceForTests(t sqlutil.ITestDB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, bus bus.Bus, migrations registry.DatabaseMigrator) (*SQLStore, error) { - return initTestDB(t, cfg, features, migrations, bus, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) + return initTestDB(t, cfg, features, migrations, bus, InitTestDBOpt{}) } // NewSQLStoreWithoutSideEffects creates a new *SQLStore without side-effects such as @@ -88,20 +87,20 @@ func ProvideServiceForTests(t sqlutil.ITestDB, cfg *setting.Cfg, features featur func NewSQLStoreWithoutSideEffects(cfg *setting.Cfg, features featuremgmt.FeatureToggles, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { - return newStore(cfg, nil, features, nil, bus, tracer, true) + cfgDBSection := cfg.Raw.Section("database") + cfgDBSection.Key("ensure_default_org_and_user").SetValue("false") + return newStore(cfg, nil, features, nil, bus, tracer) } func newStore(cfg *setting.Cfg, engine *xorm.Engine, features featuremgmt.FeatureToggles, - migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer, - skipEnsureDefaultOrgAndUser bool) (*SQLStore, error) { + migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { ss := &SQLStore{ - cfg: cfg, - log: log.New("sqlstore"), - skipEnsureDefaultOrgAndUser: skipEnsureDefaultOrgAndUser, - migrations: migrations, - bus: bus, - tracer: tracer, - features: features, + cfg: cfg, + log: log.New("sqlstore"), + migrations: migrations, + bus: bus, + tracer: tracer, + features: features, } if err := ss.initEngine(engine); err != nil { @@ -148,11 +147,10 @@ func (ss *SQLStore) Migrate(isDatabaseLockingEnabled bool) error { // Reset resets database state. // If default org and user creation is enabled, it will be ensured they exist in the database. func (ss *SQLStore) Reset() error { - if ss.skipEnsureDefaultOrgAndUser { - return nil + if ss.dbCfg.EnsureDefaultOrgAndUser { + return ss.ensureMainOrgAndAdminUser(false) } - - return ss.ensureMainOrgAndAdminUser(false) + return nil } // Quote quotes the value in the used SQL dialect @@ -412,10 +410,8 @@ var testSQLStoreCleanup []func() // InitTestDBOpt contains options for InitTestDB. type InitTestDBOpt struct { - // EnsureDefaultOrgAndUser flags whether to ensure that default org and user exist. - EnsureDefaultOrgAndUser bool - FeatureFlags []string - Cfg *setting.Cfg + FeatureFlags []string + Cfg *setting.Cfg } // InitTestDBWithMigration initializes the test DB given custom migrations. @@ -533,7 +529,8 @@ func TestMain(m *testing.M) { } if len(opts) == 0 { - opts = []InitTestDBOpt{{EnsureDefaultOrgAndUser: false, FeatureFlags: []string{}}} + cfgDBSec := testCfg.Raw.Section("database") + cfgDBSec.Key("ensure_default_org_and_user").SetValue("false") } if testSQLStore == nil { @@ -566,16 +563,8 @@ func TestMain(m *testing.M) { engine.DatabaseTZ = time.UTC engine.TZLocation = time.UTC - skipEnsureDefaultOrgAndUser := false - for _, opt := range opts { - if !opt.EnsureDefaultOrgAndUser { - skipEnsureDefaultOrgAndUser = true - break - } - } - tracer := tracing.InitializeTracerForTest() - testSQLStore, err = newStore(testCfg, engine, features, migration, bus, tracer, skipEnsureDefaultOrgAndUser) + testSQLStore, err = newStore(testCfg, engine, features, migration, bus, tracer) if err != nil { return nil, err } diff --git a/pkg/services/sqlstore/sqlstore_testinfra.go b/pkg/services/sqlstore/sqlstore_testinfra.go index 194cf843c2f..5ac0b8e8288 100644 --- a/pkg/services/sqlstore/sqlstore_testinfra.go +++ b/pkg/services/sqlstore/sqlstore_testinfra.go @@ -174,8 +174,12 @@ func NewTestStore(tb TestingTB, opts ...TestOption) *SQLStore { engine.DatabaseTZ = time.UTC engine.TZLocation = time.UTC + cfgDBSec := cfg.Raw.Section("database") + shouldEnsure := fmt.Sprintf("%t", !options.NoDefaultUserOrg && !options.Truncate) + cfgDBSec.Key("ensure_default_org_and_user").SetValue(shouldEnsure) + store, err := newStore(cfg, engine, features, options.MigratorFactory(features), - options.Bus, options.Tracer, options.NoDefaultUserOrg || options.Truncate) + options.Bus, options.Tracer) if err != nil { tb.Fatalf("failed to create a new SQLStore: %v", err) panic("unreachable") 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/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)) + }) +} 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) 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) + }) + } +} diff --git a/pkg/storage/unified/resource/search_server_distributor.go b/pkg/storage/unified/resource/search_server_distributor.go index 4fbb7083aca..9637e3055cb 100644 --- a/pkg/storage/unified/resource/search_server_distributor.go +++ b/pkg/storage/unified/resource/search_server_distributor.go @@ -41,11 +41,8 @@ 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,86 +103,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 -// } - 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 { @@ -204,24 +121,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)) 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) { 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) +} 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/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/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/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", 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": { 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/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/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/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/NestedFolderPicker/NestedFolderList.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderList.tsx index 910bc761858..4a322b6ee27 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderList.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderList.tsx @@ -9,7 +9,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { IconButton, useStyles2, Text } from '@grafana/ui'; import { Indent } from 'app/core/components/Indent/Indent'; -import { childrenByParentUIDSelector, rootItemsSelector } from 'app/features/browse-dashboards/state'; +import { childrenByParentUIDSelector, rootItemsSelector } from 'app/features/browse-dashboards/state/hooks'; import { DashboardsTreeItem } from 'app/features/browse-dashboards/types'; import { DashboardViewItem } from 'app/features/search/types'; import { useSelector } from 'app/types'; 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/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--; } 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/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/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/AlertRuleMenu.tsx b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx index 5b6a8943da4..77e751238cf 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx @@ -10,7 +10,12 @@ import { useRulePluginLinkExtension } from 'app/features/alerting/unified/plugin import { Rule, RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting'; import { PromAlertingRuleState, RulerRuleDTO } from 'app/types/unified-alerting-dto'; -import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; +import { + AlertRuleAction, + skipToken, + useGrafanaPromRuleAbilities, + useRulerRuleAbilities, +} from '../../hooks/useAbilities'; import { createShareLink, isLocalDevEnv, isOpenSourceEdition } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { prometheusRuleType, rulerRuleType } from '../../utils/rules'; @@ -33,6 +38,8 @@ interface Props { /** * Get a list of menu items + divider elements for rendering in an alert rule's * dropdown menu + * If the consumer of this component comes from the alert list view, we need to use promRule to check abilities and permissions, + * as we have removed all requests to the ruler API in the list view. */ const AlertRuleMenu = ({ promRule, @@ -46,29 +53,51 @@ const AlertRuleMenu = ({ buttonSize, fill, }: Props) => { - // check all abilities and permissions - const [pauseSupported, pauseAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Pause); - const canPause = pauseSupported && pauseAllowed; + // check all abilities and permissions using rulerRule + const [rulerPauseAbility, rulerDeleteAbility, rulerDuplicateAbility, rulerSilenceAbility, rulerExportAbility] = + useRulerRuleAbilities(rulerRule, groupIdentifier, [ + AlertRuleAction.Pause, + AlertRuleAction.Delete, + AlertRuleAction.Duplicate, + AlertRuleAction.Silence, + AlertRuleAction.ModifyExport, + ]); - const [deleteSupported, deleteAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Delete); - const canDelete = deleteSupported && deleteAllowed; + // check all abilities and permissions using promRule + const [ + grafanaPauseAbility, + grafanaDeleteAbility, + grafanaDuplicateAbility, + grafanaSilenceAbility, + grafanaExportAbility, + ] = useGrafanaPromRuleAbilities(prometheusRuleType.grafana.rule(promRule) ? promRule : skipToken, [ + AlertRuleAction.Pause, + AlertRuleAction.Delete, + AlertRuleAction.Duplicate, + AlertRuleAction.Silence, + AlertRuleAction.ModifyExport, + ]); - const [duplicateSupported, duplicateAllowed] = useRulerRuleAbility( - rulerRule, - groupIdentifier, - AlertRuleAction.Duplicate - ); - const canDuplicate = duplicateSupported && duplicateAllowed; + const [pauseSupported, pauseAllowed] = rulerPauseAbility; + const [grafanaPauseSupported, grafanaPauseAllowed] = grafanaPauseAbility; + const canPause = (pauseSupported && pauseAllowed) || (grafanaPauseSupported && grafanaPauseAllowed); - const [silenceSupported, silenceAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Silence); - const canSilence = silenceSupported && silenceAllowed; + const [deleteSupported, deleteAllowed] = rulerDeleteAbility; + const [grafanaDeleteSupported, grafanaDeleteAllowed] = grafanaDeleteAbility; + const canDelete = (deleteSupported && deleteAllowed) || (grafanaDeleteSupported && grafanaDeleteAllowed); - const [exportSupported, exportAllowed] = useRulerRuleAbility( - rulerRule, - groupIdentifier, - AlertRuleAction.ModifyExport - ); - const canExport = exportSupported && exportAllowed; + const [duplicateSupported, duplicateAllowed] = rulerDuplicateAbility; + const [grafanaDuplicateSupported, grafanaDuplicateAllowed] = grafanaDuplicateAbility; + const canDuplicate = + (duplicateSupported && duplicateAllowed) || (grafanaDuplicateSupported && grafanaDuplicateAllowed); + + const [silenceSupported, silenceAllowed] = rulerSilenceAbility; + const [grafanaSilenceSupported, grafanaSilenceAllowed] = grafanaSilenceAbility; + const canSilence = (silenceSupported && silenceAllowed) || (grafanaSilenceSupported && grafanaSilenceAllowed); + + const [exportSupported, exportAllowed] = rulerExportAbility; + const [grafanaExportSupported, grafanaExportAllowed] = grafanaExportAbility; + const canExport = (exportSupported && exportAllowed) || (grafanaExportSupported && grafanaExportAllowed); const ruleExtensionLinks = useRulePluginLinkExtension(promRule, groupIdentifier); 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/rules/RuleDetails.test.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx index 6d55821a49f..ac0d694f601 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx @@ -7,6 +7,7 @@ import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { getCloudRule, getGrafanaRule } from '../../mocks'; +import { mimirDataSource } from '../../mocks/server/configure'; import { RuleDetails } from './RuleDetails'; @@ -32,6 +33,8 @@ const ui = { setupMswServer(); +const { dataSource: mimirDs } = mimirDataSource(); + beforeAll(() => { jest.clearAllMocks(); }); @@ -81,7 +84,7 @@ describe('RuleDetails RBAC', () => { }); describe('Cloud rules action buttons', () => { - const cloudRule = getCloudRule({ name: 'Cloud' }); + const cloudRule = getCloudRule({ name: 'Cloud' }, { rulesSource: mimirDs }); it('Should not render Edit button for users with the update permission', async () => { // Arrange diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx index 22cb1bb8b7c..a4be47bbf81 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx @@ -4,7 +4,14 @@ import { byRole } from 'testing-library-selector'; import { setPluginLinksHook } from '@grafana/runtime'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; -import { AlertRuleAction, useAlertRuleAbility, useRulerRuleAbility } from '../../hooks/useAbilities'; +import { + AlertRuleAction, + useAlertRuleAbility, + useGrafanaPromRuleAbilities, + useGrafanaPromRuleAbility, + useRulerRuleAbilities, + useRulerRuleAbility, +} from '../../hooks/useAbilities'; import { getCloudRule, getGrafanaRule } from '../../mocks'; import { mimirDataSource } from '../../mocks/server/configure'; @@ -13,11 +20,15 @@ import { RulesTable } from './RulesTable'; jest.mock('../../hooks/useAbilities'); const mocks = { - // This is a bit unfortunate, but we need to mock both abilities - // RuleActionButtons still needs to use the useAlertRuleAbility hook - // whereas AlertRuleMenu has already been refactored to use useRulerRuleAbility + // Mock the hooks that are actually used by the components: + // RuleActionsButtons uses: useAlertRuleAbility (singular) + // AlertRuleMenu uses: useRulerRuleAbilities and useGrafanaPromRuleAbilities (plural) + // We can also use useGrafanaPromRuleAbility (singular) for simpler mocking useRulerRuleAbility: jest.mocked(useRulerRuleAbility), useAlertRuleAbility: jest.mocked(useAlertRuleAbility), + useGrafanaPromRuleAbility: jest.mocked(useGrafanaPromRuleAbility), + useRulerRuleAbilities: jest.mocked(useRulerRuleAbilities), + useGrafanaPromRuleAbilities: jest.mocked(useGrafanaPromRuleAbilities), }; setPluginLinksHook(() => ({ @@ -46,18 +57,40 @@ describe('RulesTable RBAC', () => { jest.clearAllMocks(); jest.restoreAllMocks(); jest.resetAllMocks(); + + // Set up default neutral mocks for all hooks + // Singular hooks (used by RuleActionsButtons and can simplify mocking) + mocks.useAlertRuleAbility.mockReturnValue([false, false]); + mocks.useRulerRuleAbility.mockReturnValue([false, false]); + mocks.useGrafanaPromRuleAbility.mockReturnValue([false, false]); + + // Plural hooks (used by AlertRuleMenu) - need to return arrays based on input actions + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map(() => [false, false]); + }); + mocks.useGrafanaPromRuleAbilities.mockImplementation((_rule, actions) => { + return actions.map(() => [false, false]); + }); }); describe('Grafana rules action buttons', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); it('Should not render Edit button for users without the update permission', async () => { - mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { + // Mock the specific hooks needed for Grafana rules + // Using singular hook for simpler mocking + mocks.useAlertRuleAbility.mockImplementation((rule, action) => { return action === AlertRuleAction.Update ? [true, false] : [true, true]; }); - mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { + mocks.useGrafanaPromRuleAbility.mockImplementation((rule, action) => { return action === AlertRuleAction.Update ? [true, false] : [true, true]; }); + // Still need plural hook for AlertRuleMenu component + mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Update ? [true, false] : [true, true]; + }); + }); render(); @@ -65,11 +98,14 @@ describe('RulesTable RBAC', () => { }); it('Should not render Delete button for users without the delete permission', async () => { - mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { + // Mock the specific hooks needed for Grafana rules + mocks.useAlertRuleAbility.mockImplementation((rule, action) => { return action === AlertRuleAction.Delete ? [true, false] : [true, true]; }); - mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { - return action === AlertRuleAction.Delete ? [true, false] : [true, true]; + mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Delete ? [true, false] : [true, true]; + }); }); render(); @@ -80,11 +116,14 @@ describe('RulesTable RBAC', () => { }); it('Should render Edit button for users with the update permission', async () => { - mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { + // Mock the specific hooks needed for Grafana rules + mocks.useAlertRuleAbility.mockImplementation((rule, action) => { return action === AlertRuleAction.Update ? [true, true] : [false, false]; }); - mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { - return action === AlertRuleAction.Update ? [true, true] : [false, false]; + mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Update ? [true, true] : [false, false]; + }); }); render(); @@ -93,11 +132,14 @@ describe('RulesTable RBAC', () => { }); it('Should render Delete button for users with the delete permission', async () => { - mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { + // Mock the specific hooks needed for Grafana rules + mocks.useAlertRuleAbility.mockImplementation((rule, action) => { return action === AlertRuleAction.Delete ? [true, true] : [false, false]; }); - mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { - return action === AlertRuleAction.Delete ? [true, true] : [false, false]; + mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Delete ? [true, true] : [false, false]; + }); }); render(); @@ -123,11 +165,15 @@ describe('RulesTable RBAC', () => { }; beforeEach(() => { - mocks.useRulerRuleAbility.mockImplementation(() => { - return [true, true]; + // Mock all hooks needed for the creating/deleting state tests + mocks.useRulerRuleAbility.mockImplementation(() => [true, true]); + mocks.useAlertRuleAbility.mockImplementation(() => [true, true]); + // Mock plural hooks for AlertRuleMenu + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map(() => [true, true]); }); - mocks.useAlertRuleAbility.mockImplementation(() => { - return [true, true]; + mocks.useGrafanaPromRuleAbilities.mockImplementation((_rule, actions) => { + return actions.map(() => [true, true]); }); }); @@ -164,6 +210,12 @@ describe('RulesTable RBAC', () => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { return action === AlertRuleAction.Update ? [true, false] : [true, true]; }); + // Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken) + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Update ? [true, false] : [true, true]; + }); + }); render(); @@ -177,6 +229,12 @@ describe('RulesTable RBAC', () => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { return action === AlertRuleAction.Delete ? [true, false] : [true, true]; }); + // Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken) + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Delete ? [true, false] : [true, true]; + }); + }); render(); @@ -191,6 +249,12 @@ describe('RulesTable RBAC', () => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { return action === AlertRuleAction.Update ? [true, true] : [false, false]; }); + // Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken) + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Update ? [true, true] : [false, false]; + }); + }); render(); @@ -204,6 +268,12 @@ describe('RulesTable RBAC', () => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { return action === AlertRuleAction.Delete ? [true, true] : [false, false]; }); + // Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken) + mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => { + return actions.map((action) => { + return action === AlertRuleAction.Delete ? [true, true] : [false, false]; + }); + }); render(); 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/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/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts index c2e96434d89..91fbe9cc603 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.ts +++ b/public/app/features/alerting/unified/hooks/useAbilities.ts @@ -14,15 +14,20 @@ import { useFolder } from 'app/features/alerting/unified/hooks/useFolder'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import { CombinedRule, RuleGroupIdentifierV2 } from 'app/types/unified-alerting'; -import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; +import { GrafanaPromRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { alertmanagerApi } from '../api/alertmanagerApi'; import { useAlertmanager } from '../state/AlertmanagerContext'; import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissions } from '../utils/access-control'; -import { getRulesSourceName } from '../utils/datasource'; -import { getGroupOriginName } from '../utils/groupIdentifier'; +import { getGroupOriginName, groupIdentifier } from '../utils/groupIdentifier'; import { isAdmin } from '../utils/misc'; -import { isFederatedRuleGroup, isPluginProvidedRule, rulerRuleType } from '../utils/rules'; +import { + isPluginProvidedRule, + isProvisionedPromRule, + isProvisionedRule, + prometheusRuleType, + rulerRuleType, +} from '../utils/rules'; import { useIsRuleEditable } from './useIsRuleEditable'; @@ -200,7 +205,7 @@ export function useRulerRuleAbility( } export function useRulerRuleAbilities( - rule: RulerRuleDTO, + rule: RulerRuleDTO | undefined, groupIdentifier: RuleGroupIdentifierV2, actions: AlertRuleAction[] ): Ability[] { @@ -211,61 +216,16 @@ export function useRulerRuleAbilities( }, [abilities, actions]); } -// This hook is being called a lot in different places -// In some cases multiple times for ~80 rules (e.g. on the list page) -// We need to investigate further if some of these calls are redundant -// In the meantime, memoizing the result helps +/** + * @deprecated Use {@link useAllRulerRuleAbilities} instead + */ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities { - const rulesSourceName = getRulesSourceName(rule.namespace.rulesSource); - - const { - isEditable, - isRemovable, - isRulerAvailable = false, - loading, - } = useIsRuleEditable(rulesSourceName, rule.rulerRule); - const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); - const canSilence = useCanSilence(rule.rulerRule); - - const abilities = useMemo>(() => { - const isProvisioned = - rulerRuleType.grafana.rule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); - const isFederated = isFederatedRuleGroup(rule.group); - const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule.rulerRule); - const isPluginProvided = isPluginProvidedRule(rule.rulerRule); - - // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited - const immutableRule = isProvisioned || isFederated || isPluginProvided; - - // while we gather info, pretend it's not supported - const MaybeSupported = loading ? NotSupported : isRulerAvailable; - const MaybeSupportedUnlessImmutable = immutableRule ? NotSupported : MaybeSupported; - - // Creating duplicates of plugin-provided rules does not seem to make a lot of sense - const duplicateSupported = isPluginProvided ? NotSupported : MaybeSupported; - - const rulesPermissions = getRulesPermissions(rulesSourceName); - - const abilities: Abilities = { - [AlertRuleAction.Duplicate]: toAbility(duplicateSupported, rulesPermissions.create), - [AlertRuleAction.View]: toAbility(AlwaysSupported, rulesPermissions.read), - [AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false], - [AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false], - [AlertRuleAction.Explore]: toAbility(AlwaysSupported, AccessControlAction.DataSourcesExplore), - [AlertRuleAction.Silence]: canSilence, - [AlertRuleAction.ModifyExport]: [isGrafanaManagedAlertRule, exportAllowed], - [AlertRuleAction.Pause]: [MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, isEditable ?? false], - [AlertRuleAction.Restore]: [MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, isEditable ?? false], - [AlertRuleAction.DeletePermanently]: [ - MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, - (isRemovable && isAdmin()) ?? false, - ], - }; - - return abilities; - }, [rule, loading, isRulerAvailable, isEditable, isRemovable, rulesSourceName, exportAllowed, canSilence]); - - return abilities; + // This hook is being called a lot in different places + // In some cases multiple times for ~80 rules (e.g. on the list page) + // We need to investigate further if some of these calls are redundant + // In the meantime, memoizing the result helps + const groupIdentifierV2 = useMemo(() => groupIdentifier.fromCombinedRule(rule), [rule]); + return useAllRulerRuleAbilities(rule.rulerRule, groupIdentifierV2); } export function useAllRulerRuleAbilities( @@ -279,7 +239,8 @@ export function useAllRulerRuleAbilities( const canSilence = useCanSilence(rule); const abilities = useMemo>(() => { - const isProvisioned = rulerRuleType.grafana.rule(rule) && Boolean(rule.grafana_alert.provenance); + const isProvisioned = rule ? isProvisionedRule(rule) : false; + // TODO: Add support for federated rules // const isFederated = isFederatedRuleGroup(); const isFederated = false; const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule); @@ -319,6 +280,131 @@ export function useAllRulerRuleAbilities( return abilities; } +/** + * Hook for checking abilities on Grafana Prometheus rules (GrafanaPromRuleDTO) + * This is the next version of useAllRulerRuleAbilities designed to work with GrafanaPromRuleDTO + */ +export function useAllGrafanaPromRuleAbilities(rule: GrafanaPromRuleDTO | undefined): Abilities { + // For GrafanaPromRuleDTO, we use useIsGrafanaPromRuleEditable instead + const { isEditable, isRemovable, loading } = useIsGrafanaPromRuleEditable(rule); // duplicate + const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); + + const silenceSupported = useGrafanaRulesSilenceSupport(); + const canSilenceInFolder = useCanSilenceInFolder(rule?.folderUid); + + const abilities = useMemo>(() => { + const isProvisioned = rule ? isProvisionedPromRule(rule) : false; + + // Note: Grafana managed rules can't be federated - this is strictly a Mimir feature + // See: https://grafana.com/docs/mimir/latest/references/architecture/components/ruler/#federated-rule-groups + const isFederated = false; + // All GrafanaPromRuleDTO rules are Grafana-managed by definition + const isAlertingRule = prometheusRuleType.grafana.alertingRule(rule); + const isPluginProvided = isPluginProvidedRule(rule); + + // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited + const immutableRule = isProvisioned || isFederated || isPluginProvided; + + // GrafanaPromRuleDTO rules are always supported (no loading state for ruler availability) + const MaybeSupported = loading ? NotSupported : AlwaysSupported; + const MaybeSupportedUnlessImmutable = immutableRule ? NotSupported : MaybeSupported; + + // Creating duplicates of plugin-provided rules does not seem to make a lot of sense + const duplicateSupported = isPluginProvided ? NotSupported : MaybeSupported; + + const rulesPermissions = getRulesPermissions('grafana'); + + const abilities: Abilities = { + [AlertRuleAction.Duplicate]: toAbility(duplicateSupported, rulesPermissions.create), + [AlertRuleAction.View]: toAbility(AlwaysSupported, rulesPermissions.read), + [AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false], + [AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false], + [AlertRuleAction.Explore]: toAbility(AlwaysSupported, AccessControlAction.DataSourcesExplore), + [AlertRuleAction.Silence]: [silenceSupported, canSilenceInFolder && isAlertingRule], + [AlertRuleAction.ModifyExport]: [isAlertingRule, exportAllowed], + [AlertRuleAction.Pause]: [MaybeSupportedUnlessImmutable && isAlertingRule, isEditable ?? false], + [AlertRuleAction.Restore]: [MaybeSupportedUnlessImmutable && isAlertingRule, isEditable ?? false], + [AlertRuleAction.DeletePermanently]: [ + MaybeSupportedUnlessImmutable && isAlertingRule, + (isRemovable && isAdmin()) ?? false, + ], + }; + + return abilities; + }, [rule, loading, isEditable, isRemovable, canSilenceInFolder, exportAllowed, silenceSupported]); + + return abilities; +} + +interface IsGrafanaPromRuleEditableResult { + isEditable: boolean; + isRemovable: boolean; + loading: boolean; +} + +/** + * Hook for checking if a GrafanaPromRuleDTO is editable + * Adapted version of useIsRuleEditable for GrafanaPromRuleDTO + */ +function useIsGrafanaPromRuleEditable(rule?: GrafanaPromRuleDTO): IsGrafanaPromRuleEditableResult { + const folderUID = rule?.folderUid; + const { folder, loading } = useFolder(folderUID); + + return useMemo(() => { + if (!rule || !folderUID) { + return { isEditable: false, isRemovable: false, loading: false }; + } + + if (!folder) { + // Loading or invalid folder UID + return { + isEditable: false, + isRemovable: false, + loading, + }; + } + + // For Grafana-managed rules, check folder permissions + const rulesPermissions = getRulesPermissions('grafana'); + const canEditGrafanaRules = ctx.hasPermissionInMetadata(rulesPermissions.update, folder); + const canRemoveGrafanaRules = ctx.hasPermissionInMetadata(rulesPermissions.delete, folder); + + return { + isEditable: canEditGrafanaRules, + isRemovable: canRemoveGrafanaRules, + loading, + }; + }, [rule, folderUID, folder, loading]); +} + +export const skipToken = Symbol('ability-skip-token'); +type SkipToken = typeof skipToken; + +/** + * Hook for checking a single ability on a GrafanaPromRuleDTO + */ +export function useGrafanaPromRuleAbility(rule: GrafanaPromRuleDTO | SkipToken, action: AlertRuleAction): Ability { + const abilities = useAllGrafanaPromRuleAbilities(rule === skipToken ? undefined : rule); + + return useMemo(() => { + return abilities[action]; + }, [abilities, action]); +} + +/** + * Hook for checking multiple abilities on a GrafanaPromRuleDTO + */ +export function useGrafanaPromRuleAbilities( + rule: GrafanaPromRuleDTO | SkipToken, + actions: AlertRuleAction[] +): Ability[] { + const abilities = useAllGrafanaPromRuleAbilities(rule === skipToken ? undefined : rule); + + return useMemo(() => { + return actions.map((action) => abilities[action]); + }, [abilities, actions]); +} + export function useAllAlertmanagerAbilities(): Abilities { const { selectedAlertmanager, 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/alerting/unified/rule-list/FilterView.tsx b/public/app/features/alerting/unified/rule-list/FilterView.tsx index e29367da11a..08a8ecbce43 100644 --- a/public/app/features/alerting/unified/rule-list/FilterView.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterView.tsx @@ -12,7 +12,7 @@ import { hashRule } from '../utils/rule-id'; import { DataSourceRuleLoader } from './DataSourceRuleLoader'; import { FilterProgressState, FilterStatus } from './FilterViewStatus'; -import { GrafanaRuleLoader } from './GrafanaRuleLoader'; +import { GrafanaRuleListItem } from './GrafanaRuleListItem'; import LoadMoreHelper from './LoadMoreHelper'; import { UnknownRuleListItem } from './components/AlertRuleListItem'; import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader'; @@ -154,11 +154,11 @@ function FilterViewResults({ filterState }: FilterViewProps) { switch (origin) { case 'grafana': return ( - ); case 'datasource': diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx index 621cd6ecd2e..68913940e8e 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx @@ -2,6 +2,7 @@ import { render } from 'test/test-utils'; import { byRole, byTitle } from 'testing-library-selector'; import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime'; +import { AccessControlAction } from 'app/types'; import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { GrafanaPromRuleDTO, @@ -13,13 +14,13 @@ import { } from 'app/types/unified-alerting-dto'; import { setupMswServer } from '../mockApi'; -import { mockGrafanaPromAlertingRule, mockGrafanaRulerRule } from '../mocks'; +import { grantUserPermissions } from '../mocks'; import { grafanaRulerGroup, grafanaRulerNamespace } from '../mocks/grafanaRulerApi'; -import { setGrafanaPromRules } from '../mocks/server/configure'; +import { setFolderAccessControl, setGrafanaPromRules } from '../mocks/server/configure'; import { rulerRuleType } from '../utils/rules'; import { intervalToSeconds } from '../utils/time'; -import { GrafanaGroupLoader, matchRules } from './GrafanaGroupLoader'; +import { GrafanaGroupLoader } from './GrafanaGroupLoader'; setPluginLinksHook(() => ({ links: [], isLoading: false })); setPluginComponentsHook(() => ({ components: [], isLoading: false })); @@ -32,9 +33,35 @@ const ui = { ruleLink: (ruleName: string) => byRole('link', { name: ruleName }), editButton: () => byRole('link', { name: 'Edit' }), moreButton: () => byRole('button', { name: 'More' }), + // Menu items that appear when More button is clicked + menuItems: { + silence: () => byRole('menuitem', { name: /silence/i }), + duplicate: () => byRole('menuitem', { name: /duplicate/i }), + copyLink: () => byRole('menuitem', { name: /copy link/i }), + export: () => byRole('menuitem', { name: /export/i }), + delete: () => byRole('menuitem', { name: /delete/i }), + }, }; describe('GrafanaGroupLoader', () => { + beforeEach(() => { + grantUserPermissions([ + AccessControlAction.AlertingRuleUpdate, + AccessControlAction.AlertingRuleDelete, + AccessControlAction.AlertingSilenceCreate, + AccessControlAction.AlertingRuleCreate, + AccessControlAction.AlertingRuleRead, + ]); + // Grant necessary permissions for editing rules + setFolderAccessControl({ + [AccessControlAction.AlertingRuleUpdate]: true, + [AccessControlAction.AlertingRuleDelete]: true, + [AccessControlAction.AlertingSilenceCreate]: true, + [AccessControlAction.AlertingRuleCreate]: true, // For duplicate action + [AccessControlAction.AlertingRuleRead]: true, // For export action + }); + }); + it('should render rule with url when ruler and prom rule exist', async () => { setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]); @@ -55,8 +82,8 @@ describe('GrafanaGroupLoader', () => { ); }); - it('should render rule with url and creating state when only ruler rule exists', async () => { - setGrafanaPromRules([]); + it('should render More button with action menu options', async () => { + setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]); const groupIdentifier = getGroupIdentifier(grafanaRulerGroup); @@ -65,92 +92,119 @@ describe('GrafanaGroupLoader', () => { const [rule1] = grafanaRulerGroup.rules; const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find(); - const creatingIcon = ui.ruleStatus('Creating').get(ruleListItem); - expect(creatingIcon).toBeInTheDocument(); + // Check that More button is present + const moreButton = ui.moreButton().get(ruleListItem); + expect(moreButton).toBeInTheDocument(); - const ruleLink = ui.ruleLink(rule1.grafana_alert.title).get(ruleListItem); - expect(ruleLink).toHaveAttribute( - 'href', - expect.stringContaining(`/alerting/grafana/${rule1.grafana_alert.uid}/view`) - ); + // Verify More button accessibility + expect(moreButton).toHaveAttribute('aria-label', 'More'); + expect(moreButton).toHaveTextContent('More'); }); - it('should render delete rule operation list item when only prom rule exists', async () => { - const promOnlyGroup: GrafanaPromRuleGroupDTO = { - ...rulerGroupToPromGroup(grafanaRulerGroup), - name: 'prom-only-group', + it('should render multiple rules with their own action buttons', async () => { + // Create a group with multiple rules + const multiRuleGroup = { + ...grafanaRulerGroup, + rules: [ + grafanaRulerGroup.rules[0], + { + ...grafanaRulerGroup.rules[0], + grafana_alert: { + ...grafanaRulerGroup.rules[0].grafana_alert, + uid: 'second-rule-uid', + title: 'Second Rule', + }, + }, + ], }; - setGrafanaPromRules([promOnlyGroup]); + setGrafanaPromRules([rulerGroupToPromGroup(multiRuleGroup)]); - const groupIdentifier = getGroupIdentifier(promOnlyGroup); + const groupIdentifier = getGroupIdentifier(multiRuleGroup); render(); - const [rule1] = promOnlyGroup.rules; - const promRule = await ui.ruleItem(rule1.name).find(); + // Check first rule + const [rule1, rule2] = multiRuleGroup.rules; + const ruleListItem1 = await ui.ruleItem(rule1.grafana_alert.title).find(); + const ruleListItem2 = await ui.ruleItem(rule2.grafana_alert.title).find(); - const deletingIcon = ui.ruleStatus('Deleting').get(promRule); - expect(deletingIcon).toBeInTheDocument(); + // Each rule should have its own More button + expect(ui.moreButton().get(ruleListItem1)).toBeInTheDocument(); + expect(ui.moreButton().get(ruleListItem2)).toBeInTheDocument(); - expect(ui.editButton().query(promRule)).not.toBeInTheDocument(); - expect(ui.moreButton().query(promRule)).not.toBeInTheDocument(); - }); -}); + // Check that edit buttons are present and have correct URLs + const editButton1 = ui.editButton().get(ruleListItem1); + const editButton2 = ui.editButton().get(ruleListItem2); -describe('matchRules', () => { - it('should return matches for all items and have empty promOnlyRules if all rules are matched by uid', () => { - const rulerRules = [ - mockGrafanaRulerRule({ uid: '1' }), - mockGrafanaRulerRule({ uid: '2' }), - mockGrafanaRulerRule({ uid: '3' }), - ]; + expect(editButton1).toBeInTheDocument(); + expect(editButton2).toBeInTheDocument(); - const promRules = rulerRules.map(rulerRuleToPromRule); - - const { matches, promOnlyRules } = matchRules(promRules, rulerRules); - - expect(matches.size).toBe(rulerRules.length); - expect(promOnlyRules).toHaveLength(0); - - for (const [rulerRule, promRule] of matches) { - expect(rulerRule.grafana_alert.uid).toBe(promRule.uid); - } + // Check that edit buttons have correct URLs (the actual format is simpler) + expect(editButton1).toHaveAttribute('href', expect.stringContaining(`/alerting/${rule1.grafana_alert.uid}/edit`)); + expect(editButton2).toHaveAttribute('href', expect.stringContaining(`/alerting/${rule2.grafana_alert.uid}/edit`)); }); - it('should return unmatched prometheus rules in promOnlyRules array', () => { - const rulerRules = [mockGrafanaRulerRule({ uid: '1' }), mockGrafanaRulerRule({ uid: '2' })]; + it('should not render edit button when user lacks edit permissions', async () => { + // Override permissions to deny editing + setFolderAccessControl({ + [AccessControlAction.AlertingRuleUpdate]: false, + [AccessControlAction.AlertingRuleDelete]: false, + }); - const matchingPromRules = rulerRules.map(rulerRuleToPromRule); - const unmatchedPromRules = [mockGrafanaPromAlertingRule({ uid: '3' }), mockGrafanaPromAlertingRule({ uid: '4' })]; + setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]); - const allPromRules = [...matchingPromRules, ...unmatchedPromRules]; - const { matches, promOnlyRules } = matchRules(allPromRules, rulerRules); + const groupIdentifier = getGroupIdentifier(grafanaRulerGroup); - expect(matches.size).toBe(rulerRules.length); - expect(promOnlyRules).toHaveLength(unmatchedPromRules.length); - expect(promOnlyRules).toEqual(expect.arrayContaining(unmatchedPromRules)); + render(); + + const [rule1] = grafanaRulerGroup.rules; + const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find(); + + // Edit button should not be present + expect(ui.editButton().query(ruleListItem)).not.toBeInTheDocument(); + + // More button should still be present (for other actions like viewing) + expect(ui.moreButton().get(ruleListItem)).toBeInTheDocument(); }); - it('should not include ruler rules in matches if they have no prometheus counterpart', () => { - const rulerRules = [ - mockGrafanaRulerRule({ uid: '1' }), - mockGrafanaRulerRule({ uid: '2' }), - mockGrafanaRulerRule({ uid: '3' }), - ]; + it('should render correct menu actions when More button is clicked', async () => { + setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]); - // Only create prom rule for the second ruler rule - const promRules = [rulerRuleToPromRule(rulerRules[1])]; + const groupIdentifier = getGroupIdentifier(grafanaRulerGroup); - const { matches, promOnlyRules } = matchRules(promRules, rulerRules); + const { user } = render( + + ); - expect(matches.size).toBe(1); - expect(promOnlyRules).toHaveLength(0); + const [rule1] = grafanaRulerGroup.rules; + const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find(); - // Verify that only the second ruler rule is in matches - expect(matches.has(rulerRules[0])).toBe(false); - expect(matches.get(rulerRules[1])).toBe(promRules[0]); - expect(matches.has(rulerRules[2])).toBe(false); + // Find and click the More button + const moreButton = ui.moreButton().get(ruleListItem); + await user.click(moreButton); + + // Check that the dropdown menu appears + const menu = byRole('menu').get(); + expect(menu).toBeInTheDocument(); + + // With proper permissions, all 4 menu actions should be available: + + // 1. Silence notifications - available for alerting rules (AlertingSilenceCreate permission) + expect(ui.menuItems.silence().get()).toBeInTheDocument(); + + // 2. Copy link - always available + expect(ui.menuItems.copyLink().get()).toBeInTheDocument(); + + // 3. Duplicate - should be available with create permissions (AlertingRuleCreate permission) + expect(ui.menuItems.duplicate().get()).toBeInTheDocument(); + + // 4. Export - should be available for Grafana alerting rules (AlertingRuleRead permission) + expect(ui.menuItems.export().get()).toBeInTheDocument(); + + // Verify that the menu contains all 4 expected menu items + const menuItems = byRole('menuitem').getAll(); + expect(menuItems.length).toBe(4); }); }); diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx index c092a748388..2dee977835d 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx @@ -1,22 +1,13 @@ -import { useMemo } from 'react'; - import { t } from '@grafana/i18n'; import { Alert } from '@grafana/ui'; import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; -import { GrafanaPromRuleDTO, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; -import { logWarning } from '../Analytics'; -import { alertRuleApi } from '../api/alertRuleApi'; import { prometheusApi } from '../api/prometheusApi'; import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants'; -import { GrafanaRulesSource } from '../utils/datasource'; -import { GrafanaRuleListItem } from './GrafanaRuleLoader'; -import { RuleOperationListItem } from './components/AlertRuleListItem'; +import { GrafanaRuleListItem } from './GrafanaRuleListItem'; import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader'; -import { RuleOperation } from './components/RuleListIcon'; -const { useGetGrafanaRulerGroupQuery } = alertRuleApi; const { useGetGrafanaGroupsQuery } = prometheusApi; export interface GrafanaGroupLoaderProps { @@ -48,20 +39,8 @@ export function GrafanaGroupLoader({ }, { pollingInterval: RULE_LIST_POLL_INTERVAL_MS } ); - const { data: rulerResponse, isLoading: isRulerGroupLoading } = useGetGrafanaRulerGroupQuery({ - folderUid: groupIdentifier.namespace.uid, - groupName: groupIdentifier.groupName, - }); - const { matches, promOnlyRules } = useMemo(() => { - const promRules = promResponse?.data.groups.at(0)?.rules ?? []; - const rulerRules = rulerResponse?.rules ?? []; - - return matchRules(promRules, rulerRules); - }, [promResponse, rulerResponse]); - - const isLoading = isPromResponseLoading || isRulerGroupLoading; - if (isLoading) { + if (isPromResponseLoading) { return ( <> {Array.from({ length: expectedRulesCount }).map((_, index) => ( @@ -71,7 +50,7 @@ export function GrafanaGroupLoader({ ); } - if (!rulerResponse && !promResponse) { + if (!promResponse) { return ( - {rulerResponse?.rules.map((rulerRule) => { - const promRule = matches.get(rulerRule); - - if (!promRule) { - return ( - - ); - } - + {promResponse.data.groups.at(0)?.rules.map((promRule) => { return ( group hierarchy @@ -115,58 +77,6 @@ export function GrafanaGroupLoader({ /> ); })} - {promOnlyRules.map((rule) => ( - - ))} ); } - -interface MatchingResult { - matches: Map; - /** - * Rules that were already removed from the Ruler but the changes has not been yet propagated to Prometheus - */ - promOnlyRules: GrafanaPromRuleDTO[]; -} - -export function matchRules( - promRules: GrafanaPromRuleDTO[], - rulerRules: RulerGrafanaRuleDTO[] -): Readonly { - const promRulesMap = new Map(promRules.map((rule) => [rule.uid, rule])); - - const matchingResult = rulerRules.reduce( - (acc, rulerRule) => { - const { matches } = acc; - const promRule = promRulesMap.get(rulerRule.grafana_alert.uid); - if (promRule) { - matches.set(rulerRule, promRule); - promRulesMap.delete(rulerRule.grafana_alert.uid); - } - return acc; - }, - { matches: new Map(), promOnlyRules: [] } - ); - - matchingResult.promOnlyRules.push(...promRulesMap.values()); - - if (matchingResult.promOnlyRules.length > 0) { - // Grafana Prometheus rules should be strongly consistent now so each Ruler rule should have a matching Prometheus rule - // If not, log it as a warning - logWarning('Grafana Managed Rules: No matching Prometheus rule found for Ruler rule', { - promOnlyRulesCount: matchingResult.promOnlyRules.length.toString(), - }); - } - - return matchingResult; -} diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx new file mode 100644 index 00000000000..3113c991e09 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx @@ -0,0 +1,71 @@ +import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; +import { GrafanaPromRuleDTO, PromRuleType } from 'app/types/unified-alerting-dto'; + +import { GrafanaRulesSource } from '../utils/datasource'; +import { totalFromStats } from '../utils/ruleStats'; +import { prometheusRuleType } from '../utils/rules'; +import { createRelativeUrl } from '../utils/url'; + +import { + AlertRuleListItem, + RecordingRuleListItem, + RuleListItemCommonProps, + UnknownRuleListItem, +} from './components/AlertRuleListItem'; +import { RuleActionsButtons } from './components/RuleActionsButtons.V2'; +import { RuleOperation } from './components/RuleListIcon'; + +interface GrafanaRuleListItemProps { + rule: GrafanaPromRuleDTO; + groupIdentifier: GrafanaRuleGroupIdentifier; + namespaceName: string; + operation?: RuleOperation; + showLocation?: boolean; +} + +export function GrafanaRuleListItem({ + rule, + groupIdentifier, + namespaceName, + operation, + showLocation = true, +}: GrafanaRuleListItemProps) { + const { name, uid, labels, provenance } = rule; + + const commonProps: RuleListItemCommonProps = { + name, + rulesSource: GrafanaRulesSource, + group: groupIdentifier.groupName, + namespace: namespaceName, + href: createRelativeUrl(`/alerting/grafana/${uid}/view`), + health: rule?.health, + error: rule?.lastError, + labels: labels, + isProvisioned: Boolean(provenance), + isPaused: rule?.isPaused, + application: 'grafana' as const, + actions: , + }; + + if (prometheusRuleType.grafana.alertingRule(rule)) { + const promAlertingRule = rule && rule.type === PromRuleType.Alerting ? rule : undefined; + const instancesCount = totalFromStats(promAlertingRule?.totals ?? {}); + + return ( + + ); + } + + if (prometheusRuleType.grafana.recordingRule(rule)) { + return ; + } + + return ; +} diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx deleted file mode 100644 index 3ca5e6a7496..00000000000 --- a/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { Trans, t } from '@grafana/i18n'; -import { Alert } from '@grafana/ui'; -import { GrafanaRuleGroupIdentifier, GrafanaRuleIdentifier } from 'app/types/unified-alerting'; -import { GrafanaPromRuleDTO, PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; - -import { alertRuleApi } from '../api/alertRuleApi'; -import { prometheusApi } from '../api/prometheusApi'; -import { createReturnTo } from '../hooks/useReturnTo'; -import { GrafanaRulesSource } from '../utils/datasource'; -import { totalFromStats } from '../utils/ruleStats'; -import { rulerRuleType } from '../utils/rules'; -import { createRelativeUrl } from '../utils/url'; - -import { - AlertRuleListItem, - RecordingRuleListItem, - RuleListItemCommonProps, - UnknownRuleListItem, -} from './components/AlertRuleListItem'; -import { AlertRuleListItemSkeleton, RulerRuleLoadingError } from './components/AlertRuleListItemLoader'; -import { RuleActionsButtons } from './components/RuleActionsButtons.V2'; -import { RuleOperation } from './components/RuleListIcon'; - -const { useGetGrafanaRulerGroupQuery } = alertRuleApi; -const { useGetGrafanaGroupsQuery } = prometheusApi; - -interface GrafanaRuleLoaderProps { - ruleIdentifier: GrafanaRuleIdentifier; - groupIdentifier: GrafanaRuleGroupIdentifier; - namespaceName: string; -} - -export function GrafanaRuleLoader({ ruleIdentifier, groupIdentifier, namespaceName }: GrafanaRuleLoaderProps) { - const { - data: rulerRuleGroup, - error: rulerRuleGroupError, - isLoading: isRulerRuleGroupLoading, - } = useGetGrafanaRulerGroupQuery({ - folderUid: groupIdentifier.namespace.uid, - groupName: groupIdentifier.groupName, - }); - const { - data: promRuleGroup, - error: promRuleGroupError, - isLoading: isPromRuleGroupLoading, - } = useGetGrafanaGroupsQuery({ - folderUid: groupIdentifier.namespace.uid, - groupName: groupIdentifier.groupName, - }); - - const rulerRule = rulerRuleGroup?.rules.find((rulerRule) => rulerRule.grafana_alert.uid === ruleIdentifier.uid); - const promRule = promRuleGroup?.data.groups - .flatMap((group) => group.rules) - .find((promRule) => promRule.uid === ruleIdentifier.uid); - - if (rulerRuleGroupError || promRuleGroupError) { - return ; - } - - if (isRulerRuleGroupLoading || isPromRuleGroupLoading) { - return ; - } - - if (!rulerRule) { - return ( - - - Cannot find rule details for UID {{ uid: ruleIdentifier.uid ?? '' }} - - - ); - } - - return ( - - ); -} - -interface GrafanaRuleListItemProps { - rule?: GrafanaPromRuleDTO; - rulerRule: RulerGrafanaRuleDTO; - groupIdentifier: GrafanaRuleGroupIdentifier; - namespaceName: string; - operation?: RuleOperation; - showLocation?: boolean; -} - -export function GrafanaRuleListItem({ - rule, - rulerRule, - groupIdentifier, - namespaceName, - operation, - showLocation = true, -}: GrafanaRuleListItemProps) { - const returnTo = createReturnTo(); - - const { - grafana_alert: { uid, title, provenance, is_paused }, - annotations = {}, - labels = {}, - } = rulerRule; - - const commonProps: RuleListItemCommonProps = { - name: title, - rulesSource: GrafanaRulesSource, - group: groupIdentifier.groupName, - namespace: namespaceName, - href: createRelativeUrl(`/alerting/grafana/${uid}/view`, { returnTo }), - health: rule?.health, - error: rule?.lastError, - labels: labels, - isProvisioned: Boolean(provenance), - isPaused: rule?.isPaused ?? is_paused, - application: 'grafana' as const, - actions: , - showLocation, - }; - - if (rulerRuleType.grafana.alertingRule(rulerRule)) { - const promAlertingRule = rule && rule.type === PromRuleType.Alerting ? rule : undefined; - const instancesCount = totalFromStats(promAlertingRule?.totals ?? {}); - - return ( - - ); - } - - if (rulerRuleType.grafana.recordingRule(rulerRule)) { - return ; - } - - return ; -} diff --git a/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx b/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx index 2c183bc6059..37e56db4058 100644 --- a/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx +++ b/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { RequireAtLeastOne } from 'type-fest'; import { Trans, t } from '@grafana/i18n'; import { LinkButton, Stack } from '@grafana/ui'; @@ -6,24 +7,34 @@ import AlertRuleMenu from 'app/features/alerting/unified/components/rule-viewer/ import { useDeleteModal } from 'app/features/alerting/unified/components/rule-viewer/DeleteModal'; import { RedirectToCloneRule } from 'app/features/alerting/unified/components/rules/CloneRule'; import SilenceGrafanaRuleDrawer from 'app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer'; -import { Rule, RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting'; +import { + EditableRuleIdentifier, + GrafanaRuleIdentifier, + Rule, + RuleGroupIdentifierV2, + RuleIdentifier, +} from 'app/types/unified-alerting'; import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; -import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; +import { logWarning } from '../../Analytics'; +import { AlertRuleAction, skipToken, useGrafanaPromRuleAbility, useRulerRuleAbility } from '../../hooks/useAbilities'; import * as ruleId from '../../utils/rule-id'; -import { isProvisionedRule, rulerRuleType } from '../../utils/rules'; +import { isProvisionedPromRule, isProvisionedRule, prometheusRuleType, rulerRuleType } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; -interface Props { - rule: RulerRuleDTO; +type RuleProps = RequireAtLeastOne<{ + rule?: RulerRuleDTO; promRule?: Rule; +}>; + +type Props = RuleProps & { groupIdentifier: RuleGroupIdentifierV2; /** * Should we show the buttons in a "compact" state? * i.e. without text and using smaller button sizes */ compact?: boolean; -} +}; // For now this is just a copy of RuleActionsButtons.tsx but with the View button removed. // This is only done to keep the new list behind a feature flag and limit changes in the existing components @@ -37,16 +48,26 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }: { identifier: RuleIdentifier; isProvisioned: boolean } | undefined >(undefined); - const isProvisioned = isProvisionedRule(rule); + const isProvisioned = getIsProvisioned(rule, promRule); const [editRuleSupported, editRuleAllowed] = useRulerRuleAbility(rule, groupIdentifier, AlertRuleAction.Update); + // If the consumer of this component comes from the alert list view, we need to use promRule to check abilities and permissions, + // as we have removed all requests to the ruler API in the list view. + const [grafanaEditRuleSupported, grafanaEditRuleAllowed] = useGrafanaPromRuleAbility( + prometheusRuleType.grafana.rule(promRule) ? promRule : skipToken, + AlertRuleAction.Update + ); - const canEditRule = editRuleSupported && editRuleAllowed; + const canEditRule = (editRuleSupported && editRuleAllowed) || (grafanaEditRuleSupported && grafanaEditRuleAllowed); const buttons: JSX.Element[] = []; const buttonSize = compact ? 'sm' : 'md'; - const identifier = ruleId.fromRulerRuleAndGroupIdentifierV2(groupIdentifier, rule); + const identifier = getEditableIdentifier(groupIdentifier, rule, promRule); + + if (!identifier) { + return null; + } if (canEditRule) { const editURL = createRelativeUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`); @@ -93,3 +114,38 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }: ); } + +function getIsProvisioned(rule?: RulerRuleDTO, promRule?: Rule): boolean { + if (rule) { + return isProvisionedRule(rule); + } + + if (promRule) { + return isProvisionedPromRule(promRule); + } + + return false; +} + +function getEditableIdentifier( + groupIdentifier: RuleGroupIdentifierV2, + rule?: RulerRuleDTO, + promRule?: Rule +): EditableRuleIdentifier | undefined { + if (rule) { + return ruleId.fromRulerRuleAndGroupIdentifierV2(groupIdentifier, rule); + } + + if (prometheusRuleType.grafana.rule(promRule)) { + return { + ruleSourceName: 'grafana', + uid: promRule.uid, + } satisfies GrafanaRuleIdentifier; + } + + logWarning('Unable to construct an editable rule identifier'); + + // Returning undefined is safer than throwing here as it allows the component to gracefully handle + // the error by returning null instead of crashing the entire component tree + return undefined; +} diff --git a/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts index 40d786f03f7..184489ccdc2 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts @@ -4,7 +4,6 @@ import { useDispatch } from 'app/types/store'; import { DataSourceRulesSourceIdentifier, RuleHealth } from 'app/types/unified-alerting'; import { PromAlertingRuleState, PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; -import { alertRuleApi } from '../../api/alertRuleApi'; import { PromRulesResponse, prometheusApi } from '../../api/prometheusApi'; const { useLazyGetGroupsQuery, useLazyGetGrafanaGroupsQuery } = prometheusApi; @@ -83,13 +82,6 @@ export function useGrafanaGroupsGenerator(hookOptions: UseGeneratorHookOptions = // Because the user waits a bit longer for the initial load but doesn't need to wait for each group to be loaded if (hookOptions.populateCache) { const cacheAndRulerPreload = response.data.groups.map(async (group) => { - dispatch( - alertRuleApi.util.prefetch( - 'getGrafanaRulerGroup', - { folderUid: group.folderUid, groupName: group.name }, - { force: true } - ) - ); await dispatch( prometheusApi.util.upsertQueryData( 'getGrafanaGroups', 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/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index bbd7a08db95..91ca9915e34 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -168,6 +168,10 @@ export function isProvisionedRule(rulerRule: RulerRuleDTO): boolean { return isGrafanaRulerRule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); } +export function isProvisionedPromRule(promRule: PromRuleDTO): boolean { + return prometheusRuleType.grafana.rule(promRule) && Boolean(promRule.provenance); +} + export function isProvisionedRuleGroup(group: RulerRuleGroupDTO): boolean { return group.rules.some((rule) => isProvisionedRule(rule)); } 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/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 15fd546a36b..76b7fe057b9 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -27,7 +27,8 @@ import CreateNewButton from './components/CreateNewButton'; import { FolderActionsButton } from './components/FolderActionsButton'; import { SearchView } from './components/SearchView'; import { getFolderPermissions } from './permissions'; -import { setAllSelection, useHasSelection } from './state'; +import { useHasSelection } from './state/hooks'; +import { setAllSelection } from './state/slice'; // New Browse/Manage/Search Dashboards views for nested folders const BrowseDashboardsPage = memo(() => { diff --git a/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx b/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx index 3e6a25cfadf..06fc910a683 100644 --- a/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx +++ b/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx @@ -16,7 +16,8 @@ import { RecentlyDeletedActions } from './components/RecentlyDeletedActions'; import { RecentlyDeletedEmptyState } from './components/RecentlyDeletedEmptyState'; import { SearchView } from './components/SearchView'; import { getFolderPermissions } from './permissions'; -import { setAllSelection, useHasSelection } from './state'; +import { useHasSelection } from './state/hooks'; +import { setAllSelection } from './state/slice'; const RecentlyDeletedPage = memo(() => { const dispatch = useDispatch(); diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 22ef7ed3ba2..fdd091aecc3 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -25,7 +25,7 @@ import { SaveDashboardResponseDTO, } from 'app/types'; -import { refetchChildren, refreshParents } from '../state'; +import { refetchChildren, refreshParents } from '../state/actions'; import { DashboardTreeSelection } from '../types'; import { isProvisionedDashboard, isProvisionedFolder } from './isProvisioned'; diff --git a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx index 87168ed0394..931c66cfd74 100644 --- a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx +++ b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx @@ -9,7 +9,8 @@ import { useDispatch } from 'app/types'; import { ShowModalReactEvent } from 'app/types/events'; import { useDeleteItemsMutation, useMoveItemsMutation } from '../../api/browseDashboardsAPI'; -import { setAllSelection, useActionSelectionState } from '../../state'; +import { useActionSelectionState } from '../../state/hooks'; +import { setAllSelection } from '../../state/slice'; import { DashboardTreeSelection } from '../../types'; import { DeleteModal } from './DeleteModal'; diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index 2b9adfebd9d..2a6c55a4ed3 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -6,17 +6,15 @@ import { DashboardViewItem } from 'app/features/search/types'; import { useDispatch } from 'app/types'; import { PAGE_SIZE } from '../api/services'; +import { fetchNextChildrenPage } from '../state/actions'; import { useFlatTreeState, useCheckboxSelectionState, - setFolderOpenState, - setItemSelectionState, useChildrenByParentUIDState, - setAllSelection, useBrowseLoadingStatus, useLoadNextChildrenPage, - fetchNextChildrenPage, -} from '../state'; +} from '../state/hooks'; +import { setFolderOpenState, setItemSelectionState, setAllSelection } from '../state/slice'; import { BrowseDashboardsState, DashboardTreeSelection, SelectionState } from '../types'; import { DashboardsTree } from './DashboardsTree'; diff --git a/public/app/features/browse-dashboards/components/NameCell.tsx b/public/app/features/browse-dashboards/components/NameCell.tsx index c85d98d5f03..3db558966a2 100644 --- a/public/app/features/browse-dashboards/components/NameCell.tsx +++ b/public/app/features/browse-dashboards/components/NameCell.tsx @@ -10,7 +10,7 @@ import { getIconForItem } from 'app/features/search/service/utils'; import { Indent } from '../../../core/components/Indent/Indent'; import { FolderRepo } from '../../../core/components/NestedFolderPicker/FolderRepo'; -import { useChildrenByParentUIDState } from '../state'; +import { useChildrenByParentUIDState } from '../state/hooks'; import { DashboardsTreeCellProps } from '../types'; import { makeRowID } from './utils'; diff --git a/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx b/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx index 6f553f478b7..158c8d9fa71 100644 --- a/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx @@ -12,7 +12,8 @@ import { ShowModalReactEvent } from 'app/types/events'; import { deletedDashboardsCache } from '../../search/service/deletedDashboardsCache'; import { useListDeletedDashboardsQuery, useRestoreDashboardMutation } from '../api/browseDashboardsAPI'; import { useRecentlyDeletedStateManager } from '../api/useRecentlyDeletedStateManager'; -import { clearFolders, setAllSelection, useActionSelectionState } from '../state'; +import { useActionSelectionState } from '../state/hooks'; +import { clearFolders, setAllSelection } from '../state/slice'; import { RestoreModal } from './RestoreModal'; diff --git a/public/app/features/browse-dashboards/components/SearchView.tsx b/public/app/features/browse-dashboards/components/SearchView.tsx index 014a417c603..203d46a4307 100644 --- a/public/app/features/browse-dashboards/components/SearchView.tsx +++ b/public/app/features/browse-dashboards/components/SearchView.tsx @@ -9,7 +9,8 @@ import { SearchStateManager } from 'app/features/search/state/SearchStateManager import { DashboardViewItemKind, SearchState } from 'app/features/search/types'; import { useDispatch, useSelector } from 'app/types'; -import { setAllSelection, setItemSelectionState, useHasSelection } from '../state'; +import { useHasSelection } from '../state/hooks'; +import { setAllSelection, setItemSelectionState } from '../state/slice'; interface SearchViewProps { height: number; 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; } diff --git a/public/app/features/browse-dashboards/state/index.ts b/public/app/features/browse-dashboards/state/index.ts deleted file mode 100644 index c5779d55154..00000000000 --- a/public/app/features/browse-dashboards/state/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './slice'; -export * from './actions'; -export * from './hooks'; diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index 7f987fed873..1821670a621 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -7,7 +7,7 @@ import { config } from 'app/core/config'; import { BackgroundConfig, Constraint, LineConfig, Placement } from 'app/plugins/panel/canvas/panelcfg.gen'; import { LineStyleConfig } from '../../plugins/panel/canvas/editor/LineStyleEditor'; -import { DimensionContext } from '../dimensions'; +import { DimensionContext } from '../dimensions/context'; import { StandardEditorConfig } from './types'; diff --git a/public/app/features/canvas/elements/button.tsx b/public/app/features/canvas/elements/button.tsx index d05b04531a7..749504e80ff 100644 --- a/public/app/features/canvas/elements/button.tsx +++ b/public/app/features/canvas/elements/button.tsx @@ -6,7 +6,7 @@ import { t } from '@grafana/i18n'; import { TextDimensionMode } from '@grafana/schema'; import { Button, Spinner, useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions/context'; -import { ColorDimensionEditor } from 'app/features/dimensions/editors'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { APIEditor, APIEditorConfig } from 'app/plugins/panel/canvas/editor/element/APIEditor'; import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/editor/element/ButtonStyleEditor'; diff --git a/public/app/features/canvas/elements/cloud.tsx b/public/app/features/canvas/elements/cloud.tsx index c4688bf7300..f215b4dfd9e 100644 --- a/public/app/features/canvas/elements/cloud.tsx +++ b/public/app/features/canvas/elements/cloud.tsx @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; diff --git a/public/app/features/canvas/elements/droneFront.tsx b/public/app/features/canvas/elements/droneFront.tsx index 628b70c3afb..f8c7a0ef1bb 100644 --- a/public/app/features/canvas/elements/droneFront.tsx +++ b/public/app/features/canvas/elements/droneFront.tsx @@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; -import { DimensionContext } from 'app/features/dimensions'; -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; diff --git a/public/app/features/canvas/elements/droneSide.tsx b/public/app/features/canvas/elements/droneSide.tsx index c4ec5fcc73f..9ff0a6deabb 100644 --- a/public/app/features/canvas/elements/droneSide.tsx +++ b/public/app/features/canvas/elements/droneSide.tsx @@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; -import { DimensionContext } from 'app/features/dimensions'; -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; diff --git a/public/app/features/canvas/elements/droneTop.tsx b/public/app/features/canvas/elements/droneTop.tsx index 864a32363e3..4994bfa0d91 100644 --- a/public/app/features/canvas/elements/droneTop.tsx +++ b/public/app/features/canvas/elements/droneTop.tsx @@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; -import { DimensionContext } from 'app/features/dimensions'; -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; diff --git a/public/app/features/canvas/elements/icon.tsx b/public/app/features/canvas/elements/icon.tsx index 48ea5beffd2..bf10461706c 100644 --- a/public/app/features/canvas/elements/icon.tsx +++ b/public/app/features/canvas/elements/icon.tsx @@ -6,9 +6,10 @@ import { LinkModel } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema'; import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG'; -import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context'; -import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor'; +import { getPublicOrAbsoluteUrl } from 'app/features/dimensions/resource'; import { LineConfig } from 'app/plugins/panel/canvas/panelcfg.gen'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; diff --git a/public/app/features/canvas/elements/parallelogram.tsx b/public/app/features/canvas/elements/parallelogram.tsx index dc46cebf21f..2dc0df79799 100644 --- a/public/app/features/canvas/elements/parallelogram.tsx +++ b/public/app/features/canvas/elements/parallelogram.tsx @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; diff --git a/public/app/features/canvas/elements/server/server.tsx b/public/app/features/canvas/elements/server/server.tsx index 5ea7724b2f1..2ad0e4cd404 100644 --- a/public/app/features/canvas/elements/server/server.tsx +++ b/public/app/features/canvas/elements/server/server.tsx @@ -4,8 +4,9 @@ import { GrafanaTheme2, LinkModel } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema'; import config from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions'; -import { ColorDimensionEditor, ScalarDimensionEditor } from 'app/features/dimensions/editors'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps } from '../../element'; diff --git a/public/app/features/canvas/elements/triangle.tsx b/public/app/features/canvas/elements/triangle.tsx index 13cba93f980..35b00d05761 100644 --- a/public/app/features/canvas/elements/triangle.tsx +++ b/public/app/features/canvas/elements/triangle.tsx @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; diff --git a/public/app/features/canvas/elements/windTurbine.tsx b/public/app/features/canvas/elements/windTurbine.tsx index 8070ab41973..0da6fea9455 100644 --- a/public/app/features/canvas/elements/windTurbine.tsx +++ b/public/app/features/canvas/elements/windTurbine.tsx @@ -4,8 +4,8 @@ import { GrafanaTheme2, LinkModel } from '@grafana/data'; import { t } from '@grafana/i18n'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; -import { DimensionContext } from 'app/features/dimensions'; -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index df072b83c58..047d42a5e45 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -15,7 +15,7 @@ import { t } from '@grafana/i18n'; import { ConfirmModal } from '@grafana/ui'; import { LayerElement } from 'app/core/components/Layers/types'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { BackgroundImageSize, Constraint, diff --git a/public/app/features/canvas/runtime/frame.tsx b/public/app/features/canvas/runtime/frame.tsx index d58accc0ade..7b9765ddecc 100644 --- a/public/app/features/canvas/runtime/frame.tsx +++ b/public/app/features/canvas/runtime/frame.tsx @@ -1,7 +1,7 @@ import { cloneDeep } from 'lodash'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { HorizontalConstraint, Placement, VerticalConstraint } from 'app/plugins/panel/canvas/panelcfg.gen'; import { LayerActionID } from 'app/plugins/panel/canvas/types'; diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index 5be6001272d..5e54afd9be8 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -16,7 +16,7 @@ import { } from '@grafana/schema'; import { Portal } from '@grafana/ui'; import { config } from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions'; +import { DimensionContext } from 'app/features/dimensions/context'; import { getColorDimensionFromData, getResourceDimensionFromData, 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 diff --git a/public/app/features/connections/Connections.tsx b/public/app/features/connections/Connections.tsx index 05330a180b5..5515e9bf96b 100644 --- a/public/app/features/connections/Connections.tsx +++ b/public/app/features/connections/Connections.tsx @@ -3,14 +3,12 @@ import { Navigate, Routes, Route, useLocation } from 'react-router-dom-v5-compat import { StoreState, useSelector } from 'app/types'; import { ROUTES } from './constants'; -import { - AddNewConnectionPage, - DataSourceDashboardsPage, - DataSourceDetailsPage, - DataSourcesListPage, - EditDataSourcePage, - NewDataSourcePage, -} from './pages'; +import { AddNewConnectionPage } from './pages/AddNewConnectionPage'; +import { DataSourceDashboardsPage } from './pages/DataSourceDashboardsPage'; +import { DataSourceDetailsPage } from './pages/DataSourceDetailsPage'; +import { DataSourcesListPage } from './pages/DataSourcesListPage'; +import { EditDataSourcePage } from './pages/EditDataSourcePage'; +import { NewDataSourcePage } from './pages/NewDataSourcePage'; function RedirectToAddNewConnection() { const { search } = useLocation(); diff --git a/public/app/features/connections/pages/DataSourcesListPage.tsx b/public/app/features/connections/pages/DataSourcesListPage.tsx index 7a794ae3b52..5c67b07da24 100644 --- a/public/app/features/connections/pages/DataSourcesListPage.tsx +++ b/public/app/features/connections/pages/DataSourcesListPage.tsx @@ -2,7 +2,7 @@ import { Page } from 'app/core/components/Page/Page'; import { AdvisorRedirectNotice } from 'app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice'; import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton'; import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; -import { getDataSourcesCount } from 'app/features/datasources/state'; +import { getDataSourcesCount } from 'app/features/datasources/state/selectors'; import { StoreState, useSelector } from 'app/types'; export function DataSourcesListPage() { diff --git a/public/app/features/connections/pages/index.tsx b/public/app/features/connections/pages/index.tsx deleted file mode 100644 index 64d2ec896ec..00000000000 --- a/public/app/features/connections/pages/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export { AddNewConnectionPage } from './AddNewConnectionPage'; -export { DataSourceDetailsPage } from './DataSourceDetailsPage'; -export { DataSourcesListPage } from './DataSourcesListPage'; -export { DataSourceDashboardsPage } from './DataSourceDashboardsPage'; -export { EditDataSourcePage } from './EditDataSourcePage'; -export { NewDataSourcePage } from './NewDataSourcePage'; diff --git a/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx b/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx index fe2006270ae..80b08bb663e 100644 --- a/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx +++ b/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx @@ -5,12 +5,10 @@ import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { featureEnabled } from '@grafana/runtime'; import { Card, Grid, useStyles2, Stack, Badge } from '@grafana/ui'; -import { - PluginDeprecatedBadge, - PluginDisabledBadge, - PluginInstalledBadge, - PluginUpdateAvailableBadge, -} from 'app/features/plugins/admin/components/Badges'; +import { PluginDeprecatedBadge } from 'app/features/plugins/admin/components/Badges/PluginDeprecatedBadge'; +import { PluginDisabledBadge } from 'app/features/plugins/admin/components/Badges/PluginDisabledBadge'; +import { PluginInstalledBadge } from 'app/features/plugins/admin/components/Badges/PluginInstallBadge'; +import { PluginUpdateAvailableBadge } from 'app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge'; import { getBadgeColor } from 'app/features/plugins/admin/components/Badges/sharedStyles'; import { isPluginUpdatable } from 'app/features/plugins/admin/helpers'; import { CatalogPlugin } from 'app/features/plugins/admin/types'; diff --git a/public/app/features/correlations/Forms/AddCorrelationForm.tsx b/public/app/features/correlations/Forms/AddCorrelationForm.tsx index bda19c40de6..cba2a2bf76f 100644 --- a/public/app/features/correlations/Forms/AddCorrelationForm.tsx +++ b/public/app/features/correlations/Forms/AddCorrelationForm.tsx @@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { PanelContainer, useStyles2 } from '@grafana/ui'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; -import { Wizard } from '../components/Wizard'; +import { Wizard } from '../components/Wizard/Wizard'; import { useCorrelations } from '../useCorrelations'; import { ConfigureCorrelationBasicInfoForm } from './ConfigureCorrelationBasicInfoForm'; diff --git a/public/app/features/correlations/Forms/EditCorrelationForm.tsx b/public/app/features/correlations/Forms/EditCorrelationForm.tsx index 55417f4fad0..11d4f6ad441 100644 --- a/public/app/features/correlations/Forms/EditCorrelationForm.tsx +++ b/public/app/features/correlations/Forms/EditCorrelationForm.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { Wizard } from '../components/Wizard'; +import { Wizard } from '../components/Wizard/Wizard'; import { Correlation } from '../types'; import { useCorrelations } from '../useCorrelations'; diff --git a/public/app/features/correlations/components/Wizard/index.ts b/public/app/features/correlations/components/Wizard/index.ts deleted file mode 100644 index 7ccbf5e9774..00000000000 --- a/public/app/features/correlations/components/Wizard/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Wizard'; -export * from './types'; diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 5e8b9cd57ca..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', + }, + }, }, }; }); @@ -218,6 +251,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: {} }); @@ -1555,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, @@ -1580,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/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-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/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index cd54c64a63f..2d5a2901b06 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -59,7 +59,7 @@ import { buildGridItemForPanel, transformSaveModelToScene } from '../serializati import { gridItemToPanel } from '../serialization/transformSceneToSaveModel'; import { DecoratedRevisionModel } from '../settings/VersionsEditView'; import { DashboardEditView } from '../settings/utils'; -import { historySrv } from '../settings/version-history'; +import { historySrv } from '../settings/version-history/HistorySrv'; import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper'; import { isInCloneChain } from '../utils/clone'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; 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-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/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 8ebde16f6f1..0c99500c2cd 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -99,8 +99,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ 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/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/scene/layout-default/DashboardGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx index 05bd804b77c..e19803ed5f9 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx @@ -57,17 +57,38 @@ export class DashboardGridItem public constructor(state: DashboardGridItemState) { super(state); - this.addActivationHandler(() => this.handleVariableName()); + this.addActivationHandler(() => this._activationHandler()); + } + + private _activationHandler() { + this.handleVariableName(); + + return () => { + this._handleGridSizeUnsubscribe(); + }; + } + + private _handleGridSizeSubscribe() { + if (!this._gridSizeSub) { + this._gridSizeSub = this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState)); + } + } + + private _handleGridSizeUnsubscribe() { + if (this._gridSizeSub) { + this._gridSizeSub.unsubscribe(); + this._gridSizeSub = undefined; + } } private _handleGridResize(newState: DashboardGridItemState, prevState: DashboardGridItemState) { - const itemCount = this.state.repeatedPanels?.length ?? 1; - const stateChange: Partial = {}; - if (newState.height === prevState.height) { return; } + const itemCount = this.state.repeatedPanels?.length ?? 1; + const stateChange: Partial = {}; + if (this.getRepeatDirection() === 'v') { stateChange.itemHeight = Math.ceil(newState.height! / itemCount); } else { @@ -203,16 +224,9 @@ export class DashboardGridItem public handleVariableName() { if (this.state.variableName) { - if (!this._gridSizeSub) { - this._gridSizeSub = this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState)); - this._subs.add(this._gridSizeSub); - } + this._handleGridSizeSubscribe(); } else { - if (this._gridSizeSub) { - this._gridSizeSub.unsubscribe(); - this._subs.remove(this._gridSizeSub); - this._gridSizeSub = undefined; - } + this._handleGridSizeUnsubscribe(); } this.performRepeat(); 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/settings/AnnotationsEditView.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx index 72510de5130..bbc49a8fc30 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx @@ -11,7 +11,8 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor } from '../utils/utils'; import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync'; -import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from './annotations'; +import { AnnotationSettingsEdit, newAnnotationName } from './annotations/AnnotationSettingsEdit'; +import { AnnotationSettingsList } from './annotations/AnnotationSettingsList'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; export enum MoveDirection { 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/VersionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx index c2d238354c0..501b6d7dfae 100644 --- a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx @@ -5,7 +5,7 @@ import { DashboardScene } from '../scene/DashboardScene'; import { activateFullSceneTree } from '../utils/test-utils'; import { VERSIONS_FETCH_LIMIT, VersionsEditView } from './VersionsEditView'; -import { historySrv } from './version-history'; +import { historySrv } from './version-history/HistorySrv'; jest.mock('./version-history/HistorySrv'); diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx index 6eb427966cf..1620111aecd 100644 --- a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx @@ -11,14 +11,11 @@ import { NavToolbarActions } from '../scene/NavToolbarActions'; import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; -import { - RevisionsModel, - VersionHistoryComparison, - VersionHistoryHeader, - VersionHistoryTable, - VersionsHistoryButtons, - historySrv, -} from './version-history'; +import { RevisionsModel, historySrv } from './version-history/HistorySrv'; +import { VersionsHistoryButtons } from './version-history/VersionHistoryButtons'; +import { VersionHistoryComparison } from './version-history/VersionHistoryComparison'; +import { VersionHistoryHeader } from './version-history/VersionHistoryHeader'; +import { VersionHistoryTable } from './version-history/VersionHistoryTable'; export const VERSIONS_FETCH_LIMIT = 10; diff --git a/public/app/features/dashboard-scene/settings/annotations/index.tsx b/public/app/features/dashboard-scene/settings/annotations/index.tsx deleted file mode 100644 index 5c5ebb6cb18..00000000000 --- a/public/app/features/dashboard-scene/settings/annotations/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit'; -export { AnnotationSettingsList } from './AnnotationSettingsList'; 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/settings/version-history/index.ts b/public/app/features/dashboard-scene/settings/version-history/index.ts deleted file mode 100644 index 880541e40d2..00000000000 --- a/public/app/features/dashboard-scene/settings/version-history/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { HistorySrv, historySrv, type RevisionsModel } from './HistorySrv'; -export { VersionHistoryTable } from './VersionHistoryTable'; -export { VersionHistoryHeader } from './VersionHistoryHeader'; -export { VersionsHistoryButtons } from './VersionHistoryButtons'; -export { VersionHistoryComparison } from './VersionHistoryComparison'; 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-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/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/components/AnnotationSettings/index.tsx b/public/app/features/dashboard/components/AnnotationSettings/index.tsx deleted file mode 100644 index 5c5ebb6cb18..00000000000 --- a/public/app/features/dashboard/components/AnnotationSettings/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit'; -export { AnnotationSettingsList } from './AnnotationSettingsList'; 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/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx index ffcf53a4085..add625aa42c 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -3,7 +3,8 @@ import { getDataSourceSrv, locationService } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; import { DashboardModel } from '../../state/DashboardModel'; -import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from '../AnnotationSettings'; +import { AnnotationSettingsEdit, newAnnotationName } from '../AnnotationSettings/AnnotationSettingsEdit'; +import { AnnotationSettingsList } from '../AnnotationSettings/AnnotationSettingsList'; import { SettingsPageProps } from './types'; 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/DashboardSettings/LinksSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx index 10f07a092d8..374c486497c 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx @@ -4,7 +4,8 @@ import { locationService } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; import { NEW_LINK } from 'app/features/dashboard-scene/settings/links/utils'; -import { LinkSettingsEdit, LinkSettingsList } from '../LinksSettings'; +import { LinkSettingsEdit } from '../LinksSettings/LinkSettingsEdit'; +import { LinkSettingsList } from '../LinksSettings/LinkSettingsList'; import { SettingsPageProps } from './types'; diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx index 31ee8c9ba2e..fad6f79bffc 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -4,12 +4,9 @@ import * as React from 'react'; import { config } from '@grafana/runtime'; import { Spinner, HorizontalGroup } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; -import { - historySrv, - RevisionsModel, - VersionHistoryHeader, - VersionsHistoryButtons, -} from 'app/features/dashboard-scene/settings/version-history'; +import { historySrv, RevisionsModel } from 'app/features/dashboard-scene/settings/version-history/HistorySrv'; +import { VersionsHistoryButtons } from 'app/features/dashboard-scene/settings/version-history/VersionHistoryButtons'; +import { VersionHistoryHeader } from 'app/features/dashboard-scene/settings/version-history/VersionHistoryHeader'; import { VersionHistoryComparison } from '../VersionHistory/VersionHistoryComparison'; import { VersionHistoryTable } from '../VersionHistory/VersionHistoryTable'; diff --git a/public/app/features/dashboard/components/LinksSettings/index.tsx b/public/app/features/dashboard/components/LinksSettings/index.tsx deleted file mode 100644 index 21e45f1e3ba..00000000000 --- a/public/app/features/dashboard/components/LinksSettings/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { LinkSettingsEdit } from './LinkSettingsEdit'; -export { LinkSettingsList } from './LinkSettingsList'; 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/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/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/dashboard/components/VersionHistory/useDashboardRestore.tsx b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx index fa192c5a7b8..f95568f8159 100644 --- a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx +++ b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx @@ -4,7 +4,7 @@ import { useAsyncFn } from 'react-use'; import { locationUtil } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; import { useAppNotification } from 'app/core/copy/appNotification'; -import { historySrv } from 'app/features/dashboard-scene/settings/version-history'; +import { historySrv } from 'app/features/dashboard-scene/settings/version-history/HistorySrv'; import { useSelector } from 'app/types'; import { dashboardWatcher } from '../../../live/dashboard/dashboardWatcher'; 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', }, }; diff --git a/public/app/features/dataframe-import/index.ts b/public/app/features/dataframe-import/index.ts deleted file mode 100644 index 3f51aca9553..00000000000 --- a/public/app/features/dataframe-import/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './utils'; -export * from './constants'; diff --git a/public/app/features/datasources/components/DataSourceDashboards.tsx b/public/app/features/datasources/components/DataSourceDashboards.tsx index 7b954ae9d03..d4011844e4c 100644 --- a/public/app/features/datasources/components/DataSourceDashboards.tsx +++ b/public/app/features/datasources/components/DataSourceDashboards.tsx @@ -6,7 +6,7 @@ import { loadPluginDashboards } from 'app/features/plugins/admin/state/actions'; import { PluginDashboard, StoreState, useDispatch, useSelector } from 'app/types'; import DashboardTable from '../components/DashboardsTable'; -import { useInitDataSourceSettings } from '../state'; +import { useInitDataSourceSettings } from '../state/hooks'; export type Props = { // The UID of the data source diff --git a/public/app/features/datasources/components/DataSourcesList.tsx b/public/app/features/datasources/components/DataSourcesList.tsx index 00f5ee5ae31..1437fa395e8 100644 --- a/public/app/features/datasources/components/DataSourcesList.tsx +++ b/public/app/features/datasources/components/DataSourcesList.tsx @@ -10,7 +10,8 @@ import { contextSrv } from 'app/core/core'; import { StoreState, AccessControlAction, useSelector } from 'app/types'; import { ROUTES } from '../../connections/constants'; -import { getDataSources, getDataSourcesCount, useLoadDataSources } from '../state'; +import { useLoadDataSources } from '../state/hooks'; +import { getDataSources, getDataSourcesCount } from '../state/selectors'; import { trackDataSourcesListViewed } from '../tracking'; import { DataSourcesListCard } from './DataSourcesListCard'; diff --git a/public/app/features/datasources/components/DataSourcesListHeader.tsx b/public/app/features/datasources/components/DataSourcesListHeader.tsx index cf0a97883d1..f23f8b3cb6b 100644 --- a/public/app/features/datasources/components/DataSourcesListHeader.tsx +++ b/public/app/features/datasources/components/DataSourcesListHeader.tsx @@ -5,7 +5,8 @@ import { SelectableValue } from '@grafana/data'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import { StoreState, useSelector, useDispatch } from 'app/types'; -import { getDataSourcesSearchQuery, getDataSourcesSort, setDataSourcesSearchQuery, setIsSortAscending } from '../state'; +import { setDataSourcesSearchQuery, setIsSortAscending } from '../state/reducers'; +import { getDataSourcesSearchQuery, getDataSourcesSort } from '../state/selectors'; import { trackDsSearched } from '../tracking'; const ascendingSortValue = 'alpha-asc'; diff --git a/public/app/features/datasources/components/EditDataSource.tsx b/public/app/features/datasources/components/EditDataSource.tsx index 077e66f931f..920979f0cd4 100644 --- a/public/app/features/datasources/components/EditDataSource.tsx +++ b/public/app/features/datasources/components/EditDataSource.tsx @@ -16,9 +16,6 @@ import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { DataSourceSettingsState, useDispatch } from 'app/types'; import { - dataSourceLoaded, - setDataSourceName, - setIsDefault, useDataSource, useDataSourceExploreUrl, useDataSourceMeta, @@ -28,7 +25,8 @@ import { useInitDataSourceSettings, useTestDataSource, useUpdateDatasource, -} from '../state'; +} from '../state/hooks'; +import { setIsDefault, setDataSourceName, dataSourceLoaded } from '../state/reducers'; import { trackDsConfigClicked, trackDsConfigUpdated } from '../tracking'; import { DataSourceRights } from '../types'; diff --git a/public/app/features/datasources/components/EditDataSourceActions.tsx b/public/app/features/datasources/components/EditDataSourceActions.tsx index b60f3774f0a..a2d18bb11f5 100644 --- a/public/app/features/datasources/components/EditDataSourceActions.tsx +++ b/public/app/features/datasources/components/EditDataSourceActions.tsx @@ -3,7 +3,7 @@ import { config } from '@grafana/runtime'; import { LinkButton } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; -import { useDataSource } from '../state'; +import { useDataSource } from '../state/hooks'; import { trackCreateDashboardClicked, trackDsConfigClicked, trackExploreClicked } from '../tracking'; import { constructDataSourceExploreUrl } from '../utils'; diff --git a/public/app/features/datasources/components/NewDataSource.tsx b/public/app/features/datasources/components/NewDataSource.tsx index db92959af19..7803caac224 100644 --- a/public/app/features/datasources/components/NewDataSource.tsx +++ b/public/app/features/datasources/components/NewDataSource.tsx @@ -10,12 +10,9 @@ import { DataSourcePluginCategory, StoreState, useDispatch, useSelector } from ' import { ROUTES } from '../../connections/constants'; import { DataSourceCategories } from '../components/DataSourceCategories'; import { DataSourceTypeCardList } from '../components/DataSourceTypeCardList'; -import { - useAddDatasource, - useLoadDataSourcePlugins, - getFilteredDataSourcePlugins, - setDataSourceTypeSearchQuery, -} from '../state'; +import { useAddDatasource, useLoadDataSourcePlugins } from '../state/hooks'; +import { setDataSourceTypeSearchQuery } from '../state/reducers'; +import { getFilteredDataSourcePlugins } from '../state/selectors'; export function NewDataSource() { useLoadDataSourcePlugins(); diff --git a/public/app/features/datasources/components/picker/DataSourceModal.tsx b/public/app/features/datasources/components/picker/DataSourceModal.tsx index 89f8226b3a9..0bfa3ca2744 100644 --- a/public/app/features/datasources/components/picker/DataSourceModal.tsx +++ b/public/app/features/datasources/components/picker/DataSourceModal.tsx @@ -15,7 +15,7 @@ import { Icon, ScrollContainer, } from '@grafana/ui'; -import * as DFImport from 'app/features/dataframe-import'; +import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { getFileDropToQueryHandler } from 'app/plugins/datasource/grafana/utils'; @@ -203,9 +203,9 @@ export function DataSourceModal({ readAs="readAsArrayBuffer" fileListRenderer={() => undefined} options={{ - maxSize: DFImport.maxFileSize, + maxSize: maxFileSize, multiple: false, - accept: DFImport.acceptedFiles, + accept: acceptedFiles, onDrop: onFileDrop, }} > 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/datasources/state/index.ts b/public/app/features/datasources/state/index.ts deleted file mode 100644 index 42fae7a84a7..00000000000 --- a/public/app/features/datasources/state/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './actions'; -export * from './buildCategories'; -export * from './hooks'; -export * from './navModel'; -export * from './reducers'; -export * from './selectors'; 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/ResourceDimensionEditor.tsx b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx index c56f6d1f2be..1b5d63348e6 100644 --- a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx @@ -7,8 +7,8 @@ import { ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema' import { InlineField, InlineFieldRow, RadioButtonGroup } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/internal'; -import { getPublicOrAbsoluteUrl, ResourceFolderName } from '..'; -import { MediaType, ResourceDimensionOptions, ResourcePickerSize } from '../types'; +import { getPublicOrAbsoluteUrl } from '../resource'; +import { MediaType, ResourceDimensionOptions, ResourceFolderName, ResourcePickerSize } from '../types'; import { ResourcePicker } from './ResourcePicker'; 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 f2b69690c62..d5eb712d47d 100644 --- a/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx @@ -11,16 +11,19 @@ import { NumberInput } from 'app/core/components/OptionsUI/NumberInput'; import { validateScaleOptions, validateScaleConfig } from '../scale'; import { ScaleDimensionOptions } from '../types'; -const fixedValueOption: SelectableValue = { - label: 'Fixed value', - value: '_____fixed_____', -}; - export const ScaleDimensionEditor = (props: StandardEditorProps) => { const { value, context, onChange, item } = props; const { settings } = item; const styles = useStyles2(getStyles); + const fixedValueOption: SelectableValue = useMemo( + () => ({ + label: t('dimensions.scale-dimension-editor.fixed-value-option.label.fixed-value', 'Fixed value'), + value: '_____fixed_____', + }), + [] + ); + const fieldName = value?.field; const isFixed = Boolean(!fieldName); const names = useFieldDisplayNames(context.data); @@ -53,7 +56,7 @@ export const ScaleDimensionEditor = (props: StandardEditorProps
diff --git a/public/app/features/dimensions/editors/index.ts b/public/app/features/dimensions/editors/index.ts deleted file mode 100644 index fe6418b3609..00000000000 --- a/public/app/features/dimensions/editors/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './ColorDimensionEditor'; -export * from './IconSelector'; -export * from './ResourceDimensionEditor'; -export * from './ScaleDimensionEditor'; -export * from './ScalarDimensionEditor'; -export * from './TextDimensionEditor'; diff --git a/public/app/features/dimensions/index.ts b/public/app/features/dimensions/index.ts deleted file mode 100644 index df2598082e9..00000000000 --- a/public/app/features/dimensions/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './types'; - -export * from './color'; -export * from './scale'; -export * from './text'; -export * from './utils'; -export * from './resource'; -export * from './context'; -export * from './scalar'; diff --git a/public/app/features/dimensions/utils.ts b/public/app/features/dimensions/utils.ts index 3e762cc5841..359e21bbf4c 100644 --- a/public/app/features/dimensions/utils.ts +++ b/public/app/features/dimensions/utils.ts @@ -7,15 +7,13 @@ import { ColorDimensionConfig, ScalarDimensionConfig, } from '@grafana/schema'; -import { - getColorDimension, - getScaledDimension, - getTextDimension, - getResourceDimension, - DimensionSupplier, -} from 'app/features/dimensions'; +import { getColorDimension } from './color'; +import { getResourceDimension } from './resource'; import { getScalarDimension } from './scalar'; +import { getScaledDimension } from './scale'; +import { getTextDimension } from './text'; +import { DimensionSupplier } from './types'; export function getColorDimensionFromData( data: PanelData | undefined, diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index 4df8b52f4cf..4afbe1d6b4a 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -27,17 +27,15 @@ import { useDispatch, useSelector } from 'app/types'; import { changePanelState } from '../state/explorePane'; -import { - SpanBarOptionsData, - SpanLinkFunc, - Trace, - TracePageHeader, - TraceTimelineViewer, - TTraceTimeline, -} from './components'; import memoizedTraceCriticalPath from './components/CriticalPath'; +import { TracePageHeader } from './components/TracePageHeader'; import SpanGraph from './components/TracePageHeader/SpanGraph'; +import TraceTimelineViewer from './components/TraceTimelineViewer'; import { TraceFlameGraphs } from './components/TraceTimelineViewer/SpanDetail'; +import { SpanBarOptionsData } from './components/settings/SpanBarSettings'; +import TTraceTimeline from './components/types/TTraceTimeline'; +import { SpanLinkFunc } from './components/types/links'; +import { Trace } from './components/types/trace'; import { createSpanLinkFactory } from './createSpanLink'; import { useChildrenState } from './useChildrenState'; import { useDetailState } from './useDetailState'; diff --git a/public/app/features/explore/TraceView/components/CriticalPath/index.tsx b/public/app/features/explore/TraceView/components/CriticalPath/index.tsx index 7d8a8d3dc99..6a485972c0e 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/index.tsx +++ b/public/app/features/explore/TraceView/components/CriticalPath/index.tsx @@ -14,7 +14,7 @@ import memoizeOne from 'memoize-one'; -import { CriticalPathSection, Trace, TraceSpan } from '../types'; +import { TraceSpan, CriticalPathSection, Trace } from '../types/trace'; import findLastFinishingChildSpan from './utils/findLastFinishingChildSpan'; import getChildOfSpans from './utils/getChildOfSpans'; diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test1.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test1.ts index d2f0cf757fd..4e37e81ef21 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test1.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test1.ts @@ -27,7 +27,9 @@ Here +++++ are critical path sections */ -import { Trace, TraceResponse, transformTraceData } from '../../index'; + +import transformTraceData from '../../model/transform-trace-data'; +import { Trace, TraceResponse } from '../../types/trace'; const testTrace: TraceResponse = { traceID: 'test1-trace', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test2.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test2.ts index 1f445a9339e..896519af4bd 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test2.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test2.ts @@ -29,7 +29,8 @@ | Here ++++++ is critical path | */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const happyTrace: TraceResponse = { traceID: 'trace-123', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test3.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test3.ts index ab13f8d12fb..04ff9908b09 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test3.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test3.ts @@ -26,7 +26,8 @@ Span B will be dropped. | span A is on critical path(+++++) | */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const trace: TraceResponse = { traceID: '006c3cf93508f205', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test4.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test4.ts index dfab4611889..28d0abfc196 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test4.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test4.ts @@ -29,7 +29,8 @@ Both spanB and spanC will be dropped. | span A is on critical path(+++++) | */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const trace: TraceResponse = { traceID: 'trace-abc', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test5.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test5.ts index 0f057fe60e1..88d35296afd 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test5.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test5.ts @@ -28,7 +28,8 @@ Here span B is ref-type is 'FOLLOWS_FROM' | */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const trace: TraceResponse = { traceID: 'trace-abc', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test6.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test6.ts index 3d35d416f85..faa703c4885 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test6.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test6.ts @@ -26,7 +26,8 @@ └──────────────┘ | (parent-child tree) */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const trace: TraceResponse = { traceID: 'trace-abc', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test7.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test7.ts index ef67a541817..d4256c95888 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test7.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test7.ts @@ -26,7 +26,8 @@ └──────────────┘ | */ -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; const trace: TraceResponse = { traceID: 'trace-abc', diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test8.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test8.ts index 4be57aa68db..068d852c965 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test8.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test8.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; /* ┌─────────────────┐ | diff --git a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test9.ts b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test9.ts index b1e52d18f22..0233d4b721c 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/testCases/test9.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/testCases/test9.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceResponse, transformTraceData } from '../../index'; +import transformTraceData from '../../model/transform-trace-data'; +import { TraceResponse } from '../../types/trace'; /* ┌──────────┐ | diff --git a/public/app/features/explore/TraceView/components/CriticalPath/utils/findLastFinishingChildSpan.tsx b/public/app/features/explore/TraceView/components/CriticalPath/utils/findLastFinishingChildSpan.tsx index 21bdc7edd22..b9cb5423f62 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/utils/findLastFinishingChildSpan.tsx +++ b/public/app/features/explore/TraceView/components/CriticalPath/utils/findLastFinishingChildSpan.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../../types'; +import { TraceSpan } from '../../types/trace'; /** * @returns - Returns the span that finished last among the remaining child spans. diff --git a/public/app/features/explore/TraceView/components/CriticalPath/utils/getChildOfSpans.tsx b/public/app/features/explore/TraceView/components/CriticalPath/utils/getChildOfSpans.tsx index 850c59c04c6..a42cb801506 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/utils/getChildOfSpans.tsx +++ b/public/app/features/explore/TraceView/components/CriticalPath/utils/getChildOfSpans.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../../types'; +import { TraceSpan } from '../../types/trace'; /** * Removes child spans whose refType is FOLLOWS_FROM and their descendants. diff --git a/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.test.ts b/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.test.ts index c27a6eab732..100de12c3aa 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.test.ts +++ b/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../../types'; +import { TraceSpan } from '../../types/trace'; import test3 from '../testCases/test3'; import test4 from '../testCases/test4'; import test6 from '../testCases/test6'; diff --git a/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.tsx b/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.tsx index 2b0b93eef8e..a52a7d8966f 100644 --- a/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.tsx +++ b/public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../../types'; +import { TraceSpan } from '../../types/trace'; /** * This function resolves overflowing child spans for each span. diff --git a/public/app/features/explore/TraceView/components/ScrollManager.tsx b/public/app/features/explore/TraceView/components/ScrollManager.tsx index e996b3b5dd3..2190062e190 100644 --- a/public/app/features/explore/TraceView/components/ScrollManager.tsx +++ b/public/app/features/explore/TraceView/components/ScrollManager.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TNil } from './types'; +import TNil from './types/TNil'; import { TraceSpan, TraceSpanReference, Trace } from './types/trace'; /** 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/SearchBar/NextPrevResult.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx index f16b2cc2fbf..44453f8dd1b 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx @@ -23,7 +23,7 @@ import { config, reportInteraction } from '@grafana/runtime'; import { Icon, PopoverContent, Tooltip, useTheme2 } from '@grafana/ui'; import { getButtonStyles } from '@grafana/ui/internal'; -import { Trace } from '../../types'; +import { Trace } from '../../types/trace'; export type NextPrevResultProps = { trace: Trace; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx index 8889408b7eb..954b57cb039 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx @@ -21,7 +21,7 @@ import { Button, Switch, useStyles2 } from '@grafana/ui'; import { getButtonStyles } from '@grafana/ui/internal'; import { SearchProps } from '../../../useSearch'; -import { Trace } from '../../types'; +import { Trace } from '../../types/trace'; import { convertTimeFilter } from '../../utils/filter-spans'; import NextPrevResult from './NextPrevResult'; 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 7cb9997fcc8..0708a574c17 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -23,7 +23,7 @@ import { Collapse, Icon, InlineField, InlineFieldRow, Select, Stack, Tooltip, us import { defaultFilters, SearchProps } from '../../../useSearch'; import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags'; import SearchBarInput from '../../common/SearchBarInput'; -import { Trace } from '../../types'; +import { Trace } from '../../types/trace'; import NextPrevResult from '../SearchBar/NextPrevResult'; import TracePageSearchBar from '../SearchBar/TracePageSearchBar'; @@ -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/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx index a5ffb81d12c..ce36acf89ea 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx @@ -9,7 +9,7 @@ import { Input, Select, Stack, useStyles2 } from '@grafana/ui'; import { randomId, SearchProps, Tag } from '../../../useSearch'; import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags'; -import { Trace } from '../../types'; +import { Trace } from '../../types/trace'; interface Props { search: SearchProps; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx index 6173ac3534d..dd638f44f8a 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx @@ -19,7 +19,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { withTheme2, stylesFactory } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import { getRgbColorByKey } from '../../utils/color-generator'; import renderIntoCanvas from './render-into-canvas'; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx index 4cdf8088c1c..ac031caa160 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx @@ -21,8 +21,11 @@ import { Trans } from '@grafana/i18n'; import { withTheme2, stylesFactory, Button } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate, TNil } from '../../index'; -import DraggableManager, { DraggableBounds, DraggingUpdate, EUpdateTypes } from '../../utils/DraggableManager'; +import { TUpdateViewRangeTimeFunction, ViewRangeTimeUpdate, ViewRange } from '../../TraceTimelineViewer/types'; +import TNil from '../../types/TNil'; +import DraggableManager from '../../utils/DraggableManager/DraggableManager'; +import EUpdateTypes from '../../utils/DraggableManager/EUpdateTypes'; +import { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager/types'; import GraphTicks from './GraphTicks'; import Scrubber from './Scrubber'; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx index 64595920cf5..78a4c151f88 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx @@ -16,8 +16,8 @@ import { css } from '@emotion/css'; import memoizeOne from 'memoize-one'; import * as React from 'react'; -import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../../index'; -import { TraceSpan, Trace } from '../../types'; +import { ViewRange, TUpdateViewRangeTimeFunction, ViewRangeTimeUpdate } from '../../TraceTimelineViewer/types'; +import { Trace, TraceSpan } from '../../types/trace'; import CanvasSpanGraph from './CanvasSpanGraph'; import TickLabels from './TickLabels'; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/render-into-canvas.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/render-into-canvas.tsx index e339120e0fe..fc23e53f517 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/render-into-canvas.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/render-into-canvas.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TNil } from '../../index'; +import TNil from '../../types/TNil'; // exported for tests export const ITEM_ALPHA = 0.8; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx index 504c4782a84..076edfccc46 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx @@ -27,7 +27,7 @@ import ExternalLinks from '../common/ExternalLinks'; import TraceName from '../common/TraceName'; import { getTraceLinks } from '../model/link-patterns'; import { getHeaderTags, getTraceName } from '../model/trace-viewer'; -import { Trace } from '../types'; +import { Trace } from '../types/trace'; import { formatDuration } from '../utils/date'; import TracePageActions from './Actions/TracePageActions'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/ListView/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/ListView/index.tsx index 91a57172395..7fcbe3553bd 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/ListView/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/ListView/index.tsx @@ -14,7 +14,7 @@ import * as React from 'react'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import Positions from './Positions'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx index 03663851799..0e447aa0ce0 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx @@ -25,7 +25,8 @@ import { Tooltip, useStyles2 } from '@grafana/ui'; import { autoColor } from '../Theme'; import { Popover } from '../common/Popover'; -import { TraceSpan, TNil, CriticalPathSection } from '../types'; +import TNil from '../types/TNil'; +import { TraceSpan, CriticalPathSection } from '../types/trace'; import AccordianLogs from './SpanDetail/AccordianLogs'; import { ViewedBoundsFunctionType } from './utils'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.test.tsx index ea39758763f..39262e91b21 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.test.tsx @@ -17,7 +17,8 @@ import userEvent from '@testing-library/user-event'; import { DURATION, NONE, TAG } from '@grafana/o11y-ds-frontend'; -import { SpanLinkDef, TraceSpan } from '../types'; +import { SpanLinkDef } from '../types/links'; +import { TraceSpan } from '../types/trace'; import SpanBarRow, { SpanBarRowProps } from './SpanBarRow'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx index 09db59d0e01..c01bbbe4abb 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx @@ -21,7 +21,10 @@ import { DURATION, NONE, TAG } from '@grafana/o11y-ds-frontend'; import { Icon, stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; -import { SpanBarOptions, SpanLinkFunc, TraceSpan, TNil, CriticalPathSection } from '../types'; +import { SpanBarOptions } from '../settings/SpanBarSettings'; +import TNil from '../types/TNil'; +import { SpanLinkFunc } from '../types/links'; +import { TraceSpan, CriticalPathSection } from '../types/trace'; import SpanBar from './SpanBar'; import { SpanLinksMenu } from './SpanLinks'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx index 1932b8d2c96..b182fa5df41 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx @@ -20,7 +20,7 @@ import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data'; import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import * as markers from './AccordianKeyValues.markers'; import KeyValuesTable, { KeyValuesTableLink } from './KeyValuesTable'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx index 7e331151c99..f0efc64b10a 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx @@ -20,7 +20,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; import TextList from './TextList'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 60c00367373..83632c9351d 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -21,7 +21,7 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import CopyIcon from '../../common/CopyIcon'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import jsonMarkup from './jsonMarkup'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx index 1e210dd0508..36810a370df 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx @@ -9,8 +9,7 @@ import { DataLinkButton, Dropdown, Menu, ToolbarButton } from '@grafana/ui'; import { RelatedProfilesTitle } from '@grafana-plugins/tempo/resultTransformer'; import { pyroscopeProfileIdTagKey } from '../../../createSpanLink'; -import { SpanLinkFunc } from '../../types'; -import { SpanLinkDef, SpanLinkType } from '../../types/links'; +import { SpanLinkDef, SpanLinkFunc, SpanLinkType } from '../../types/links'; import { TraceSpan } from '../../types/trace'; export type ProfilesButtonContext = { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index 815f5f68974..0b8065b09c6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -39,7 +39,7 @@ import { pyroscopeProfileIdTagKey } from '../../../createSpanLink'; import { autoColor } from '../../Theme'; import LabeledList from '../../common/LabeledList'; import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE } from '../../constants/span'; -import { SpanLinkFunc } from '../../types'; +import { SpanLinkFunc } from '../../types/links'; import { TraceProcess, TraceSpan, TraceSpanReference } from '../../types/trace'; import { formatDuration } from '../utils'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx index 051fecb2e6b..a6e8219214d 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx @@ -22,7 +22,7 @@ import { TimeZone } from '@grafana/schema'; import { Button, clearButtonStyles, stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; -import { SpanLinkFunc } from '../types'; +import { SpanLinkFunc } from '../types/links'; import { TraceSpan, TraceSpanReference } from '../types/trace'; import SpanDetail, { TraceFlameGraphs } from './SpanDetail'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.test.tsx index 9994b36dd37..463b2035841 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.test.tsx @@ -17,7 +17,7 @@ import userEvent from '@testing-library/user-event'; import { createTheme } from '@grafana/data'; -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import spanAncestorIdsSpy from '../utils/span-ancestor-ids'; import SpanTreeOffset, { getStyles, TProps } from './SpanTreeOffset'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx index 786edc7a183..72a985faad4 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx @@ -21,7 +21,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Icon, stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import spanAncestorIds from '../utils/span-ancestor-ids'; export const getStyles = stylesFactory((theme: GrafanaTheme2) => ({ diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx index 8c1341b3193..81c255bcba6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx @@ -20,7 +20,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { autoColor } from '../Theme'; -import { TNil } from '../types'; +import TNil from '../types/TNil'; import { formatDuration } from './utils'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx index 91b3365f695..4fb9b184dcc 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx @@ -16,8 +16,9 @@ import { css } from '@emotion/css'; import cx from 'classnames'; import * as React from 'react'; -import { TNil } from '../../types'; -import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager'; +import TNil from '../../types/TNil'; +import DraggableManager from '../../utils/DraggableManager/DraggableManager'; +import { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager/types'; export const getStyles = () => ({ TimelineColumnResizer: css({ diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx index f7ec95ced7b..0afaeaa0ce2 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx @@ -17,8 +17,9 @@ import * as React from 'react'; import { stylesFactory } from '@grafana/ui'; -import { TNil } from '../../types'; -import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager'; +import TNil from '../../types/TNil'; +import DraggableManager from '../../utils/DraggableManager/DraggableManager'; +import { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager/types'; import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; // exported for testing diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx index fd93ab261f2..74d25ed8c1f 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx @@ -15,7 +15,7 @@ import { render, screen } from '@testing-library/react'; import traceGenerator from '../demo/trace-generators'; import transformTraceData from '../model/transform-trace-data'; -import { Trace } from '../types'; +import { Trace } from '../types/trace'; import { formatDuration } from '../utils/date'; import SpanTreeOffset from './SpanTreeOffset'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx index 2e38d68ebcf..23113a16c24 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -26,9 +26,11 @@ import { TimeZone } from '@grafana/schema'; import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui'; import { PEER_SERVICE } from '../constants/tag-keys'; -import { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types'; +import { SpanBarOptions } from '../settings/SpanBarSettings'; +import TNil from '../types/TNil'; import TTraceTimeline from '../types/TTraceTimeline'; -import { TraceSpan, Trace, TraceSpanReference } from '../types/trace'; +import { SpanLinkFunc } from '../types/links'; +import { TraceSpan, Trace, TraceSpanReference, CriticalPathSection } from '../types/trace'; import { getColorByKey } from '../utils/color-generator'; import ListView from './ListView'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx index e2840a15db2..4d475aa8430 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx @@ -23,9 +23,10 @@ import { stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; import { merge as mergeShortcuts } from '../keyboard-shortcuts'; -import { CriticalPathSection, SpanLinkFunc, TNil } from '../types'; +import TNil from '../types/TNil'; import TTraceTimeline from '../types/TTraceTimeline'; -import { TraceSpan, Trace, TraceSpanReference } from '../types/trace'; +import { SpanLinkFunc } from '../types/links'; +import { TraceSpan, Trace, TraceSpanReference, CriticalPathSection } from '../types/trace'; import { TraceFlameGraphs } from './SpanDetail'; import TimelineHeaderRow from './TimelineHeaderRow'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/types.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/types.tsx index c427b0ecd3d..c4038f337f6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/types.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/types.tsx @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TNil } from '../types'; - +import TNil from '../types/TNil'; interface TimeCursorUpdate { cursor: number | TNil; } diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.test.ts b/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.test.ts index 9306391774f..a3a634b79d3 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.test.ts +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.test.ts @@ -13,7 +13,7 @@ // limitations under the License. import traceGenerator from '../demo/trace-generators'; -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import { findServerChildSpan, diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx index 1fa55b1d957..3a03cbffdf6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; export type ViewedBoundsFunctionType = (start: number, end: number) => { start: number; end: number }; /** diff --git a/public/app/features/explore/TraceView/components/Tween.tsx b/public/app/features/explore/TraceView/components/Tween.tsx index e4c3b8271e7..0f839636b3d 100644 --- a/public/app/features/explore/TraceView/components/Tween.tsx +++ b/public/app/features/explore/TraceView/components/Tween.tsx @@ -14,7 +14,7 @@ import ease from 'tween-functions'; -import { TNil } from './types'; +import TNil from './types/TNil'; export interface TweenState { done: boolean; diff --git a/public/app/features/explore/TraceView/components/common/TraceName.tsx b/public/app/features/explore/TraceView/components/common/TraceName.tsx index 128a297d7eb..0c17a814b87 100644 --- a/public/app/features/explore/TraceView/components/common/TraceName.tsx +++ b/public/app/features/explore/TraceView/components/common/TraceName.tsx @@ -19,7 +19,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { FALLBACK_TRACE_NAME } from '../constants'; -import { TNil } from '../types'; +import TNil from '../types/TNil'; import BreakableText from './BreakableText'; diff --git a/public/app/features/explore/TraceView/components/index.ts b/public/app/features/explore/TraceView/components/index.ts deleted file mode 100644 index 51b22738e75..00000000000 --- a/public/app/features/explore/TraceView/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { default as TraceTimelineViewer } from './TraceTimelineViewer'; -export { TracePageHeader } from './TracePageHeader'; -export { default as SpanBarSettings } from './settings/SpanBarSettings'; -export * from './types'; -export * from './TraceTimelineViewer/types'; -export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState'; -export { default as transformTraceData } from './model/transform-trace-data'; -export { filterSpans } from './utils/filter-spans'; -export * from './Theme'; diff --git a/public/app/features/explore/TraceView/components/model/find-trace-name.test.ts b/public/app/features/explore/TraceView/components/model/find-trace-name.test.ts index 8dedcd277a9..68d6b60cbe7 100644 --- a/public/app/features/explore/TraceView/components/model/find-trace-name.test.ts +++ b/public/app/features/explore/TraceView/components/model/find-trace-name.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import { getHeaderTags, _getTraceNameImpl as getTraceName } from './trace-viewer'; diff --git a/public/app/features/explore/TraceView/components/model/link-patterns.test.ts b/public/app/features/explore/TraceView/components/model/link-patterns.test.ts index e3d5a98ffe0..2e9ca895b4c 100644 --- a/public/app/features/explore/TraceView/components/model/link-patterns.test.ts +++ b/public/app/features/explore/TraceView/components/model/link-patterns.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Trace } from '../types'; +import { Trace } from '../types/trace'; import { processTemplate, diff --git a/public/app/features/explore/TraceView/components/model/link-patterns.tsx b/public/app/features/explore/TraceView/components/model/link-patterns.tsx index 8964b969a0d..2c73aae342e 100644 --- a/public/app/features/explore/TraceView/components/model/link-patterns.tsx +++ b/public/app/features/explore/TraceView/components/model/link-patterns.tsx @@ -15,7 +15,7 @@ import { uniq as _uniq } from 'lodash'; import memoize from 'lru-memoize'; -import { Trace } from '../types'; +import { Trace } from '../types/trace'; import { getConfigValue } from '../utils/config/get-config'; const parameterRegExp = /#\{([^{}]*)\}/g; diff --git a/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts b/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts index 2895b6c0013..0eaafaaa596 100644 --- a/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts +++ b/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts @@ -1,4 +1,4 @@ -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import { findHeaderTags } from './trace-viewer'; diff --git a/public/app/features/explore/TraceView/components/model/trace-viewer.ts b/public/app/features/explore/TraceView/components/model/trace-viewer.ts index 14a359a33b9..0a3b2533bb9 100644 --- a/public/app/features/explore/TraceView/components/model/trace-viewer.ts +++ b/public/app/features/explore/TraceView/components/model/trace-viewer.ts @@ -14,7 +14,7 @@ import { memoize } from 'lodash'; -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; export function _getTraceNameImpl(spans: TraceSpan[]) { // Use a span with no references to another span in given array diff --git a/public/app/features/explore/TraceView/components/model/transform-trace-data.test.ts b/public/app/features/explore/TraceView/components/model/transform-trace-data.test.ts index 53d3f3b696d..fda614cc40c 100644 --- a/public/app/features/explore/TraceView/components/model/transform-trace-data.test.ts +++ b/public/app/features/explore/TraceView/components/model/transform-trace-data.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceResponse } from '../types'; +import { TraceResponse } from '../types/trace'; import transformTraceData, { orderTags, deduplicateTags } from './transform-trace-data'; diff --git a/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx b/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx index 8220af49732..f85f99f8e0c 100644 --- a/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx +++ b/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx @@ -18,7 +18,7 @@ import { isEqual as _isEqual } from 'lodash'; import { TraceKeyValuePair } from '@grafana/data'; import { getTraceSpanIdsAsTree } from '../selectors/trace'; -import { TraceSpan, Trace, TraceResponse, TraceProcess } from '../types'; +import { TraceResponse, Trace, TraceSpan, TraceProcess } from '../types/trace'; // @ts-ignore import TreeNode from '../utils/TreeNode'; import { getConfigValue } from '../utils/config/get-config'; diff --git a/public/app/features/explore/TraceView/components/selectors/span.test.ts b/public/app/features/explore/TraceView/components/selectors/span.test.ts index 89ebd46732c..a805c45891e 100644 --- a/public/app/features/explore/TraceView/components/selectors/span.test.ts +++ b/public/app/features/explore/TraceView/components/selectors/span.test.ts @@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceResponse } from 'app/features/explore/TraceView/components/types'; - import traceGenerator from '../demo/trace-generators'; +import { TraceResponse } from '../types/trace'; import * as spanSelectors from './span'; diff --git a/public/app/features/explore/TraceView/components/types/index.tsx b/public/app/features/explore/TraceView/components/types/index.tsx deleted file mode 100644 index e91e53e601b..00000000000 --- a/public/app/features/explore/TraceView/components/types/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export type { TraceSpan, TraceResponse, Trace, TraceProcess, TraceLink, CriticalPathSection } from './trace'; -export type { SpanBarOptions, SpanBarOptionsData } from '../settings/SpanBarSettings'; -export type { default as TTraceTimeline } from './TTraceTimeline'; -export type { default as TNil } from './TNil'; -export type { SpanLinkFunc, SpanLinkDef } from './links'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/DraggableManager.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/DraggableManager.tsx index 44a0820a011..242edfbc161 100644 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/DraggableManager.tsx +++ b/public/app/features/explore/TraceView/components/utils/DraggableManager/DraggableManager.tsx @@ -14,7 +14,7 @@ import { get as _get } from 'lodash'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import EUpdateTypes from './EUpdateTypes'; import { DraggableBounds, DraggingUpdate } from './types'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DividerDemo.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DividerDemo.tsx index 835573a5fa5..ff9a9f6a0b1 100644 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DividerDemo.tsx +++ b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DividerDemo.tsx @@ -16,7 +16,7 @@ import { PureComponent } from 'react'; import TNil from '../../../types/TNil'; import DraggableManager from '../DraggableManager'; -import { DraggableBounds, DraggingUpdate } from '../index'; +import { DraggableBounds, DraggingUpdate } from '../types'; import './DividerDemo.css'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DraggableManagerDemo.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DraggableManagerDemo.tsx index 5a524ddf53a..0d4c5dde159 100644 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DraggableManagerDemo.tsx +++ b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DraggableManagerDemo.tsx @@ -16,7 +16,7 @@ import { PureComponent } from 'react'; import { Trans } from '@grafana/i18n'; -import { TNil } from '../../../types'; +import TNil from '../../../types/TNil'; import DividerDemo from './DividerDemo'; import RegionDemo from './RegionDemo'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/RegionDemo.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/RegionDemo.tsx index 793428fcce5..e92f883060b 100644 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/RegionDemo.tsx +++ b/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/RegionDemo.tsx @@ -14,8 +14,9 @@ import { PureComponent } from 'react'; -import { TNil } from '../../../types'; -import DraggableManager, { DraggableBounds, DraggingUpdate } from '../index'; +import TNil from '../../../types/TNil'; +import DraggableManager from '../DraggableManager'; +import { DraggableBounds, DraggingUpdate } from '../types'; import './RegionDemo.css'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx deleted file mode 100644 index 150607a3925..00000000000 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export * from './types'; -export { default as EUpdateTypes } from './EUpdateTypes'; -export { default } from './DraggableManager'; diff --git a/public/app/features/explore/TraceView/components/utils/DraggableManager/types.tsx b/public/app/features/explore/TraceView/components/utils/DraggableManager/types.tsx index 79e213f8d7e..dfcd0abefa6 100644 --- a/public/app/features/explore/TraceView/components/utils/DraggableManager/types.tsx +++ b/public/app/features/explore/TraceView/components/utils/DraggableManager/types.tsx @@ -14,7 +14,7 @@ import * as React from 'react'; -import { TNil } from '../../types'; +import TNil from '../../types/TNil'; import DraggableManager from './DraggableManager'; import EUpdateTypes from './EUpdateTypes'; diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts index 4bae04a74a2..833ee607c3a 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts @@ -13,7 +13,7 @@ // limitations under the License. import { defaultFilters, defaultTagFilter } from '../../useSearch'; -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import { filterSpans } from './filter-spans'; diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index 31eaf008d67..82664ded026 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -18,7 +18,8 @@ import { TraceKeyValuePair } from '@grafana/data'; import { SearchProps, Tag } from '../../useSearch'; import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../constants/span'; -import { TNil, TraceSpan } from '../types'; +import TNil from '../types/TNil'; +import { TraceSpan } from '../types/trace'; // filter spans where all filters added need to be true for each individual span that is returned // i.e. the more filters added -> the more specific that the returned results are diff --git a/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.test.ts b/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.test.ts index d8532b74151..2a9d2c6de48 100644 --- a/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.test.ts +++ b/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '../types'; +import { TraceSpan } from '../types/trace'; import spanAncestorIdsSpy from './span-ancestor-ids'; diff --git a/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.tsx b/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.tsx index 5ddb037aa2a..af9ac5282e0 100644 --- a/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.tsx +++ b/public/app/features/explore/TraceView/components/utils/span-ancestor-ids.tsx @@ -14,7 +14,8 @@ import { find as _find, get as _get } from 'lodash'; -import { TNil, TraceSpan } from '../types'; +import TNil from '../types/TNil'; +import { TraceSpan } from '../types/trace'; function getFirstAncestor(span: TraceSpan): TraceSpan | TNil { return _get( diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index 6d2ee26d684..03a4651e8a5 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -14,8 +14,8 @@ import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv'; import { TemplateSrv } from '../../templating/template_srv'; -import { Trace, TraceSpan } from './components'; import { SpanLinkType } from './components/types/links'; +import { Trace, TraceSpan } from './components/types/trace'; import { createSpanLinkFactory, pyroscopeProfileIdTagKey } from './createSpanLink'; const dummyTraceData = { duration: 10, traceID: 'trace1', traceName: 'test trace' } as unknown as Trace; diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 7059ebac9b3..ee689c39920 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -28,9 +28,8 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { LokiQuery } from '../../../plugins/datasource/loki/types'; import { ExploreFieldLinkModel, getFieldLinksForExplore, getVariableUsageInfo } from '../utils/links'; -import { SpanLinkDef, SpanLinkFunc, Trace, TraceSpan } from './components'; -import { SpanLinkType } from './components/types/links'; -import { TraceSpanReference } from './components/types/trace'; +import { SpanLinkDef, SpanLinkFunc, SpanLinkType } from './components/types/links'; +import { Trace, TraceSpan, TraceSpanReference } from './components/types/trace'; /** * This is a factory for the link creator. It returns the function mainly so it can return undefined in which case diff --git a/public/app/features/explore/TraceView/useChildrenState.test.ts b/public/app/features/explore/TraceView/useChildrenState.test.ts index 7b17d67eaaf..ff14e7f9a01 100644 --- a/public/app/features/explore/TraceView/useChildrenState.test.ts +++ b/public/app/features/explore/TraceView/useChildrenState.test.ts @@ -1,6 +1,6 @@ import { renderHook, act } from '@testing-library/react'; -import { TraceSpan } from './components'; +import { TraceSpan } from './components/types/trace'; import { useChildrenState } from './useChildrenState'; describe('useChildrenState', () => { diff --git a/public/app/features/explore/TraceView/useChildrenState.ts b/public/app/features/explore/TraceView/useChildrenState.ts index 1bfca3611d6..7b9c34070eb 100644 --- a/public/app/features/explore/TraceView/useChildrenState.ts +++ b/public/app/features/explore/TraceView/useChildrenState.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -import { TraceSpan } from './components'; +import { TraceSpan } from './components/types/trace'; /** * Children state means whether spans are collapsed or not. Also provides some functions to manipulate that state. diff --git a/public/app/features/explore/TraceView/useDetailState.ts b/public/app/features/explore/TraceView/useDetailState.ts index 48414154995..aea1548d7b4 100644 --- a/public/app/features/explore/TraceView/useDetailState.ts +++ b/public/app/features/explore/TraceView/useDetailState.ts @@ -2,7 +2,7 @@ import { useCallback, useState, useEffect } from 'react'; import { DataFrame, TraceLog } from '@grafana/data'; -import { DetailState } from './components'; +import DetailState from './components/TraceTimelineViewer/SpanDetail/DetailState'; import { TraceSpanReference } from './components/types/trace'; /** * Keeps state of the span detail. This means whether span details are open but also state of each detail subitem diff --git a/public/app/features/explore/TraceView/useSearch.test.ts b/public/app/features/explore/TraceView/useSearch.test.ts index 138710c8b79..0379aabba75 100644 --- a/public/app/features/explore/TraceView/useSearch.test.ts +++ b/public/app/features/explore/TraceView/useSearch.test.ts @@ -1,6 +1,6 @@ import { act, renderHook } from '@testing-library/react'; -import { TraceSpan } from './components'; +import { TraceSpan } from './components/types/trace'; import { defaultFilters, useSearch } from './useSearch'; describe('useSearch', () => { diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 834dbfb0714..779a318fec2 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -4,7 +4,8 @@ import { v4 as uuidv4 } from 'uuid'; import { InterpolateFunction } from '@grafana/data'; -import { filterSpans, TraceSpan } from './components'; +import { TraceSpan } from './components/types/trace'; +import { filterSpans } from './components/utils/filter-spans'; export interface SearchProps { serviceName?: string; diff --git a/public/app/features/explore/TraceView/useViewRange.ts b/public/app/features/explore/TraceView/useViewRange.ts index f577beb0b38..54cb8397a72 100644 --- a/public/app/features/explore/TraceView/useViewRange.ts +++ b/public/app/features/explore/TraceView/useViewRange.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -import { ViewRangeTimeUpdate, ViewRange } from './components'; +import { ViewRange, ViewRangeTimeUpdate } from './components/TraceTimelineViewer/types'; /** * Controls state of the zoom function that can be used through minimap in header or on the timeline. ViewRange contains diff --git a/public/app/features/explore/TraceView/utils/tags.ts b/public/app/features/explore/TraceView/utils/tags.ts index fb3670573ab..293326e359e 100644 --- a/public/app/features/explore/TraceView/utils/tags.ts +++ b/public/app/features/explore/TraceView/utils/tags.ts @@ -1,7 +1,6 @@ import { SpanStatusCode } from '@opentelemetry/api'; import { uniq } from 'lodash'; -import { Trace } from '../components'; import { ID, KIND, @@ -11,6 +10,7 @@ import { STATUS_MESSAGE, TRACE_STATE, } from '../components/constants/span'; +import { Trace } from '../components/types/trace'; export const getTraceServiceNames = (trace: Trace) => { const serviceNames = trace.spans.map((span) => { diff --git a/public/app/features/explore/TraceView/utils/transform.ts b/public/app/features/explore/TraceView/utils/transform.ts index f731ac653da..4688bc4b94b 100644 --- a/public/app/features/explore/TraceView/utils/transform.ts +++ b/public/app/features/explore/TraceView/utils/transform.ts @@ -1,6 +1,7 @@ import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data'; -import { Trace, TraceProcess, TraceResponse, transformTraceData } from '../components'; +import transformTraceData from '../components/model/transform-trace-data'; +import { Trace, TraceProcess, TraceResponse } from '../components/types/trace'; export function transformDataFrames(frame?: DataFrame): Trace | null { if (!frame) { diff --git a/public/app/features/geo/editor/locationEditor.ts b/public/app/features/geo/editor/locationEditor.ts index a1deb38f8a3..ad71e5ced6d 100644 --- a/public/app/features/geo/editor/locationEditor.ts +++ b/public/app/features/geo/editor/locationEditor.ts @@ -1,4 +1,5 @@ import { Field, FieldType, PanelOptionsEditorBuilder, DataFrame } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { FrameGeometrySource, FrameGeometrySourceMode } from '@grafana/schema'; import { GazetteerPathEditor } from 'app/features/geo/editor/GazetteerPathEditor'; @@ -14,7 +15,7 @@ export function addLocationFields( builder.addCustomEditor({ id: 'modeEditor', path: `${prefix}mode`, - name: 'Location Mode', + name: t('geo.location-editor.name-location-mode', 'Location Mode'), editor: LocationModeEditor, settings: { data, source }, }); @@ -25,18 +26,18 @@ export function addLocationFields( builder .addFieldNamePicker({ path: `${prefix}latitude`, - name: 'Latitude field', + name: t('geo.location-editor.name-latitude-field', 'Latitude field'), settings: { filter: (f: Field) => f.type === FieldType.number, - noFieldsMessage: 'No numeric fields found', + noFieldsMessage: t('geo.location-editor.latitude-field.no-fields-message', 'No numeric fields found'), }, }) .addFieldNamePicker({ path: `${prefix}longitude`, - name: 'Longitude field', + name: t('geo.location-editor.name-longitude-field', 'Longitude field'), settings: { filter: (f: Field) => f.type === FieldType.number, - noFieldsMessage: 'No numeric fields found', + noFieldsMessage: t('geo.location-editor.longitude-field.no-fields-message', 'No numeric fields found'), }, }); break; @@ -44,10 +45,10 @@ export function addLocationFields( case FrameGeometrySourceMode.Geohash: builder.addFieldNamePicker({ path: `${prefix}geohash`, - name: 'Geohash field', + name: t('geo.location-editor.name-geohash-field', 'Geohash field'), settings: { filter: (f: Field) => f.type === FieldType.string, - noFieldsMessage: 'No strings fields found', + noFieldsMessage: t('geo.location-editor.geohash-field.no-fields-message', 'No strings fields found'), }, }); break; @@ -56,16 +57,16 @@ export function addLocationFields( builder .addFieldNamePicker({ path: `${prefix}lookup`, - name: 'Lookup field', + name: t('geo.location-editor.name-lookup-field', 'Lookup field'), settings: { filter: (f: Field) => f.type === FieldType.string, - noFieldsMessage: 'No strings fields found', + noFieldsMessage: t('geo.location-editor.lookup-field.no-fields-message', 'No strings fields found'), }, }) .addCustomEditor({ id: 'gazetteer', path: `${prefix}gazetteer`, - name: 'Gazetteer', + name: t('geo.location-editor.name-gazetteer', 'Gazetteer'), editor: GazetteerPathEditor, }); } diff --git a/public/app/features/geo/editor/locationModeEditor.tsx b/public/app/features/geo/editor/locationModeEditor.tsx index 6ded40a3abe..aacc913123c 100644 --- a/public/app/features/geo/editor/locationModeEditor.tsx +++ b/public/app/features/geo/editor/locationModeEditor.tsx @@ -3,38 +3,12 @@ import { useEffect, useState } from 'react'; import { StandardEditorProps, DataFrame, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { t } from '@grafana/i18n'; import { FrameGeometrySource, FrameGeometrySourceMode } from '@grafana/schema'; import { Alert, HorizontalGroup, Icon, Select, useStyles2 } from '@grafana/ui'; import { FrameGeometryField, getGeometryField, getLocationMatchers } from '../utils/location'; -const MODE_OPTIONS = [ - { - value: FrameGeometrySourceMode.Auto, - label: 'Auto', - ariaLabel: selectors.components.Transforms.SpatialOperations.location.autoOption, - description: 'Automatically identify location data based on default field names', - }, - { - value: FrameGeometrySourceMode.Coords, - label: 'Coords', - ariaLabel: selectors.components.Transforms.SpatialOperations.location.coords.option, - description: 'Specify latitude and longitude fields', - }, - { - value: FrameGeometrySourceMode.Geohash, - label: 'Geohash', - ariaLabel: selectors.components.Transforms.SpatialOperations.location.geohash.option, - description: 'Specify geohash field', - }, - { - value: FrameGeometrySourceMode.Lookup, - label: 'Lookup', - ariaLabel: selectors.components.Transforms.SpatialOperations.location.lookup.option, - description: 'Specify Gazetteer and lookup field', - }, -]; - interface ModeEditorSettings { data?: DataFrame[]; source?: FrameGeometrySource; @@ -50,6 +24,39 @@ export const LocationModeEditor = ({ }: StandardEditorProps) => { const [info, setInfo] = useState(); + const MODE_OPTIONS = [ + { + value: FrameGeometrySourceMode.Auto, + label: t('geo.location-more-editor.mode-options.label-auto', 'Auto'), + ariaLabel: selectors.components.Transforms.SpatialOperations.location.autoOption, + description: t( + 'geo.location-more-editor.mode-options.description-auto', + 'Automatically identify location data based on default field names' + ), + }, + { + value: FrameGeometrySourceMode.Coords, + label: t('geo.location-more-editor.mode-options.label-coords', 'Coords'), + ariaLabel: selectors.components.Transforms.SpatialOperations.location.coords.option, + description: t( + 'geo.location-more-editor.mode-options.description-coords', + 'Specify latitude and longitude fields' + ), + }, + { + value: FrameGeometrySourceMode.Geohash, + label: t('geo.location-more-editor.mode-options.label-geohash', 'Geohash'), + ariaLabel: selectors.components.Transforms.SpatialOperations.location.geohash.option, + description: t('geo.location-more-editor.mode-options.description-geohash', 'Specify geohash field'), + }, + { + value: FrameGeometrySourceMode.Lookup, + label: t('geo.location-more-editor.mode-options.label-lookup', 'Lookup'), + ariaLabel: selectors.components.Transforms.SpatialOperations.location.lookup.option, + description: t('geo.location-more-editor.mode-options.description-lookup', 'Specify Gazetteer and lookup field'), + }, + ]; + useEffect(() => { if (item.settings?.source && item.settings?.data?.length && item.settings.data[0]) { getLocationMatchers(item.settings.source).then((location) => { diff --git a/public/app/features/geo/utils/location.ts b/public/app/features/geo/utils/location.ts index 91f009d54b6..96e92929cdd 100644 --- a/public/app/features/geo/utils/location.ts +++ b/public/app/features/geo/utils/location.ts @@ -9,6 +9,7 @@ import { getFieldDisplayName, FieldType, } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { FrameGeometrySource, FrameGeometrySourceMode } from '@grafana/schema'; import { getGeoFieldFromGazetteer, pointFieldFromGeohash, pointFieldFromLonLat } from '../format/utils'; @@ -178,7 +179,7 @@ export function getGeometryField(frame: DataFrame, location: LocationFieldMatche }; } return { - warning: 'Unable to find location fields', + warning: t('geo.get-geometry-field.warning-unable-to-find', 'Unable to find location fields'), }; case FrameGeometrySourceMode.Coords: @@ -190,7 +191,7 @@ export function getGeometryField(frame: DataFrame, location: LocationFieldMatche }; } return { - warning: 'Select latitude/longitude fields', + warning: t('geo.get-geometry-field.warning-select-lat-long', 'Select latitude/longitude fields'), }; case FrameGeometrySourceMode.Geohash: @@ -202,7 +203,7 @@ export function getGeometryField(frame: DataFrame, location: LocationFieldMatche }; } return { - warning: 'Select geohash field', + warning: t('geo.get-geometry-field.warning-select-geohash', 'Select geohash field'), }; case FrameGeometrySourceMode.Lookup: @@ -215,13 +216,13 @@ export function getGeometryField(frame: DataFrame, location: LocationFieldMatche }; } return { - warning: 'Gazetteer not found', + warning: t('geo.get-geometry-field.warning-gazetteer-not-found', 'Gazetteer not found'), }; } return { - warning: 'Select lookup field', + warning: t('geo.get-geometry-field.warning-select-lookup', 'Select lookup field'), }; } - return { warning: 'unable to find geometry' }; + return { warning: t('geo.get-geometry-field.warning-no-geometry', 'unable to find geometry') }; } 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/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; } 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/features/plugins/admin/components/Badges/index.ts b/public/app/features/plugins/admin/components/Badges/index.ts deleted file mode 100644 index c62d5cecc71..00000000000 --- a/public/app/features/plugins/admin/components/Badges/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { PluginDisabledBadge } from './PluginDisabledBadge'; -export { PluginInstalledBadge } from './PluginInstallBadge'; -export { PluginEnterpriseBadge } from './PluginEnterpriseBadge'; -export { PluginUpdateAvailableBadge } from './PluginUpdateAvailableBadge'; -export { PluginDeprecatedBadge } from './PluginDeprecatedBadge'; diff --git a/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx b/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx index 710f90dc13b..340e455e14b 100644 --- a/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx +++ b/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx @@ -11,9 +11,13 @@ import { datasourcePlugin } from '../mocks/catalogPlugin.mock'; import ConnectionsTab, { ConnectionsList } from './ConnectionsTab'; -jest.mock('app/features/datasources/state', () => ({ - ...jest.requireActual('app/features/datasources/state'), +jest.mock('app/features/datasources/state/hooks.ts', () => ({ + ...jest.requireActual('app/features/datasources/state/hooks.ts'), useLoadDataSource: jest.fn().mockReturnValue({ isLoading: false }), +})); + +jest.mock('app/features/datasources/state/selectors.ts', () => ({ + ...jest.requireActual('app/features/datasources/state/selectors.ts'), getDataSources: jest.fn(() => 'getDataSources mock implementation'), })); @@ -46,7 +50,7 @@ describe('', () => { setupContextSrv(); const mockedConnections = getMockDataSources(3, { type: datasourcePlugin.id }); mockedConnections[2].type = 'other-plugin-id'; - jest.requireMock('app/features/datasources/state').getDataSources.mockReturnValue(mockedConnections); + jest.requireMock('app/features/datasources/state/selectors.ts').getDataSources.mockReturnValue(mockedConnections); render(); @@ -59,7 +63,9 @@ describe('', () => { it('should render add new datasource button when no datasources are defined', async () => { setupContextSrv(); - jest.requireMock('app/features/datasources/state').getDataSources.mockReturnValue(getMockDataSources(1)); + jest + .requireMock('app/features/datasources/state/selectors.ts') + .getDataSources.mockReturnValue(getMockDataSources(1)); render(); expect(screen.getByText('Add new data source')).toBeVisible(); diff --git a/public/app/features/plugins/admin/components/ConnectionsTab.tsx b/public/app/features/plugins/admin/components/ConnectionsTab.tsx index e12bfec46d0..a7b82b35774 100644 --- a/public/app/features/plugins/admin/components/ConnectionsTab.tsx +++ b/public/app/features/plugins/admin/components/ConnectionsTab.tsx @@ -6,7 +6,8 @@ import { EmptyState, Stack, TextLink, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { ViewProps } from 'app/features/datasources/components/DataSourcesList'; import { DataSourcesListCard } from 'app/features/datasources/components/DataSourcesListCard'; -import { getDataSources, useLoadDataSources } from 'app/features/datasources/state'; +import { useLoadDataSources } from 'app/features/datasources/state/hooks'; +import { getDataSources } from 'app/features/datasources/state/selectors'; import { AccessControlAction, useSelector } from 'app/types'; import { CatalogPlugin } from '../types'; diff --git a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx index cd58057ae3c..13b490c3797 100644 --- a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx +++ b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx @@ -6,7 +6,7 @@ import { Trans, t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { Button } from '@grafana/ui'; import { ROUTES } from 'app/features/connections/constants'; -import { addDataSource } from 'app/features/datasources/state'; +import { addDataSource } from 'app/features/datasources/state/actions'; import { useDispatch } from 'app/types'; import { isDataSourceEditor } from '../../permissions'; 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/InstallControls/index.tsx b/public/app/features/plugins/admin/components/InstallControls/index.tsx deleted file mode 100644 index b471668b365..00000000000 --- a/public/app/features/plugins/admin/components/InstallControls/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { InstallControlsWarning } from './InstallControlsWarning'; -export { InstallControlsButton } from './InstallControlsButton'; diff --git a/public/app/features/plugins/admin/components/PluginActions.tsx b/public/app/features/plugins/admin/components/PluginActions.tsx index edcd72cfb4c..d3e7f4a675c 100644 --- a/public/app/features/plugins/admin/components/PluginActions.tsx +++ b/public/app/features/plugins/admin/components/PluginActions.tsx @@ -6,7 +6,7 @@ import { Trans } from '@grafana/i18n'; import { Icon, Stack, useStyles2 } from '@grafana/ui'; import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin'; -import { InstallControlsButton } from '../components/InstallControls'; +import { InstallControlsButton } from '../components/InstallControls/InstallControlsButton'; import { getLatestCompatibleVersion, hasInstallControlWarning, diff --git a/public/app/features/plugins/admin/components/PluginListItemBadges.tsx b/public/app/features/plugins/admin/components/PluginListItemBadges.tsx index 206b5993162..b6b6cd9e084 100644 --- a/public/app/features/plugins/admin/components/PluginListItemBadges.tsx +++ b/public/app/features/plugins/admin/components/PluginListItemBadges.tsx @@ -3,13 +3,11 @@ import { PluginSignatureBadge, Stack } from '@grafana/ui'; import { isPluginUpdatable } from '../helpers'; import { CatalogPlugin } from '../types'; -import { - PluginEnterpriseBadge, - PluginDisabledBadge, - PluginInstalledBadge, - PluginUpdateAvailableBadge, - PluginDeprecatedBadge, -} from './Badges'; +import { PluginDeprecatedBadge } from './Badges/PluginDeprecatedBadge'; +import { PluginDisabledBadge } from './Badges/PluginDisabledBadge'; +import { PluginEnterpriseBadge } from './Badges/PluginEnterpriseBadge'; +import { PluginInstalledBadge } from './Badges/PluginInstallBadge'; +import { PluginUpdateAvailableBadge } from './Badges/PluginUpdateAvailableBadge'; type PluginBadgeType = { plugin: CatalogPlugin; diff --git a/public/app/features/plugins/admin/components/PluginSubtitle.tsx b/public/app/features/plugins/admin/components/PluginSubtitle.tsx index 6f8175908db..0a66d863938 100644 --- a/public/app/features/plugins/admin/components/PluginSubtitle.tsx +++ b/public/app/features/plugins/admin/components/PluginSubtitle.tsx @@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Alert, Stack, useStyles2 } from '@grafana/ui'; -import { InstallControlsWarning } from '../components/InstallControls'; +import { InstallControlsWarning } from '../components/InstallControls/InstallControlsWarning'; import { getLatestCompatibleVersion, hasInstallControlWarning } from '../helpers'; import { useInstallStatus, useIsRemotePluginsAvailable } from '../state/hooks'; import { CatalogPlugin, PluginStatus } from '../types'; 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 = ({ - ( + + )} > - - + + + + ); 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/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; } diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index bebd5adaf51..ccc445fae5e 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -228,8 +228,14 @@ export const ServiceAccountPageUnconnected = ({ 'serviceaccounts.service-account-page-unconnected.title-delete-service-account', 'Delete service account' )} - body="Are you sure you want to delete this service account?" - confirmText="Delete service account" + body={t( + 'serviceaccounts.service-account-page-unconnected.body-delete-service-account', + 'Are you sure you want to delete this service account?' + )} + confirmText={t( + 'serviceaccounts.service-account-page-unconnected.confirmText-delete-service-account', + 'Delete service account' + )} onConfirm={handleServiceAccountDelete} onDismiss={showDeleteServiceAccountModal(false)} /> @@ -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 eb55c1d9d6f..9508dfee1e7 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -13,10 +13,10 @@ 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'; +import { findField } from 'app/features/dimensions/utils'; import { getTransformationContent } from '../docs/getTransformationContent'; import { getTimezoneOptions } from '../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/extractFields/extractFields.ts b/public/app/features/transformers/extractFields/extractFields.ts index 70ab7f5cfa1..c50c575939a 100644 --- a/public/app/features/transformers/extractFields/extractFields.ts +++ b/public/app/features/transformers/extractFields/extractFields.ts @@ -11,7 +11,7 @@ import { SynchronousDataTransformerInfo, } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { findField } from 'app/features/dimensions'; +import { findField } from 'app/features/dimensions/utils'; import { fieldExtractors } from './fieldExtractors'; import { ExtractFieldsOptions, FieldExtractorID, JSONPath } from './types'; 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 ( ( result: AzureAPIResponse, diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts index 148a8a94baf..545c1648315 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts @@ -1,6 +1,7 @@ import { TemplateSrv } from '@grafana/runtime'; -import { AzureMonitorResource, GetMetricNamespacesQuery, GetMetricNamesQuery } from '../types'; +import { AzureMonitorResource } from '../types/query'; +import { GetMetricNamespacesQuery, GetMetricNamesQuery } from '../types/types'; export default class UrlBuilder { static buildResourceUri(templateSrv: TemplateSrv, resource: AzureMonitorResource, multipleResources?: boolean) { diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts index fc58c626fbf..c6c920f062d 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts @@ -7,7 +7,7 @@ import { createMockInstanceSetttings } from '../mocks/instanceSettings'; import createMockQuery from '../mocks/query'; import { createTemplateVariables } from '../mocks/utils'; import { multiVariable, singleVariable, subscriptionsVariable } from '../mocks/variables'; -import { AzureQueryType } from '../types'; +import { AzureQueryType } from '../types/query'; import AzureResourceGraphDatasource from './azure_resource_graph_datasource'; diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts index ac4f32e1986..67a6532deeb 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts @@ -3,20 +3,19 @@ import { filter, find, includes, startsWith } from 'lodash'; import { ScopedVars } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; -import { resourceTypes } from '../azureMetadata'; +import { resourceTypes } from '../azureMetadata/resourceTypes'; import { ARGScope } from '../dataquery.gen'; +import { AzureMonitorQuery, AzureQueryType } from '../types/query'; import { AzureGetResourceNamesQuery, AzureGraphResponse, AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceJsonData, - AzureMonitorQuery, - AzureQueryType, AzureResourceGraphOptions, RawAzureResourceGroupItem, RawAzureResourceItem, RawAzureSubscriptionItem, -} from '../types'; +} from '../types/types'; import { interpolateVariable, replaceTemplateVariables, routeNames } from '../utils/common'; export default class AzureResourceGraphDatasource extends DataSourceWithBackend< diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx index 43b2a82ee43..b15705db73b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx @@ -9,7 +9,8 @@ import { Combobox } from '@grafana/ui'; import { ARGScope } from '../../dataquery.gen'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; +import { AzureMonitorQuery } from '../../types/query'; +import { AzureMonitorErrorish, AzureMonitorOption } from '../../types/types'; import { Field } from '../shared/Field'; import QueryField from './QueryField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/QueryField.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/QueryField.tsx index d6a69f3f3b4..fd06a327e1e 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/QueryField.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { CodeEditor } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; const QueryField = ({ query, onQueryChange }: AzureQueryEditorFieldProps) => { const onChange = useCallback( diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/SubscriptionField.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/SubscriptionField.tsx index 16ceab1e26d..dfe75072933 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/SubscriptionField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/SubscriptionField.tsx @@ -5,7 +5,8 @@ import { Trans, t } from '@grafana/i18n'; import { FieldValidationMessage, MultiSelect } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorQuery, AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureMonitorQuery } from '../../types/query'; +import { AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types/types'; import { findOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.tsx index fc5966ccf5d..094780de576 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Trans, t } from '@grafana/i18n'; import { Field, Switch, TextLink, useTheme2 } from '@grafana/ui'; -import { AzureMonitorDataSourceJsonData } from '../../types'; +import { AzureMonitorDataSourceJsonData } from '../../types/types'; export interface Props { options: AzureMonitorDataSourceJsonData; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx index 212b02739a9..6fc1c94ce81 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx @@ -13,7 +13,7 @@ import { AzureMonitorDataSourceSecureJsonData, AzureMonitorDataSourceSettings, Subscription, -} from '../../types'; +} from '../../types/types'; import { routeNames } from '../../utils/common'; import { MonitorConfig } from './MonitorConfig'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.tsx index e6386e608fb..0bd33039bda 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.tsx @@ -6,7 +6,7 @@ import { Trans, t } from '@grafana/i18n'; import { Select, Button, Field } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorDataSourceJsonData } from '../../types'; +import { AzureMonitorDataSourceJsonData } from '../../types/types'; export interface Props { options: AzureMonitorDataSourceJsonData; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx index a989a01056d..e8b3d77b52a 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx @@ -6,7 +6,7 @@ import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; import { getCredentials, updateCredentials } from '../../credentials'; -import { AzureMonitorDataSourceSettings } from '../../types'; +import { AzureMonitorDataSourceSettings } from '../../types/types'; import { AzureCredentialsForm, getAzureCloudOptions } from './AzureCredentialsForm'; import { BasicLogsToggle } from './BasicLogsToggle'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx index 2647546f0ef..4f3a1050a14 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx @@ -5,7 +5,8 @@ import { t } from '@grafana/i18n'; import { EditorField, EditorFieldGroup, EditorList, EditorRow } from '@grafana/plugin-ui'; import { BuilderQueryEditorReduceExpression } from '../../dataquery.gen'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; import AggregateItem from './AggregateItem'; import { BuildAndUpdateOptions } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FilterSection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FilterSection.tsx index 1864cc14e5b..c3deae532a3 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FilterSection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FilterSection.tsx @@ -15,7 +15,8 @@ import { BuilderQueryEditorWhereExpressionItems, } from '../../dataquery.gen'; import Datasource from '../../datasource'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; import { FilterItem } from './FilterItem'; import { BuildAndUpdateOptions } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FuzzySearch.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FuzzySearch.tsx index bceacb94a8c..6f000c1a923 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FuzzySearch.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/FuzzySearch.tsx @@ -10,7 +10,8 @@ import { BuilderQueryEditorWhereExpression, BuilderQueryEditorPropertyType, } from '../../dataquery.gen'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; import { BuildAndUpdateOptions, removeExtraQuotes } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx index 629a5b8093e..5a3b1bca4fd 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx @@ -10,7 +10,8 @@ import { BuilderQueryEditorGroupByExpression, BuilderQueryEditorPropertyType, } from '../../dataquery.gen'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; import { GroupByItem } from './GroupByItem'; import { BuildAndUpdateOptions } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx index 459f0cebae8..3015fa4179b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx @@ -17,12 +17,9 @@ import { } from '../../dataquery.gen'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { - AzureLogAnalyticsMetadataTable, - AzureLogAnalyticsMetadataColumn, - AzureMonitorQuery, - EngineSchema, -} from '../../types'; +import { AzureLogAnalyticsMetadataTable, AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; +import { EngineSchema } from '../../types/types'; import { AggregateSection } from './AggregationSection'; import { AzureMonitorKustoQueryBuilder } from './AzureMonitorKustoQueryBuilder'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx index f98a94cde13..5d88282f289 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx @@ -11,7 +11,8 @@ import { BuilderQueryEditorOrderByOptions, BuilderQueryEditorPropertyType, } from '../../dataquery.gen'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; import { BuildAndUpdateOptions, inputFieldSize } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/TableSection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/TableSection.tsx index f01475f78e0..1b5a7e35aa4 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/TableSection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/TableSection.tsx @@ -6,12 +6,9 @@ import { EditorField, EditorFieldGroup, EditorRow, InputGroup } from '@grafana/p import { Button, Select } from '@grafana/ui'; import { BuilderQueryEditorExpressionType, BuilderQueryEditorPropertyType } from '../../dataquery.gen'; -import { - AzureMonitorQuery, - AzureLogAnalyticsMetadataColumn, - AzureLogAnalyticsMetadataTable, - TablePlan, -} from '../../types'; +import { AzureLogAnalyticsMetadataColumn, AzureLogAnalyticsMetadataTable } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; +import { TablePlan } from '../../types/types'; import { BuildAndUpdateOptions, inputFieldSize } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/expressions.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/expressions.ts index bada9ae81b2..8a0dc1a0b37 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/expressions.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/expressions.ts @@ -1,4 +1,4 @@ -import { QueryEditorProperty, QueryEditorPropertyType } from '../../types'; +import { QueryEditorProperty, QueryEditorPropertyType } from '../../types/types'; export enum QueryEditorExpressionType { Property = 'property', diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts index 3c72a880040..062dac286f9 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts @@ -12,7 +12,8 @@ import { BuilderQueryEditorWhereExpression, BuilderQueryExpression, } from '../../dataquery.gen'; -import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; +import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery } from '../../types/query'; const DYNAMIC_TYPE_ARRAY_DELIMITER = '["`indexer`"]'; export const inputFieldSize = 20; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx index c68a707e5f5..204bc43d984 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx @@ -17,14 +17,8 @@ import { } from '@grafana/ui'; import AzureLogAnalyticsDatasource from '../../azure_log_analytics/azure_log_analytics_datasource'; -import { - AzureMonitorQuery, - AzureQueryType, - Category, - CheatsheetQueries, - CheatsheetQuery, - DropdownCategories, -} from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; +import { Category, CheatsheetQueries, CheatsheetQuery, DropdownCategories } from '../../types/types'; import { RawQuery } from './RawQuery'; import tokenizer from './syntax'; @@ -161,7 +155,10 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => { 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 b0ba3eab206..a54949c3187 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { t } from '@grafana/i18n'; import { ConfirmModal, InlineField, RadioButtonGroup } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { setBasicLogsQuery, setDashboardTime, setKustoQuery } from './setQueryValue'; @@ -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/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx index 1f2ad8a8f6f..2c7fbced3be 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx @@ -9,14 +9,9 @@ import { Alert, LinkButton, Space, Text, TextLink } from '@grafana/ui'; import { LogsEditorMode } from '../../dataquery.gen'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { - AzureMonitorErrorish, - AzureMonitorOption, - AzureMonitorQuery, - ResultFormat, - EngineSchema, - AzureLogAnalyticsMetadataTable, -} from '../../types'; +import { AzureLogAnalyticsMetadataTable } from '../../types/logAnalyticsMetadata'; +import { AzureMonitorQuery, ResultFormat } from '../../types/query'; +import { AzureMonitorErrorish, AzureMonitorOption, EngineSchema } from '../../types/types'; import { LogsQueryBuilder } from '../LogsQueryBuilder/LogsQueryBuilder'; import ResourceField from '../ResourceField'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx index e540de2bf21..679b2dad27f 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { setKustoQuery } from './setQueryValue'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.tsx index 534f91c2fcb..f66e54ce88b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.tsx @@ -4,7 +4,7 @@ import { SelectableValue } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { InlineField, RadioButtonGroup, Select } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { setDashboardTime, setTimeColumn } from './setQueryValue'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/setQueryValue.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/setQueryValue.ts index 31c7933c9ad..9662afd52af 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/setQueryValue.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/setQueryValue.ts @@ -1,6 +1,6 @@ import { SelectableValue } from '@grafana/data'; -import { AzureMonitorQuery, ResultFormat } from '../../types'; +import { AzureMonitorQuery, ResultFormat } from '../../types/query'; export function setKustoQuery(query: AzureMonitorQuery, kustoQuery: string): AzureMonitorQuery { return { diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/useMigrations.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/useMigrations.ts index 9c5beb75421..32024ee2b7b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/useMigrations.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/useMigrations.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import Datasource from '../../datasource'; -import { AzureMonitorQuery } from '../../types'; +import { AzureMonitorQuery } from '../../types/query'; import { isGUIDish } from '../ResourcePicker/utils'; async function migrateWorkspaceQueryToResourceQuery( diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx index dfeb9b59867..abf4d91de90 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx @@ -7,7 +7,7 @@ import { AccessoryButton } from '@grafana/plugin-ui'; import { Input, Label, InlineField, Button, useStyles2 } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorResource } from '../../types'; +import { AzureMonitorResource } from '../../types/query'; export interface ResourcePickerProps { resources: T[]; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx index 35063802773..26724c82f09 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorOption } from '../../types'; +import { AzureMonitorOption } from '../../types/types'; import AggregationField from './AggregationField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.tsx index 659718a0c15..e3eae750d76 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.tsx @@ -4,7 +4,7 @@ import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types/types'; import { addValueToOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx index e4756276aab..6a7d3c22d37 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx @@ -5,7 +5,8 @@ import { t } from '@grafana/i18n'; import { EditorList, AccessoryButton } from '@grafana/plugin-ui'; import { Select, Stack, MultiSelect } from '@grafana/ui'; -import { AzureMetricDimension, AzureMonitorOption, AzureMonitorQuery, AzureQueryEditorFieldProps } from '../../types'; +import { AzureMetricDimension, AzureMonitorQuery } from '../../types/query'; +import { AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types/types'; import { Field } from '../shared/Field'; import { setDimensionFilters } from './setQueryValue'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/LegendFormatField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/LegendFormatField.tsx index 7852168636d..955e29f3ee8 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/LegendFormatField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/LegendFormatField.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { t } from '@grafana/i18n'; import { Input } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { Field } from '../shared/Field'; import { setLegendAlias } from './setQueryValue'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx index 06a08b1cdeb..95215db882c 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorOption } from '../../types'; +import { AzureMonitorOption } from '../../types/types'; import MetricNameField from './MetricNameField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.tsx index 92a68181347..4d94af7a366 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.tsx @@ -5,7 +5,7 @@ import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types/types'; import { addValueToOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx index dc1aab8b0bf..4187cfb6a88 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorOption } from '../../types'; +import { AzureMonitorOption } from '../../types/types'; import MetricNamespaceField from './MetricNamespaceField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.tsx index e916cf064a1..53e436c1028 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.tsx @@ -4,7 +4,7 @@ import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types/types'; import { addValueToOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx index 6048ebca21e..04b046d31e4 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -1,10 +1,11 @@ import { PanelData } from '@grafana/data'; import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/plugin-ui'; -import { multiResourceCompatibleTypes } from '../../azureMetadata'; +import { multiResourceCompatibleTypes } from '../../azureMetadata/resourceTypes'; import type Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish, AzureMonitorResource } from '../../types'; +import { AzureMonitorQuery, AzureMonitorResource } from '../../types/query'; +import { AzureMonitorOption, AzureMonitorErrorish } from '../../types/types'; import ResourceField from '../ResourceField'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; import { parseResourceDetails } from '../ResourcePicker/utils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx index 8722f5eee2c..bf427ff37aa 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorOption } from '../../types'; +import { AzureMonitorOption } from '../../types/types'; import TimeGrainField from './TimeGrainField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.tsx index 22b9350b24c..dda333837e6 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.tsx @@ -5,7 +5,7 @@ import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; import TimegrainConverter from '../../time_grain_converter'; -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types/types'; import { addValueToOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TopField.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TopField.tsx index 201c378a88e..3037e17868d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TopField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TopField.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { t } from '@grafana/i18n'; import { Input } from '@grafana/ui'; -import { AzureQueryEditorFieldProps } from '../../types'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { Field } from '../shared/Field'; import { setTop } from './setQueryValue'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts index acf197d2e2c..47a6edf0a19 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts @@ -2,7 +2,8 @@ import { renderHook, waitFor } from '@testing-library/react'; import Datasource from '../../datasource'; import createMockDatasource from '../../mocks/datasource'; -import { AzureMetricQuery, AzureMonitorOption, AzureMonitorQuery, AzureQueryType } from '../../types'; +import { AzureMetricQuery, AzureMonitorQuery, AzureQueryType } from '../../types/query'; +import { AzureMonitorOption } from '../../types/types'; import { useMetricNames, diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.ts b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.ts index f6ec8f2b8f1..82115c289c4 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.ts +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.ts @@ -4,7 +4,8 @@ import { rangeUtil } from '@grafana/data'; import Datasource from '../../datasource'; import TimegrainConverter from '../../time_grain_converter'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, AzureMonitorResource } from '../../types'; +import { AzureMonitorQuery, AzureMonitorResource } from '../../types/query'; +import { AzureMonitorErrorish, AzureMonitorOption } from '../../types/types'; import { toOption } from '../../utils/common'; import { useAsyncState } from '../../utils/useAsyncState'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/setQueryValue.ts b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/setQueryValue.ts index 6e1416913ce..05b17493ca3 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/setQueryValue.ts +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/setQueryValue.ts @@ -1,4 +1,4 @@ -import { AzureMetricDimension, AzureMonitorQuery } from '../../types'; +import { AzureMetricDimension, AzureMonitorQuery } from '../../types/query'; export function setCustomNamespace(query: AzureMonitorQuery, selection: string | undefined): AzureMonitorQuery { if (query.azureMonitor?.customNamespace === selection) { diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx index 5a944e472c3..367c406b0cb 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx @@ -8,7 +8,7 @@ import { selectors } from '../../e2e/selectors'; import createMockDatasource from '../../mocks/datasource'; import { invalidNamespaceError } from '../../mocks/errors'; import createMockQuery from '../../mocks/query'; -import { AzureQueryType, ResultFormat } from '../../types'; +import { AzureQueryType, ResultFormat } from '../../types/query'; import { selectOptionInTest } from '../../utils/testUtils'; import { createMockResourcePickerData } from '../LogsQueryEditor/mocks'; diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx index dbf026c7c3e..54c1bf3dea8 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx @@ -9,13 +9,8 @@ import { Alert, CodeEditor, Space, TextLink } from '@grafana/ui'; import AzureMonitorDatasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { - AzureMonitorDataSourceJsonData, - AzureMonitorErrorish, - AzureMonitorOption, - AzureMonitorQuery, - AzureQueryType, -} from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; +import { AzureMonitorDataSourceJsonData, AzureMonitorErrorish, AzureMonitorOption } from '../../types/types'; import useLastError from '../../utils/useLastError'; import ArgQueryEditor from '../ArgQueryEditor'; import LogsQueryEditor from '../LogsQueryEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx index 3a91a5d9998..035a918fc8d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx @@ -8,7 +8,7 @@ import { Button, ConfirmModal, RadioButtonGroup } from '@grafana/ui'; import { LogsEditorMode } from '../../dataquery.gen'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorQuery, AzureQueryType } from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; interface QueryTypeFieldProps { query: AzureMonitorQuery; @@ -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/components/QueryEditor/usePreparedQuery.ts b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/usePreparedQuery.ts index 8b12487cd39..c2da635fcd0 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/usePreparedQuery.ts +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/usePreparedQuery.ts @@ -2,7 +2,7 @@ import deepEqual from 'fast-deep-equal'; import { defaults } from 'lodash'; import { useEffect, useMemo } from 'react'; -import { AzureMonitorQuery, AzureQueryType, ResultFormat } from '../../types'; +import { AzureMonitorQuery, AzureQueryType, ResultFormat } from '../../types/query'; import migrateQuery from '../../utils/migrateQuery'; const DEFAULT_QUERY = { diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourceField/ResourceField.tsx b/public/app/plugins/datasource/azuremonitor/components/ResourceField/ResourceField.tsx index 723603e968c..ddf88888f07 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourceField/ResourceField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ResourceField/ResourceField.tsx @@ -8,7 +8,8 @@ import { Button, Icon, Modal, useStyles2, IconName } from '@grafana/ui'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; import { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData'; -import { AzureQueryEditorFieldProps, AzureMonitorResource } from '../../types'; +import { AzureMonitorResource } from '../../types/query'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import ResourcePicker from '../ResourcePicker'; import getStyles from '../ResourcePicker/styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx index dffe37fb6ba..d16cfff82e9 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx @@ -5,7 +5,7 @@ import { t } from '@grafana/i18n'; import { Collapse, Space } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorResource } from '../../types'; +import { AzureMonitorResource } from '../../types/query'; export interface ResourcePickerProps { resources: T[]; diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.tsx b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.tsx index 52053c3702d..0f47899d98d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.tsx @@ -8,7 +8,7 @@ import { Alert, Button, LoadingPlaceholder, Modal, useStyles2, Space } from '@gr import { selectors } from '../../e2e/selectors'; import ResourcePickerData, { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData'; -import { AzureMonitorResource } from '../../types'; +import { AzureMonitorResource } from '../../types/query'; import messageFromError from '../../utils/messageFromError'; import AdvancedMulti from './AdvancedMulti'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.ts b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.ts index c226f80366a..bcc9c9d2d03 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.ts +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.ts @@ -4,7 +4,7 @@ import { getTemplateSrv } from '@grafana/runtime'; import UrlBuilder from '../../azure_monitor/url_builder'; import { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData'; -import { AzureMonitorResource, AzureMonitorQuery } from '../../types'; +import { AzureMonitorQuery, AzureMonitorResource } from '../../types/query'; import { ResourceRow, ResourceRowGroup } from './types'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx index c6b03769683..21f8dad755e 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx @@ -11,7 +11,7 @@ import { Select, ButtonSelect, AsyncMultiSelect, getSelectStyles, useTheme2, Che import { AzureMonitorQuery, AzureQueryType, AzureTracesFilter } from '../../dataquery.gen'; import Datasource from '../../datasource'; -import { VariableOptionGroup } from '../../types'; +import { VariableOptionGroup } from '../../types/types'; import { addValueToOptions } from '../../utils/common'; export interface FilterProps { diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx index 9eef48a3a4d..61c571f6ca1 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx @@ -9,7 +9,7 @@ import { AzureQueryType } from '../../dataquery.gen'; import Datasource from '../../datasource'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorQuery } from '../../types'; +import { AzureMonitorQuery } from '../../types/query'; import { selectOptionInTest } from '../../utils/testUtils'; import Filters from './Filters'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx index 96d443343ac..785c9fd4e12 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx @@ -6,7 +6,8 @@ import { t } from '@grafana/i18n'; import { EditorList } from '@grafana/plugin-ui'; import { Field } from '@grafana/ui'; -import { AzureQueryEditorFieldProps, AzureTracesFilter } from '../../types'; +import { AzureTracesFilter } from '../../types/query'; +import { AzureQueryEditorFieldProps } from '../../types/types'; import { makeRenderItem } from './Filter'; import { tablesSchema } from './consts'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.tsx index b1067b3170e..a9ecc848a0b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.tsx @@ -5,7 +5,7 @@ import { t } from '@grafana/i18n'; import { MultiSelect } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types/types'; import { findOptions } from '../../utils/common'; import { Field } from '../shared/Field'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx index 686f8b9475c..405674a1665 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx @@ -9,7 +9,8 @@ import { Input } from '@grafana/ui'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, AzureQueryType, ResultFormat } from '../../types'; +import { AzureMonitorQuery, AzureQueryType, ResultFormat } from '../../types/query'; +import { AzureMonitorErrorish, AzureMonitorOption } from '../../types/types'; import AdvancedResourcePicker from '../LogsQueryEditor/AdvancedResourcePicker'; import ResourceField from '../ResourceField'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/setQueryValue.ts b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/setQueryValue.ts index ca71c5dd010..b901c7b800d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/setQueryValue.ts +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/setQueryValue.ts @@ -1,6 +1,6 @@ import { SelectableValue } from '@grafana/data'; -import { AzureMonitorQuery, AzureQueryType, AzureTracesFilter, ResultFormat } from '../../types'; +import { AzureMonitorQuery, AzureQueryType, AzureTracesFilter, ResultFormat } from '../../types/query'; // Used when switching from a traces exemplar query to a standard Azure Traces query export function setDefaultTracesQuery(query: AzureMonitorQuery): AzureMonitorQuery { diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/GrafanaTemplateVariableFn.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/GrafanaTemplateVariableFn.tsx index b84c05f8641..1727cac59a7 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/GrafanaTemplateVariableFn.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/GrafanaTemplateVariableFn.tsx @@ -5,7 +5,7 @@ import { InlineField, Input } from '@grafana/ui'; import DataSource from '../../datasource'; import { migrateStringQueriesToObjectQueries } from '../../grafanaTemplateVariableFns'; -import { AzureMonitorQuery, AzureQueryType } from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; const GrafanaTemplateVariableFnInput = ({ query, diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx index 9f6c8ac8e75..f14bb609a37 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx @@ -6,7 +6,7 @@ import { select, openMenu } from 'react-select-event'; import * as ui from '@grafana/ui'; import createMockDatasource from '../../mocks/datasource'; -import { AzureMonitorQuery, AzureQueryType } from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; import VariableEditor from './VariableEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx index 850b87ee06f..ce722187c7d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx @@ -11,7 +11,8 @@ import UrlBuilder from '../../azure_monitor/url_builder'; import DataSource from '../../datasource'; import { selectors } from '../../e2e/selectors'; import { migrateQuery } from '../../grafanaTemplateVariableFns'; -import { AzureMonitorOption, AzureMonitorQuery, AzureQueryType } from '../../types'; +import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; +import { AzureMonitorOption } from '../../types/types'; import useLastError from '../../utils/useLastError'; import ArgQueryEditor from '../ArgQueryEditor'; import LogsQueryEditor from '../LogsQueryEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx index fe06dc7cee1..ed02f1b9ce3 100644 --- a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx @@ -4,7 +4,7 @@ import { SelectableValue } from '@grafana/data'; import createMockDatasource from '../../mocks/datasource'; import createMockQuery from '../../mocks/query'; -import { AzureMonitorQuery, ResultFormat } from '../../types'; +import { AzureMonitorQuery, ResultFormat } from '../../types/query'; import FormatAsField from './FormatAsField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.tsx b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.tsx index 34318ec1e74..240db475305 100644 --- a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.tsx @@ -6,7 +6,8 @@ import { t } from '@grafana/i18n'; import { Select } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; -import { FormatAsFieldProps, ResultFormat } from '../../types'; +import { ResultFormat } from '../../types/query'; +import { FormatAsFieldProps } from '../../types/types'; import { Field } from './Field'; diff --git a/public/app/plugins/datasource/azuremonitor/credentials.ts b/public/app/plugins/datasource/azuremonitor/credentials.ts index 44aeb3af365..7557ca5c2b4 100644 --- a/public/app/plugins/datasource/azuremonitor/credentials.ts +++ b/public/app/plugins/datasource/azuremonitor/credentials.ts @@ -8,7 +8,7 @@ import { } from '@grafana/azure-sdk'; import { config } from '@grafana/runtime'; -import { AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceSettings } from './types'; +import { AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceSettings } from './types/types'; export function getCredentials( options: AzureMonitorDataSourceSettings | AzureMonitorDataSourceInstanceSettings diff --git a/public/app/plugins/datasource/azuremonitor/datasource.test.ts b/public/app/plugins/datasource/azuremonitor/datasource.test.ts index 709e7c836d8..be090fa6254 100644 --- a/public/app/plugins/datasource/azuremonitor/datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/datasource.test.ts @@ -2,7 +2,7 @@ import Datasource from './datasource'; import { createMockInstanceSetttings } from './mocks/instanceSettings'; import createMockQuery from './mocks/query'; import { singleVariable } from './mocks/variables'; -import { AzureQueryType } from './types'; +import { AzureQueryType } from './types/query'; jest.mock('@grafana/runtime', () => { return { diff --git a/public/app/plugins/datasource/azuremonitor/datasource.ts b/public/app/plugins/datasource/azuremonitor/datasource.ts index 917426a0169..db8acd9a103 100644 --- a/public/app/plugins/datasource/azuremonitor/datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/datasource.ts @@ -18,7 +18,8 @@ import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analyti import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource'; import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource'; import ResourcePickerData from './resourcePicker/resourcePickerData'; -import { AzureMonitorDataSourceJsonData, AzureMonitorQuery, AzureQueryType } from './types'; +import { AzureMonitorQuery, AzureQueryType } from './types/query'; +import { AzureMonitorDataSourceJsonData } from './types/types'; import migrateAnnotation from './utils/migrateAnnotation'; import migrateQuery from './utils/migrateQuery'; import { VariableSupport } from './variables'; diff --git a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariableFns.ts b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariableFns.ts index b4859079099..3e3945da4f2 100644 --- a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariableFns.ts +++ b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariableFns.ts @@ -1,6 +1,6 @@ import { isGUIDish } from './components/ResourcePicker/utils'; import DataSource from './datasource'; -import { AzureMonitorQuery, AzureQueryType } from './types'; +import { AzureMonitorQuery, AzureQueryType } from './types/query'; import { AppInsightsGroupByQuery, AppInsightsMetricNameQuery, diff --git a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts index 68743a6942e..d5db96929e2 100644 --- a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts +++ b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts @@ -1,6 +1,6 @@ import { migrateQuery, migrateStringQueriesToObjectQueries } from './grafanaTemplateVariableFns'; import createMockDatasource from './mocks/datasource'; -import { AzureMonitorQuery, AzureQueryType } from './types'; +import { AzureMonitorQuery, AzureQueryType } from './types/query'; describe('migrateStringQueriesToObjectQueries', () => { const expectedMigrations: Array<{ input: string; output: AzureMonitorQuery }> = [ 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/datasource/azuremonitor/mocks/argResourcePickerResponse.ts b/public/app/plugins/datasource/azuremonitor/mocks/argResourcePickerResponse.ts index da06ff8236d..875736cfb69 100644 --- a/public/app/plugins/datasource/azuremonitor/mocks/argResourcePickerResponse.ts +++ b/public/app/plugins/datasource/azuremonitor/mocks/argResourcePickerResponse.ts @@ -3,7 +3,7 @@ import { RawAzureResourceGroupItem, RawAzureResourceItem, RawAzureSubscriptionItem, -} from '../types'; +} from '../types/types'; export const createMockARGSubscriptionResponse = (): AzureGraphResponse => ({ data: [ diff --git a/public/app/plugins/datasource/azuremonitor/mocks/datasource.ts b/public/app/plugins/datasource/azuremonitor/mocks/datasource.ts index 893e1123490..e8cdd539fcb 100644 --- a/public/app/plugins/datasource/azuremonitor/mocks/datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/mocks/datasource.ts @@ -1,7 +1,7 @@ import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import Datasource from '../datasource'; -import { AzureMonitorDataSourceInstanceSettings } from '../types'; +import { AzureMonitorDataSourceInstanceSettings } from '../types/types'; import { createMockInstanceSetttings } from './instanceSettings'; import { DeepPartial } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts b/public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts index ec558625438..9b2b5a6daac 100644 --- a/public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts +++ b/public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts @@ -1,6 +1,6 @@ import { KeyValue } from '@grafana/data'; -import { AzureMonitorDataSourceSettings } from '../types'; +import { AzureMonitorDataSourceSettings } from '../types/types'; import { DeepPartial } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts b/public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts index 9c984c870a9..fd0acf35979 100644 --- a/public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts +++ b/public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts @@ -1,6 +1,6 @@ import { DataSourceInstanceSettings, PluginType } from '@grafana/data'; -import { AzureMonitorDataSourceInstanceSettings } from '../types'; +import { AzureMonitorDataSourceInstanceSettings } from '../types/types'; import { DeepPartial, mapPartialArrayObject } from './utils'; diff --git a/public/app/plugins/datasource/azuremonitor/mocks/query.ts b/public/app/plugins/datasource/azuremonitor/mocks/query.ts index fab83f3fdcd..6fbfb66b365 100644 --- a/public/app/plugins/datasource/azuremonitor/mocks/query.ts +++ b/public/app/plugins/datasource/azuremonitor/mocks/query.ts @@ -1,4 +1,4 @@ -import { AzureMonitorQuery, AzureQueryType, ResultFormat } from '../types'; +import { AzureMonitorQuery, AzureQueryType, ResultFormat } from '../types/query'; export default function createMockQuery(overrides?: Partial): AzureMonitorQuery { return { diff --git a/public/app/plugins/datasource/azuremonitor/module.test.ts b/public/app/plugins/datasource/azuremonitor/module.test.ts index 06a55850647..117ded762ce 100644 --- a/public/app/plugins/datasource/azuremonitor/module.test.ts +++ b/public/app/plugins/datasource/azuremonitor/module.test.ts @@ -2,7 +2,7 @@ import { DashboardLoadedEvent } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import './module'; -import { AzureMonitorQuery } from './types'; +import { AzureMonitorQuery } from './types/query'; jest.mock('@grafana/runtime', () => { return { diff --git a/public/app/plugins/datasource/azuremonitor/module.ts b/public/app/plugins/datasource/azuremonitor/module.ts index 654ac1ff5a4..bf96a78c1b3 100644 --- a/public/app/plugins/datasource/azuremonitor/module.ts +++ b/public/app/plugins/datasource/azuremonitor/module.ts @@ -7,7 +7,8 @@ import AzureMonitorQueryEditor from './components/QueryEditor'; import Datasource from './datasource'; import pluginJson from './plugin.json'; import { trackAzureMonitorDashboardLoaded } from './tracking'; -import { AzureMonitorQuery, AzureMonitorDataSourceJsonData, AzureQueryType, ResultFormat } from './types'; +import { AzureMonitorQuery, AzureQueryType, ResultFormat } from './types/query'; +import { AzureMonitorDataSourceJsonData } from './types/types'; initPluginTranslations(pluginJson.id); diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 7ff99e186da..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", @@ -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/azuremonitor/resourcePicker/resourcePickerData.test.ts b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts index 0243f717d52..9bdf757a5c3 100644 --- a/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts +++ b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts @@ -1,4 +1,4 @@ -import { resourceTypes } from '../azureMetadata'; +import { resourceTypes } from '../azureMetadata/resourceTypes'; import AzureResourceGraphDatasource from '../azure_resource_graph/azure_resource_graph_datasource'; import { ResourceRowType } from '../components/ResourcePicker/types'; import { @@ -8,7 +8,7 @@ import { } from '../mocks/argResourcePickerResponse'; import createMockDatasource from '../mocks/datasource'; import { createMockInstanceSetttings } from '../mocks/instanceSettings'; -import { AzureGraphResponse } from '../types'; +import { AzureGraphResponse } from '../types/types'; import ResourcePickerData from './resourcePickerData'; diff --git a/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.ts b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.ts index b3cd9e7a498..35bf3933f1b 100644 --- a/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.ts +++ b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.ts @@ -1,6 +1,7 @@ import { DataSourceWithBackend, reportInteraction } from '@grafana/runtime'; -import { logsResourceTypes, resourceTypeDisplayNames, resourceTypes } from '../azureMetadata'; +import { logsResourceTypes } from '../azureMetadata/logsResourceTypes'; +import { resourceTypeDisplayNames, resourceTypes } from '../azureMetadata/resourceTypes'; import AzureMonitorDatasource from '../azure_monitor/azure_monitor_datasource'; import AzureResourceGraphDatasource from '../azure_resource_graph/azure_resource_graph_datasource'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types'; @@ -12,14 +13,13 @@ import { parseResourceURI, resourceToString, } from '../components/ResourcePicker/utils'; +import { AzureMonitorQuery, AzureMonitorResource } from '../types/query'; import { AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceJsonData, - AzureMonitorResource, - AzureMonitorQuery, AzureResourceSummaryItem, RawAzureResourceItem, -} from '../types'; +} from '../types/types'; const logsSupportedResourceTypesKusto = logsResourceTypes.map((v) => `"${v}"`).join(','); diff --git a/public/app/plugins/datasource/azuremonitor/types/index.ts b/public/app/plugins/datasource/azuremonitor/types/index.ts deleted file mode 100644 index 06ced0cd13a..00000000000 --- a/public/app/plugins/datasource/azuremonitor/types/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './logAnalyticsMetadata'; -export * from './query'; -export * from './types'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/common.ts b/public/app/plugins/datasource/azuremonitor/utils/common.ts index 3d47b161f5c..00472a6cf94 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/common.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/common.ts @@ -3,7 +3,7 @@ import { map } from 'lodash'; import { ScopedVars, SelectableValue, VariableWithMultiSupport } from '@grafana/data'; import { TemplateSrv, VariableInterpolation } from '@grafana/runtime'; -import { AzureMonitorOption, VariableOptionGroup } from '../types'; +import { AzureMonitorOption, VariableOptionGroup } from '../types/types'; export const hasOption = (options: AzureMonitorOption[], value: string): boolean => options.some((v) => (v.options ? hasOption(v.options, value) : v.value === value)); diff --git a/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts b/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts index 2a7236df003..3fc3c36f735 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts @@ -1,6 +1,6 @@ import { isValidElement } from 'react'; -import { AzureMonitorErrorish } from '../types'; +import { AzureMonitorErrorish } from '../types/types'; export function messageFromElement(error: AzureMonitorErrorish): AzureMonitorErrorish | undefined { if (isValidElement(error)) { diff --git a/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.test.ts b/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.test.ts index bfd74252441..b1642f250e2 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.test.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.test.ts @@ -1,6 +1,6 @@ import { AnnotationQuery } from '@grafana/data'; -import { AzureMonitorQuery, AzureQueryType } from '../types'; +import { AzureMonitorQuery, AzureQueryType } from '../types/query'; import migrateAnnotation from './migrateAnnotation'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.ts b/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.ts index 31247ec7a78..ce37019e15f 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/migrateAnnotation.ts @@ -1,6 +1,6 @@ import { AnnotationQuery } from '@grafana/data'; -import { AzureMonitorQuery, AzureQueryType } from '../types'; +import { AzureMonitorQuery, AzureQueryType } from '../types/query'; // The old Angular annotations editor put some properties (rawQuery, workspace, subscription) // on the root annotation object, rather than down in the 'targets' query value diff --git a/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.test.ts b/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.test.ts index e46be675f8f..7e5e1769660 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.test.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.test.ts @@ -1,4 +1,4 @@ -import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType, ResultFormat } from '../types'; +import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType, ResultFormat } from '../types/query'; import migrateQuery from './migrateQuery'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.ts b/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.ts index feb22d424e3..6ebf853d8fa 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/migrateQuery.ts @@ -5,7 +5,7 @@ import { } from '../components/MetricsQueryEditor/setQueryValue'; import { parseResourceDetails } from '../components/ResourcePicker/utils'; import TimegrainConverter from '../time_grain_converter'; -import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType } from '../types'; +import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType } from '../types/query'; const OLD_DEFAULT_DROPDOWN_VALUE = 'select'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/useLastError.ts b/public/app/plugins/datasource/azuremonitor/utils/useLastError.ts index 1c26117b2b2..00a6f289062 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/useLastError.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/useLastError.ts @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo } from 'react'; -import { AzureMonitorErrorish } from '../types'; +import { AzureMonitorErrorish } from '../types/types'; import { messageFromElement } from './messageFromError'; diff --git a/public/app/plugins/datasource/azuremonitor/variables.test.ts b/public/app/plugins/datasource/azuremonitor/variables.test.ts index dcec7ff8005..55102e3fcfa 100644 --- a/public/app/plugins/datasource/azuremonitor/variables.test.ts +++ b/public/app/plugins/datasource/azuremonitor/variables.test.ts @@ -4,7 +4,7 @@ import { DataQueryRequest, toDataFrame } from '@grafana/data'; import createMockDatasource from './mocks/datasource'; import { invalidSubscriptionError } from './mocks/errors'; -import { AzureMonitorQuery, AzureQueryType } from './types'; +import { AzureMonitorQuery, AzureQueryType } from './types/query'; import { VariableSupport } from './variables'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/plugins/datasource/azuremonitor/variables.ts b/public/app/plugins/datasource/azuremonitor/variables.ts index 2d6b3d73cb8..5477def0e2d 100644 --- a/public/app/plugins/datasource/azuremonitor/variables.ts +++ b/public/app/plugins/datasource/azuremonitor/variables.ts @@ -15,8 +15,9 @@ import { parseResourceURI } from './components/ResourcePicker/utils'; import VariableEditor from './components/VariableEditor/VariableEditor'; import DataSource from './datasource'; import { migrateQuery } from './grafanaTemplateVariableFns'; -import { AzureMonitorQuery, AzureQueryType, RawAzureResourceItem } from './types'; +import { AzureMonitorQuery, AzureQueryType } from './types/query'; import { GrafanaTemplateVariableQuery } from './types/templateVariables'; +import { RawAzureResourceItem } from './types/types'; import messageFromError from './utils/messageFromError'; export function parseResourceNamesAsTemplateVariable(resources: RawAzureResourceItem[], metricNamespace?: string) { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx index 6925e724d68..cb754445570 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx @@ -10,10 +10,9 @@ import CloudMonitoringDatasource from '../datasource'; import { TimeSeriesList, CloudMonitoringQuery, QueryType } from '../types/query'; import { CloudMonitoringOptions } from '../types/types'; +import { AnnotationsHelp } from './AnnotationsHelp'; import { MetricQueryEditor, defaultTimeSeriesList } from './MetricQueryEditor'; -import { AnnotationsHelp } from './'; - export type Props = QueryEditorProps; export const defaultQuery: (datasource: CloudMonitoringDatasource) => TimeSeriesList = (datasource) => ({ diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx index 69801b0c976..a3c1d9372b6 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx @@ -4,7 +4,7 @@ import { Stack, Switch } from '@grafana/ui'; import { GRAPH_PERIODS } from '../constants'; -import { PeriodSelect } from './index'; +import { PeriodSelect } from './PeriodSelect'; export interface Props { refId: string; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx index 7b701df34da..d0578dea9d4 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx @@ -11,12 +11,10 @@ import { selectors } from '../e2e/selectors'; import { CloudMonitoringQuery, PromQLQuery, QueryType, SLOQuery } from '../types/query'; import { CloudMonitoringOptions } from '../types/types'; -import { defaultTimeSeriesList, defaultTimeSeriesQuery } from './MetricQueryEditor'; +import { defaultTimeSeriesList, defaultTimeSeriesQuery, MetricQueryEditor } from './MetricQueryEditor'; import { PromQLQueryEditor } from './PromQLEditor'; import { QueryHeader } from './QueryHeader'; -import { defaultQuery as defaultSLOQuery } from './SLOQueryEditor'; - -import { MetricQueryEditor, SLOQueryEditor } from './'; +import { defaultQuery as defaultSLOQuery, SLOQueryEditor } from './SLOQueryEditor'; export type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx index 0b7169ae2fd..6045e61b37d 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx @@ -13,7 +13,7 @@ import { VariableQueryData, } from '../types/types'; -import { VariableQueryField } from './'; +import { VariableQueryField } from './Fields'; export type Props = QueryEditorProps< CloudMonitoringDatasource, 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} + 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/QueryEditor.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx index 95b67bf1f0c..3fc5d4b84c2 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx @@ -5,7 +5,6 @@ import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { selectors as editorSelectors } from '@grafana/e2e-selectors'; import { InlineField, InlineFieldRow, InlineSwitch, Input, Select, Icon, TextArea } from '@grafana/ui'; -import { RandomWalkEditor, StreamingClientEditor } from './components'; import { CSVContentEditor } from './components/CSVContentEditor'; import { CSVFileEditor } from './components/CSVFileEditor'; import { CSVWavesEditor } from './components/CSVWaveEditor'; @@ -14,8 +13,10 @@ import ErrorWithSourceQueryEditor from './components/ErrorWithSourceEditor'; import { GrafanaLiveEditor } from './components/GrafanaLiveEditor'; import { NodeGraphEditor } from './components/NodeGraphEditor'; import { PredictablePulseEditor } from './components/PredictablePulseEditor'; +import { RandomWalkEditor } from './components/RandomWalkEditor'; import { RawFrameEditor } from './components/RawFrameEditor'; import { SimulationQueryEditor } from './components/SimulationQueryEditor'; +import { StreamingClientEditor } from './components/StreamingClientEditor'; import { USAQueryEditor, usaQueryModes } from './components/USAQueryEditor'; import { defaultCSVWaveQuery, defaultPulseQuery, defaultQuery } from './constants'; import { CSVWave, NodesQuery, TestDataDataQuery, TestDataQueryType, USAQuery } from './dataquery'; 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} /> diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts b/public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts deleted file mode 100644 index 66ff3418758..00000000000 --- a/public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { StreamingClientEditor } from './StreamingClientEditor'; -export { RandomWalkEditor } from './RandomWalkEditor'; 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/grafana/components/QueryEditor.tsx b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx index 9b1a9918edf..8f799c3e35a 100644 --- a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx @@ -32,7 +32,8 @@ import { Stack, } from '@grafana/ui'; import { hasAlphaPanels } from 'app/core/config'; -import * as DFImport from 'app/features/dataframe-import'; +import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants'; +import { filesToDataframes } from 'app/features/dataframe-import/utils'; import { getManagedChannelInfo } from 'app/features/live/info'; import { SearchQuery } from 'app/features/search/service/types'; @@ -360,7 +361,7 @@ export class UnthemedQueryEditor extends PureComponent { }; onFileDrop = (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => { - DFImport.filesToDataframes(acceptedFiles).subscribe((next) => { + filesToDataframes(acceptedFiles).subscribe((next) => { const snapshot: DataFrameJSON[] = []; next.dataFrames.forEach((df) => { const dataframeJson = dataFrameToJSON(df); @@ -406,9 +407,9 @@ export class UnthemedQueryEditor extends PureComponent { fileListRenderer={this.fileListRenderer} options={{ onDrop: this.onFileDrop, - maxSize: DFImport.maxFileSize, + maxSize: maxFileSize, multiple: false, - accept: DFImport.acceptedFiles, + accept: acceptedFiles, }} > void ) { return (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => { - DFImport.filesToDataframes(acceptedFiles).subscribe(async (next) => { + filesToDataframes(acceptedFiles).subscribe(async (next) => { const snapshot: DataFrameJSON[] = []; next.dataFrames.forEach((df: DataFrame) => { const dataframeJson = dataFrameToJSON(df); 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/components/LokiLabelBrowser.tsx b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx index 49e58e8f233..5b1c9b8c9a6 100644 --- a/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx +++ b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx @@ -26,6 +26,7 @@ const MAX_LABEL_COUNT = 1000; const MAX_VALUE_COUNT = 10000; const MAX_AUTO_SELECT = 4; const EMPTY_SELECTOR = '{}'; +const collator = new Intl.Collator('en', { sensitivity: 'accent' }); export interface BrowserProps { languageProvider: LokiLanguageProvider; @@ -68,7 +69,10 @@ export function buildSelector(labels: SelectableLabel[]): string { const selectedLabels = []; for (const label of labels) { if (label.selected && label.values && label.values.length > 0) { - const selectedValues = label.values.filter((value) => value.selected).map((value) => value.name); + const selectedValues = label.values + .filter((value) => value.selected) + .map((value) => value.name) + .sort(collator.compare); // sort selected values alphabetically if (selectedValues.length > 1) { selectedLabels.push(`${label.name}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"`); } else if (selectedValues.length === 1) { @@ -97,7 +101,13 @@ export function facetLabels( label.values?.filter((value) => value.selected).map((value) => value.name) || [] ); // Values for this label have not been requested yet, let's use the facetted ones as the initial values - existingValues = possibleValues.map((value) => ({ name: value, selected: selectedValues.has(value) })); + existingValues = possibleValues + .slice() + .sort(collator.compare) // sort raw label values alphabetically + .map((value) => ({ + name: value, + selected: selectedValues.has(value), + })); } return { ...label, loading: false, values: existingValues, facets: existingValues.length }; } diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index c63332522cd..8d49fe6f27b 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1809,6 +1809,162 @@ describe('LokiDatasource', () => { }); }); + describe('scopes application', () => { + let ds: LokiDatasource; + let origBackendSrv: BackendSrv; + + beforeEach(() => { + origBackendSrv = getBackendSrv(); + ds = createLokiDatasource(templateSrvStub); + // Enable the required feature toggles + config.featureToggles.scopeFilters = true; + config.featureToggles.logQLScope = true; + }); + + afterEach(() => { + setBackendSrv(origBackendSrv); + // Reset feature toggles to false + config.featureToggles.scopeFilters = false; + config.featureToggles.logQLScope = false; + }); + + it('should apply scopes to queries when feature toggles are enabled', async () => { + const mockScopes = [ + { + metadata: { name: 'test-scope' }, + spec: { + title: 'Test Scope', + type: 'test', + description: 'Test scope description', + category: 'test-category', + filters: [ + { key: 'environment', value: 'production', operator: 'equals' as const }, + { key: 'service', value: 'api', operator: 'equals' as const }, + ], + }, + }, + ]; + + const query: DataQueryRequest = { + ...baseRequestOptions, + targets: [{ expr: '{job="grafana"}', refId: 'A' }], + scopes: mockScopes, + }; + + const fetchMock = jest.fn().mockReturnValue(of({ data: testLogsResponse })); + setBackendSrv({ ...origBackendSrv, fetch: fetchMock }); + + await ds.query(query).pipe(take(1)).toPromise(); + + expect(fetchMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + queries: expect.arrayContaining([ + expect.objectContaining({ + scopes: [ + { key: 'environment', value: 'production', operator: 'equals' }, + { key: 'service', value: 'api', operator: 'equals' }, + ], + }), + ]), + }), + }) + ); + }); + + it('should not apply scopes when feature toggles are disabled', async () => { + // Disable the required feature toggles + config.featureToggles.scopeFilters = false; + config.featureToggles.logQLScope = false; + + const mockScopes = [ + { + metadata: { name: 'test-scope' }, + spec: { + title: 'Test Scope', + type: 'test', + description: 'Test scope description', + category: 'test-category', + filters: [{ key: 'environment', value: 'production', operator: 'equals' as const }], + }, + }, + ]; + + const query: DataQueryRequest = { + ...baseRequestOptions, + targets: [{ expr: '{job="grafana"}', refId: 'A' }], + scopes: mockScopes, + }; + + const fetchMock = jest.fn().mockReturnValue(of({ data: testLogsResponse })); + setBackendSrv({ ...origBackendSrv, fetch: fetchMock }); + + await ds.query(query).pipe(take(1)).toPromise(); + + expect(fetchMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + queries: expect.arrayContaining([ + expect.objectContaining({ + scopes: undefined, + }), + ]), + }), + }) + ); + }); + + it('should handle empty scopes array', async () => { + const query: DataQueryRequest = { + ...baseRequestOptions, + targets: [{ expr: '{job="grafana"}', refId: 'A' }], + scopes: [], + }; + + const fetchMock = jest.fn().mockReturnValue(of({ data: testLogsResponse })); + setBackendSrv({ ...origBackendSrv, fetch: fetchMock }); + + await ds.query(query).pipe(take(1)).toPromise(); + + expect(fetchMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + queries: expect.arrayContaining([ + expect.objectContaining({ + scopes: [], + }), + ]), + }), + }) + ); + }); + + it('should handle undefined scopes', async () => { + const query: DataQueryRequest = { + ...baseRequestOptions, + targets: [{ expr: '{job="grafana"}', refId: 'A' }], + scopes: undefined, + }; + + const fetchMock = jest.fn().mockReturnValue(of({ data: testLogsResponse })); + setBackendSrv({ ...origBackendSrv, fetch: fetchMock }); + + await ds.query(query).pipe(take(1)).toPromise(); + + expect(fetchMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + queries: expect.arrayContaining([ + expect.objectContaining({ + scopes: undefined, + }), + ]), + }), + }) + ); + }); + }); + describe('getQueryStats', () => { let ds: LokiDatasource; let query: LokiQuery; diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 4ded88df124..468b52b005e 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -299,7 +299,14 @@ export class LokiDatasource query(request: DataQueryRequest): Observable { const queries = request.targets .map(getNormalizedLokiQuery) // used to "fix" the deprecated `.queryType` prop - .map((q) => ({ ...q, maxLines: q.maxLines ?? this.maxLines })); + .map((q) => ({ + ...q, + maxLines: q.maxLines ?? this.maxLines, + scopes: + config.featureToggles.scopeFilters && config.featureToggles.logQLScope + ? request.scopes?.flatMap((scope) => scope.spec.filters) + : undefined, + })); const fixedRequest: DataQueryRequest = { ...request, 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); } 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..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", @@ -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/public/app/plugins/panel/candlestick/fields.ts b/public/app/plugins/panel/candlestick/fields.ts index 29ce56ec079..42fb07732df 100644 --- a/public/app/plugins/panel/candlestick/fields.ts +++ b/public/app/plugins/panel/candlestick/fields.ts @@ -9,7 +9,7 @@ import { } from '@grafana/data'; import { maybeSortFrame } from '@grafana/data/internal'; import { t } from '@grafana/i18n'; -import { findField } from 'app/features/dimensions'; +import { findField } from 'app/features/dimensions/utils'; import { prepareGraphableFields } from '../timeseries/utils'; diff --git a/public/app/plugins/panel/canvas/components/SetBackground.tsx b/public/app/plugins/panel/canvas/components/SetBackground.tsx index 7d33b53df2a..4fea18ad6a5 100644 --- a/public/app/plugins/panel/canvas/components/SetBackground.tsx +++ b/public/app/plugins/panel/canvas/components/SetBackground.tsx @@ -5,8 +5,8 @@ import { GrafanaTheme2 } from '@grafana/data'; import { ResourceDimensionMode } from '@grafana/schema'; import { Portal, useTheme2 } from '@grafana/ui'; import { Scene } from 'app/features/canvas/runtime/scene'; -import { MediaType, ResourceFolderName } from 'app/features/dimensions'; import { ResourcePickerPopover } from 'app/features/dimensions/editors/ResourcePickerPopover'; +import { MediaType, ResourceFolderName } from 'app/features/dimensions/types'; import { AnchorPoint } from '../types'; diff --git a/public/app/plugins/panel/canvas/editor/options.ts b/public/app/plugins/panel/canvas/editor/options.ts index 79420b34187..dd107d010cb 100644 --- a/public/app/plugins/panel/canvas/editor/options.ts +++ b/public/app/plugins/panel/canvas/editor/options.ts @@ -3,8 +3,10 @@ import { PanelOptionsSupplier } from '@grafana/data/internal'; import { t } from '@grafana/i18n'; import { ConnectionDirection } from 'app/features/canvas/element'; import { SVGElements } from 'app/features/canvas/runtime/element'; -import { ColorDimensionEditor, ResourceDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensions/editors'; import { BackgroundSizeEditor } from 'app/features/dimensions/editors/BackgroundSizeEditor'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor'; +import { ScaleDimensionEditor } from 'app/features/dimensions/editors/ScaleDimensionEditor'; import { CanvasConnection, CanvasElementOptions } from '../panelcfg.gen'; import { LineStyle } from '../types'; diff --git a/public/app/plugins/panel/geomap/components/MarkersLegend.tsx b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx index 1a06f88bdfb..cb897193034 100644 --- a/public/app/plugins/panel/geomap/components/MarkersLegend.tsx +++ b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx @@ -17,7 +17,7 @@ import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG'; import { getThresholdItems } from 'app/core/components/TimelineChart/utils'; import { config } from 'app/core/config'; -import { DimensionSupplier } from 'app/features/dimensions'; +import { DimensionSupplier } from 'app/features/dimensions/types'; import { StyleConfigState } from '../style/types'; import { MapLayerState } from '../types'; diff --git a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx index fb36d762eb5..c7f07fb2dba 100644 --- a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx @@ -23,13 +23,11 @@ import { } from '@grafana/ui'; import { NumberValueEditor } from 'app/core/components/OptionsUI/number'; import { SliderValueEditor } from 'app/core/components/OptionsUI/slider'; -import { - ColorDimensionEditor, - ResourceDimensionEditor, - ScaleDimensionEditor, - ScalarDimensionEditor, - TextDimensionEditor, -} from 'app/features/dimensions/editors'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor'; +import { ScaleDimensionEditor } from 'app/features/dimensions/editors/ScaleDimensionEditor'; +import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { ResourceFolderName, defaultTextConfig, MediaType } from 'app/features/dimensions/types'; import { @@ -140,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, @@ -230,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, @@ -244,9 +252,15 @@ export const StyleEditor = (props: Props) => { value={value?.symbolAlign?.vertical ?? defaultStyleConfig.symbolAlign.vertical} onChange={onAlignVerticalChange} options={[ - { value: VerticalAlign.Top, label: capitalize(VerticalAlign.Top) }, - { value: VerticalAlign.Center, label: capitalize(VerticalAlign.Center) }, - { value: VerticalAlign.Bottom, label: capitalize(VerticalAlign.Bottom) }, + { value: VerticalAlign.Top, label: t('geomap.style-editor.vertical-align-options.label-top', 'Top') }, + { + value: VerticalAlign.Center, + label: t('geomap.style-editor.vertical-align-options.label-center', 'Center'), + }, + { + value: VerticalAlign.Bottom, + label: t('geomap.style-editor.vertical-align-options.label-bottom', 'Bottom'), + }, ]} /> @@ -255,9 +269,18 @@ export const StyleEditor = (props: Props) => { value={value?.symbolAlign?.horizontal ?? defaultStyleConfig.symbolAlign.horizontal} onChange={onAlignHorizontalChange} options={[ - { value: HorizontalAlign.Left, label: capitalize(HorizontalAlign.Left) }, - { value: HorizontalAlign.Center, label: capitalize(HorizontalAlign.Center) }, - { value: HorizontalAlign.Right, label: capitalize(HorizontalAlign.Right) }, + { + value: HorizontalAlign.Left, + label: t('geomap.style-editor.horizontal-align-options.label-left', 'Left'), + }, + { + value: HorizontalAlign.Center, + label: t('geomap.style-editor.horizontal-align-options.label-center', 'Center'), + }, + { + value: HorizontalAlign.Right, + label: t('geomap.style-editor.horizontal-align-options.label-right', 'Right'), + }, ]} /> diff --git a/public/app/plugins/panel/geomap/editor/layerEditor.tsx b/public/app/plugins/panel/geomap/editor/layerEditor.tsx index 865b9f94654..4f70a82e587 100644 --- a/public/app/plugins/panel/geomap/editor/layerEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/layerEditor.tsx @@ -2,6 +2,7 @@ import { get as lodashGet, isEqual } from 'lodash'; import { FrameGeometrySourceMode, getFrameMatchers, MapLayerOptions } from '@grafana/data'; import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/internal'; +import { t } from '@grafana/i18n'; import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; import { addLocationFields } from 'app/features/geo/editor/locationEditor'; @@ -69,7 +70,7 @@ export function getLayerEditor(opts: LayerEditorOptions): NestedPanelOptions = { .addCustomEditor({ id: 'config.style', path: 'config.style', - name: 'Styles', + name: t('geomap.markers-layer.name-styles', 'Styles'), editor: StyleEditor, settings: { displayRotation: true, @@ -239,8 +240,8 @@ export const markersLayer: MapLayerRegistryItem = { }) .addBooleanSwitch({ path: 'config.showLegend', - name: 'Show legend', - description: 'Show map legend', + name: t('geomap.markers-layer.name-show-legend', 'Show legend'), + description: t('geomap.markers-layer.description-show-legend', 'Show map legend'), defaultValue: defaultOptions.showLegend, }); }, diff --git a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx index ed9a77ce960..ffd64aa03cd 100644 --- a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx @@ -5,8 +5,9 @@ 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'; +import { findField } from 'app/features/dimensions/utils'; import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource'; import { getLocationMatchers } from 'app/features/geo/utils/location'; @@ -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/app/plugins/panel/geomap/module.tsx b/public/app/plugins/panel/geomap/module.tsx index b5376ea7acf..f8c7825f896 100644 --- a/public/app/plugins/panel/geomap/module.tsx +++ b/public/app/plugins/panel/geomap/module.tsx @@ -1,5 +1,5 @@ import { PanelPlugin } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; +import { t, Trans } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { commonOptionsBuilder } from '@grafana/ui'; @@ -20,13 +20,13 @@ export const plugin = new PanelPlugin(GeomapPanel) }, }) .setPanelOptions((builder, context) => { - let category = ['Map view']; + let category = [t('geomap.category-map-view', 'Map view')]; builder.addCustomEditor({ category, id: 'view', path: 'view', - name: 'Initial view', // don't show it - description: 'This location will show when the panel first loads.', + name: t('geomap.name-initial-view', 'Initial view'), // don't show it + description: t('geomap.description-initial-view', 'This location will show when the panel first loads.'), editor: MapViewEditor, defaultValue: defaultMapViewConfig, }); @@ -34,8 +34,11 @@ export const plugin = new PanelPlugin(GeomapPanel) builder.addBooleanSwitch({ category, path: 'view.shared', - description: 'Use the same view across multiple panels. Note: this may require a dashboard reload.', - name: 'Share view', + description: t( + 'geomap.description-share-view', + 'Use the same view across multiple panels. Note: this may require a dashboard reload.' + ), + name: t('geomap.name-share-view', 'Share view'), defaultValue: defaultMapViewConfig.shared, }); @@ -44,8 +47,8 @@ export const plugin = new PanelPlugin(GeomapPanel) if (!state?.layers) { // TODO? show spinner? } else { - const layersCategory = ['Map layers']; - const basemapCategory = ['Basemap layer']; + const layersCategory = [t('geomap.category-map-layers', 'Map layers')]; + const basemapCategory = [t('geomap.category-basemap-layer', 'Basemap layer')]; builder.addCustomEditor({ category: layersCategory, id: 'layers', @@ -93,59 +96,70 @@ export const plugin = new PanelPlugin(GeomapPanel) } // The controls section - category = ['Map controls']; + category = [t('geomap.category-map-controls', 'Map controls')]; builder .addBooleanSwitch({ category, path: 'controls.showZoom', - description: 'Show zoom control buttons in the upper left corner', - name: 'Show zoom control', + description: t('geomap.description-show-zoom', 'Show zoom control buttons in the upper left corner'), + name: t('geomap.name-show-zoom', 'Show zoom control'), defaultValue: true, }) .addBooleanSwitch({ category, path: 'controls.mouseWheelZoom', - description: 'Enable zoom control via mouse wheel', - name: 'Mouse wheel zoom', + description: t('geomap.description-mouse-wheel-zoom', 'Enable zoom control via mouse wheel'), + name: t('geomap.name-mouse-wheel-zoom', 'Mouse wheel zoom'), defaultValue: true, }) .addBooleanSwitch({ category, path: 'controls.showAttribution', - name: 'Show attribution', - description: 'Show the map source attribution info in the lower right', + name: t('geomap.name-show-attribution', 'Show attribution'), + description: t( + 'geomap.description-show-attribution', + 'Show the map source attribution info in the lower right' + ), defaultValue: true, }) .addBooleanSwitch({ category, path: 'controls.showScale', - name: 'Show scale', - description: 'Indicate map scale', + name: t('geomap.name-show-scale', 'Show scale'), + description: t('geomap.description-show-scale', 'Indicate map scale'), defaultValue: false, }) .addBooleanSwitch({ category, path: 'controls.showMeasure', - name: 'Show measure tools', - description: 'Show tools for making measurements on the map', + name: t('geomap.name-show-measure', 'Show measure tools'), + description: t('geomap.description-show-measure', 'Show tools for making measurements on the map'), defaultValue: false, }) .addBooleanSwitch({ category, path: 'controls.showDebug', - name: 'Show debug', - description: 'Show map info', + name: t('geomap.name-show-debug', 'Show debug'), + description: t('geomap.description-show-debug', 'Show map info'), defaultValue: false, }) .addRadio({ category, path: 'tooltip.mode', - name: 'Tooltip', + name: t('geomap.name-tooltip', 'Tooltip'), defaultValue: TooltipMode.Details, settings: { options: [ - { label: 'None', value: TooltipMode.None, description: 'Show contents on click, not hover' }, - { label: 'Details', value: TooltipMode.Details, description: 'Show popup on hover' }, + { + label: t('geomap.tooltip-options.label-none', 'None'), + value: TooltipMode.None, + description: t('geomap.tooltip-options.description-none', 'Show contents on click, not hover'), + }, + { + label: t('geomap.tooltip-options.label-details', 'Details'), + value: TooltipMode.Details, + description: t('geomap.tooltip-options.description-details', 'Show popup on hover'), + }, ], }, }); diff --git a/public/app/plugins/panel/geomap/style/markers.test.ts b/public/app/plugins/panel/geomap/style/markers.test.ts index cd7e5acf452..610df9ebcf4 100644 --- a/public/app/plugins/panel/geomap/style/markers.test.ts +++ b/public/app/plugins/panel/geomap/style/markers.test.ts @@ -1,9 +1,9 @@ -import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; +import { getPublicOrAbsoluteUrl } from 'app/features/dimensions/resource'; import { getWebGLStyle } from './markers'; // Mock dependencies -jest.mock('app/features/dimensions', () => ({ +jest.mock('app/features/dimensions/resource', () => ({ getPublicOrAbsoluteUrl: jest.fn(), })); diff --git a/public/app/plugins/panel/geomap/style/markers.ts b/public/app/plugins/panel/geomap/style/markers.ts index 04772af1189..b54e3fd54f6 100644 --- a/public/app/plugins/panel/geomap/style/markers.ts +++ b/public/app/plugins/panel/geomap/style/markers.ts @@ -4,7 +4,7 @@ import tinycolor from 'tinycolor2'; import { Registry, RegistryItem, textUtil } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; +import { getPublicOrAbsoluteUrl } from 'app/features/dimensions/resource'; import { defaultStyleConfig, DEFAULT_SIZE, StyleConfigValues, StyleMaker } from './types'; import { getDisplacement } from './utils'; diff --git a/public/app/plugins/panel/geomap/style/types.ts b/public/app/plugins/panel/geomap/style/types.ts index a1e7329ee33..72703a90476 100644 --- a/public/app/plugins/panel/geomap/style/types.ts +++ b/public/app/plugins/panel/geomap/style/types.ts @@ -9,7 +9,7 @@ import { ScalarDimensionMode, TextDimensionConfig, } from '@grafana/schema'; -import { DimensionSupplier } from 'app/features/dimensions'; +import { DimensionSupplier } from 'app/features/dimensions/types'; export enum GeometryTypeId { Point = 'point', diff --git a/public/app/plugins/panel/geomap/utils/utils.test.ts b/public/app/plugins/panel/geomap/utils/utils.test.ts index d338233d63c..44995c1857f 100644 --- a/public/app/plugins/panel/geomap/utils/utils.test.ts +++ b/public/app/plugins/panel/geomap/utils/utils.test.ts @@ -15,10 +15,16 @@ jest.mock('@grafana/runtime', () => ({ })); // Mock the dimensions module since it's imported by utils.ts -jest.mock('app/features/dimensions', () => ({ +jest.mock('app/features/dimensions/color', () => ({ getColorDimension: jest.fn(), +})); +jest.mock('app/features/dimensions/scalar', () => ({ getScalarDimension: jest.fn(), +})); +jest.mock('app/features/dimensions/scale', () => ({ getScaledDimension: jest.fn(), +})); +jest.mock('app/features/dimensions/text', () => ({ getTextDimension: jest.fn(), })); diff --git a/public/app/plugins/panel/geomap/utils/utils.ts b/public/app/plugins/panel/geomap/utils/utils.ts index 23426d54db4..615466f339a 100644 --- a/public/app/plugins/panel/geomap/utils/utils.ts +++ b/public/app/plugins/panel/geomap/utils/utils.ts @@ -14,8 +14,12 @@ import TileSource from 'ol/source/Tile'; import VectorSource from 'ol/source/Vector'; import { DataFrame, GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { getTemplateSrv } from '@grafana/runtime'; -import { getColorDimension, getScalarDimension, getScaledDimension, getTextDimension } from 'app/features/dimensions'; +import { getColorDimension } from 'app/features/dimensions/color'; +import { getScalarDimension } from 'app/features/dimensions/scalar'; +import { getScaledDimension } from 'app/features/dimensions/scale'; +import { getTextDimension } from 'app/features/dimensions/text'; import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; import { GeomapPanel } from '../GeomapPanel'; @@ -129,13 +133,13 @@ export const notifyPanelEditor = (geomapPanel: GeomapPanel, layers: MapLayerStat export const getNextLayerName = (panel: GeomapPanel) => { let idx = panel.layers.length; // since basemap is 0, this looks right while (true && idx < 100) { - const name = `Layer ${idx++}`; + const name = t('geomap.utils.get-next-layer-name', 'Layer {{name}}', { name: idx++ }); if (!panel.byName.has(name)) { return name; } } - return `Layer ${Date.now()}`; + return t('geomap.utils.get-next-layer-name', 'Layer {{name}}', { name: Date.now() }); }; export function isSegmentVisible( 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/app/plugins/panel/traces/TracesPanel.tsx b/public/app/plugins/panel/traces/TracesPanel.tsx index 5ad8aebecd8..7a9c9394827 100644 --- a/public/app/plugins/panel/traces/TracesPanel.tsx +++ b/public/app/plugins/panel/traces/TracesPanel.tsx @@ -6,7 +6,7 @@ import { Field, LinkModel, PanelProps } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { getDataSourceSrv } from '@grafana/runtime'; import { TraceView } from 'app/features/explore/TraceView/TraceView'; -import { SpanLinkFunc } from 'app/features/explore/TraceView/components'; +import { SpanLinkFunc } from 'app/features/explore/TraceView/components/types/links'; import { transformDataFrames } from 'app/features/explore/TraceView/utils/transform'; import { replaceSearchVariables, SearchProps } from '../../../features/explore/TraceView/useSearch'; diff --git a/public/app/plugins/panel/trend/TrendPanel.tsx b/public/app/plugins/panel/trend/TrendPanel.tsx index ff54529bbe4..d5a7cff51d3 100644 --- a/public/app/plugins/panel/trend/TrendPanel.tsx +++ b/public/app/plugins/panel/trend/TrendPanel.tsx @@ -15,7 +15,7 @@ import { TooltipHoverMode } from '@grafana/ui/internal'; import { XYFieldMatchers } from 'app/core/components/GraphNG/types'; import { preparePlotFrame } from 'app/core/components/GraphNG/utils'; import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries'; -import { findFieldIndex } from 'app/features/dimensions'; +import { findFieldIndex } from 'app/features/dimensions/utils'; import { TimeSeriesTooltip } from '../timeseries/TimeSeriesTooltip'; import { prepareGraphableFields } from '../timeseries/utils'; diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 0978062b808..c6440d0bac1 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -132,6 +132,7 @@ interface GrafanaPromRuleDTOBase extends PromRuleDTOBase { folderUid: string; isPaused: boolean; queriedDatasourceUIDs?: string[]; + provenance?: string; } export interface PromAlertingRuleDTO extends PromRuleDTOBase { 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/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 6a1e7dbaab0..5b405718570 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktivní časování" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Přidat další" }, @@ -1537,8 +1541,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Začínáme", "label-insights": "观察", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Výstrahy" }, "import-to-gma": { @@ -3931,6 +3947,7 @@ "tooltip": "Zavřít" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Při načítání možností došlo k chybě." @@ -8408,6 +8425,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 +8460,6 @@ "label-wrap-lines": "Zalomit řádky" }, "log-details": { - "close": "Zavřít podrobnosti protokolu", "fields": "Pole", "links": "Odkazy", "log-line": "Řádek protokolu", @@ -8457,6 +8485,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 +8640,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 +11516,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 +11537,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..9d81611c46d 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktive Zeitvorgaben" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Weitere hinzufügen" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Erste Schritte", "label-insights": "Insights", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Meldungen" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Schließen" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Beim Laden der Optionen ist ein Fehler aufgetreten." @@ -8356,6 +8373,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 +8408,6 @@ "label-wrap-lines": "Zeilen umbrechen" }, "log-details": { - "close": "Logdetails schließen", "fields": "Felder", "links": "Links", "log-line": "Protokollzeile", @@ -8405,6 +8433,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 +8588,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 +11446,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 +11467,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/en-US/grafana.json b/public/locales/en-US/grafana.json index 8c65d4ed09f..2a3a370bb07 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", @@ -352,6 +373,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" }, @@ -457,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", @@ -546,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": { @@ -560,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...", @@ -844,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", @@ -1380,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" @@ -1525,8 +1559,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": { @@ -1826,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", @@ -2079,9 +2129,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": { @@ -2135,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.", @@ -2202,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": { @@ -2399,8 +2453,6 @@ "title-inspect-alert-rule": "Inspect Alert rule" }, "rule-list": { - "cannot-find-rule-details-for": "Cannot find rule details for UID {{uid}}", - "cannot-load-rule-details-for": "Cannot load rule details for UID {{uid}}", "configure-datasource": "Configure", "draft-new-rule": "Draft a new rule", "ds-error": { @@ -2750,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" } @@ -2776,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" @@ -2903,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" @@ -3059,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", @@ -3193,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 }}\".", @@ -3237,6 +3300,7 @@ "additional-actions-menu": { "label-reset-to-default-values": "Reset to default values" }, + "confirmText-reset": "Reset", "disable": "Disable", "disabling": "Disabling...", "discard": "Discard", @@ -3894,7 +3958,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." @@ -4163,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": { @@ -4326,6 +4391,9 @@ }, "dash-nav": { "on-open-snapshot-original": { + "confirmText": { + "proceed": "Proceed" + }, "title": { "proceed-to-external-site": "Proceed to external site?" } @@ -4540,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": { @@ -4844,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": { @@ -4894,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" }, @@ -4928,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" }, @@ -5124,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", @@ -5146,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", @@ -5186,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", @@ -5262,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": { @@ -5335,6 +5423,9 @@ } }, "config-public-dashboard": { + "confirmText": { + "revoke-public-url": "Revoke public URL" + }, "title": { "revoke-public-url": "Revoke public URL" } @@ -5346,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": { @@ -5364,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": { @@ -5410,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": { @@ -5455,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", @@ -5485,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", @@ -5521,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": { @@ -5548,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?" } @@ -5583,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?" }, @@ -5636,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" }, @@ -5650,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": { @@ -5743,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" @@ -5763,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" @@ -5786,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": { @@ -5802,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.", @@ -5811,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", @@ -5829,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" @@ -5862,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", @@ -6249,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" @@ -6287,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", @@ -6295,9 +6456,15 @@ } }, "scale-dimension-editor": { + "fixed-value-option": { + "label": { + "fixed-value": "Fixed value" + } + }, "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", @@ -6869,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", @@ -6939,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", @@ -7076,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" }, @@ -7144,7 +7316,53 @@ "incomplete-request-error": "Sorry, I was unable to complete your request. Please try again.", "send-custom-feedback": "Send" }, + "geo": { + "get-geometry-field": { + "warning-gazetteer-not-found": "Gazetteer not found", + "warning-no-geometry": "unable to find geometry", + "warning-select-geohash": "Select geohash field", + "warning-select-lat-long": "Select latitude/longitude fields", + "warning-select-lookup": "Select lookup field", + "warning-unable-to-find": "Unable to find location fields" + }, + "location-editor": { + "geohash-field": { + "no-fields-message": "No strings fields found" + }, + "latitude-field": { + "no-fields-message": "No numeric fields found" + }, + "longitude-field": { + "no-fields-message": "No numeric fields found" + }, + "lookup-field": { + "no-fields-message": "No strings fields found" + }, + "name-gazetteer": "Gazetteer", + "name-geohash-field": "Geohash field", + "name-latitude-field": "Latitude field", + "name-location-mode": "Location Mode", + "name-longitude-field": "Longitude field", + "name-lookup-field": "Lookup field" + }, + "location-more-editor": { + "mode-options": { + "description-auto": "Automatically identify location data based on default field names", + "description-coords": "Specify latitude and longitude fields", + "description-geohash": "Specify geohash field", + "description-lookup": "Specify Gazetteer and lookup field", + "label-auto": "Auto", + "label-coords": "Coords", + "label-geohash": "Geohash", + "label-lookup": "Lookup" + } + } + }, "geomap": { + "category-basemap-layer": "Basemap layer", + "category-map-controls": "Map controls", + "category-map-layers": "Map layers", + "category-map-view": "Map view", "coordinates-map-view-editor": { "label-latitude": "Latitude", "label-longitude": "Longitude" @@ -7153,6 +7371,14 @@ "center": "Center:", "zoom": "Zoom:" }, + "description-initial-view": "This location will show when the panel first loads.", + "description-mouse-wheel-zoom": "Enable zoom control via mouse wheel", + "description-share-view": "Use the same view across multiple panels. Note: this may require a dashboard reload.", + "description-show-attribution": "Show the map source attribution info in the lower right", + "description-show-debug": "Show map info", + "description-show-measure": "Show tools for making measurements on the map", + "description-show-scale": "Indicate map scale", + "description-show-zoom": "Show zoom control buttons in the upper left corner", "fit-map-view-editor": { "all-layers-editor-fragment": { "label-layer": "Layer" @@ -7175,6 +7401,14 @@ "geomap-style-rules-editor": { "aria-label-add-geomap-style-rule": "Add geomap style rule" }, + "layer-editor": { + "category-base-layer": "Base layer", + "description-display-tooltip": "Show the tooltip for layer", + "name-data": "Data", + "name-display-tooltip": "Display tooltip", + "name-layer-type": "Layer type", + "name-opacity": "Opacity" + }, "layers-editor": { "label-add-layer": "Add layer", "no-layers": "No layers?" @@ -7185,16 +7419,38 @@ "label-zoom": "Zoom", "use-current-map-settings": "Use current map settings" }, + "markers-layer": { + "description-show-legend": "Show map legend", + "name-show-legend": "Show legend", + "name-styles": "Styles" + }, "markers-legend": { "title-symbol": "Symbol" }, "measure-overlay": { "tooltip-show-measure-tools": "Show measure tools" }, + "name-initial-view": "Initial view", + "name-mouse-wheel-zoom": "Mouse wheel zoom", + "name-share-view": "Share view", + "name-show-attribution": "Show attribution", + "name-show-debug": "Show debug", + "name-show-measure": "Show measure tools", + "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." }, "style-editor": { + "horizontal-align-options": { + "label-center": "Center", + "label-left": "Left", + "label-right": "Right" + }, "label-align": "Align", "label-baseline": "Baseline", "label-color": "Color", @@ -7208,7 +7464,14 @@ "label-symbol-vertical-align": "Symbol vertical align", "label-text-label": "Text label", "label-x-offset": "X offset", - "label-y-offset": "Y 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", + "label-top": "Top" + } }, "style-rule-editor": { "aria-label-comparison-operator": "Comparison operator", @@ -7219,6 +7482,15 @@ "placeholder-feature-property": "Feature property", "placeholder-numeric-value": "Numeric value", "placeholder-value": "value" + }, + "tooltip-options": { + "description-details": "Show popup on hover", + "description-none": "Show contents on click, not hover", + "label-details": "Details", + "label-none": "None" + }, + "utils": { + "get-next-layer-name": "Layer {{name}}" } }, "get-enterprise": { @@ -7468,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" @@ -7650,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", @@ -7817,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": { @@ -8195,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?" }, @@ -8639,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": { @@ -8650,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.", @@ -9348,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": { @@ -9358,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": { @@ -9488,6 +9778,7 @@ } }, "org-picker": { + "noOptionsMessage-no-organizations-found": "No organizations found", "select-placeholder": "Select organization" }, "page": { @@ -9710,6 +10001,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": { @@ -9753,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" } @@ -9808,6 +10099,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" @@ -9841,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.", @@ -10877,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" }, @@ -10922,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", @@ -10948,6 +11252,7 @@ "used-by": "Used by" }, "service-account-profile-row": { + "confirmText-save": "Save", "edit": "Edit" }, "service-account-role-row": { @@ -10961,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", @@ -11356,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)" } }, @@ -11429,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", @@ -11501,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" }, @@ -11826,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" @@ -11868,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", @@ -11881,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" }, @@ -11906,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" @@ -12220,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" }, @@ -12305,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": { @@ -12353,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" }, @@ -12384,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", @@ -12398,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" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index fbdb3450d6e..cd899910ad3 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Tiempos de actividad" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Añadir más" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Empezar", "label-insights": "Información", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alertas" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Cerrar" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Se ha producido un error al cargar las opciones." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..7031be13c8e 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Horaires actifs" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Ajouter plus" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Commencer", "label-insights": "Aperçus", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alerte" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Fermer" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Une erreur s'est produite lors du chargement des options." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..a6462ff2003 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktív időzítések" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Továbbiak hozzáadása" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Kezdés", "label-insights": "Adatok", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Riasztás" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Bezárás" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Hiba történt az opciók betöltése során." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..f35230e46cb 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Pengaturan waktu aktif" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Tambah lagi" }, @@ -1519,8 +1523,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Mulai", "label-insights": "Wawasan", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alerting" }, "import-to-gma": { @@ -3877,6 +3893,7 @@ "tooltip": "Tutup" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Terjadi kesalahan saat memuat opsi." @@ -8330,6 +8347,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 +8382,6 @@ "label-wrap-lines": "Terapkan wrap pada baris" }, "log-details": { - "close": "Tutup detail log", "fields": "Bidang", "links": "Tautan", "log-line": "Baris log", @@ -8379,6 +8407,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 +8562,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 +11411,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 +11432,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..483ac13ebff 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Orari attivi" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Aggiungi altro" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Inizia subito", "label-insights": "Approfondimenti", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Avvisi" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Chiudi" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Si è verificato un errore nel caricamento delle opzioni." @@ -8356,6 +8373,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 +8408,6 @@ "label-wrap-lines": "A capo" }, "log-details": { - "close": "Chiudi i dettagli del registro", "fields": "Campi", "links": "Link", "log-line": "Riga registro", @@ -8405,6 +8433,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 +8588,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 +11446,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 +11467,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..8596e0ca673 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "アクティブタイミング" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "さらに追加" }, @@ -1519,8 +1523,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "開始する", "label-insights": "インサイト", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "アラート" }, "import-to-gma": { @@ -3877,6 +3893,7 @@ "tooltip": "閉じる" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "オプションの読み込み中にエラーが発生しました。" @@ -8330,6 +8347,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 +8382,6 @@ "label-wrap-lines": "行を折り返す" }, "log-details": { - "close": "ログの詳細を閉じる", "fields": "フィールド", "links": "リンク", "log-line": "ログ行", @@ -8379,6 +8407,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 +8562,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 +11411,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 +11432,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..61264e39511 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "활성 타이밍" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "더 추가하기" }, @@ -1519,8 +1523,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "시작하기", "label-insights": "인사이트", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "경고" }, "import-to-gma": { @@ -3877,6 +3893,7 @@ "tooltip": "닫기" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "옵션을 불러오는 중에 오류가 발생했습니다." @@ -8330,6 +8347,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 +8382,6 @@ "label-wrap-lines": "줄 바꿈" }, "log-details": { - "close": "로그 세부 정보 닫기", "fields": "필드", "links": "링크", "log-line": "로그 라인", @@ -8379,6 +8407,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 +8562,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 +11411,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 +11432,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..55f484ff8d8 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Actieve timings" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Meer toevoegen" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Aan de slag", "label-insights": "Inzichten", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alerting" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Sluiten" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Er is een fout opgetreden bij het laden van opties." @@ -8356,6 +8373,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 +8408,6 @@ "label-wrap-lines": "Lijnen omsluiten" }, "log-details": { - "close": "Logboekdetails sluiten", "fields": "Velden", "links": "Links", "log-line": "Logregel", @@ -8405,6 +8433,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 +8588,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 +11446,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 +11467,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..b4d18b3c136 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktywne harmonogramy" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Dodaj więcej" }, @@ -1537,8 +1541,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Rozpocznij", "label-insights": "Analizy", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alerty" }, "import-to-gma": { @@ -3931,6 +3947,7 @@ "tooltip": "Zamknij" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Podczas ładowania opcji wystąpił błąd." @@ -8408,6 +8425,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 +8460,6 @@ "label-wrap-lines": "Zawijaj wiersze" }, "log-details": { - "close": "Zamknij szczegóły dziennika", "fields": "Pola", "links": "Linki", "log-line": "Wiersz logu", @@ -8457,6 +8485,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 +8640,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 +11516,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 +11537,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..32f6cfcf51f 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Ativar cronogramas" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Adicionar mais" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Primeiros passos", "label-insights": "Insights", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alertas" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Fechar" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Ocorreu um erro ao carregar as opções." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..f40f99dd17f 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Tempos ativos" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Adicionar mais" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Começar", "label-insights": "Conhecimentos", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Alerta" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Fechar" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Ocorreu um erro ao carregar as opções." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..b22ba9ec123 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Периоды активности" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Добавить еще" }, @@ -1537,8 +1541,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Начало работы", "label-insights": "Аналитика", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Оповещение" }, "import-to-gma": { @@ -3931,6 +3947,7 @@ "tooltip": "Закрыть" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Ошибка при загрузке параметров." @@ -8408,6 +8425,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 +8460,6 @@ "label-wrap-lines": "Переносить строки" }, "log-details": { - "close": "Закрыть сведения о журнале", "fields": "Поля", "links": "Ссылки", "log-line": "Строка журнала", @@ -8457,6 +8485,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 +8640,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 +11516,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 +11537,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..8bf23d8b471 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktiva tidsinställningar" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Lägg till fler" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Kom igång nu", "label-insights": "Insikter", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Varnar" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Stäng" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Ett fel uppstod vid inläsning av alternativ." @@ -8356,6 +8373,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 +8408,6 @@ "label-wrap-lines": "Radbryt linjer" }, "log-details": { - "close": "Stäng loggdetaljer", "fields": "Fält", "links": "Länkar", "log-line": "Loggrad", @@ -8405,6 +8433,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 +8588,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 +11446,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 +11467,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..657b0842789 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Aktif zamanlamalar" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "Daha fazlasını ekle" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "Başlayalım", "label-insights": "İçgörüler", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "Uyarı sistemi" }, "import-to-gma": { @@ -3895,6 +3911,7 @@ "tooltip": "Kapat" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "Seçenekler yüklenirken bir hata oluştu." @@ -8356,6 +8373,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 +8408,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 +8433,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 +8588,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 +11446,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 +11467,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..ccac589a2e7 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "活跃时间设定" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "添加更多" }, @@ -1519,8 +1523,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "开始操作", "label-insights": "洞察", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "警报" }, "import-to-gma": { @@ -3877,6 +3893,7 @@ "tooltip": "关闭" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "加载选项时发生错误。" @@ -8330,6 +8347,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 +8382,6 @@ "label-wrap-lines": "多行显示" }, "log-details": { - "close": "关闭日志详情", "fields": "字段", "links": "链接", "log-line": "日志行", @@ -8379,6 +8407,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 +8562,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 +11411,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 +11432,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..1562d9aee81 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "啟用時間" }, + "ad": { + "close": "", + "learn-more": "" + }, "add-button": { "add-more": "新增更多" }, @@ -1519,8 +1523,20 @@ } }, "home": { + "irm-card-description": "", + "irm-card-item-1": "", + "irm-card-item-2": "", + "irm-card-item-3": "", + "irm-card-item-4": "", + "irm-card-title": "", "label-get-started": "開始使用", "label-insights": "深入分析", + "synthetic-monitoring-card-description": "", + "synthetic-monitoring-card-item-1": "", + "synthetic-monitoring-card-item-2": "", + "synthetic-monitoring-card-item-3": "", + "synthetic-monitoring-card-item-4": "", + "synthetic-monitoring-card-title": "", "title-alerting": "警報" }, "import-to-gma": { @@ -3877,6 +3893,7 @@ "tooltip": "關閉" }, "cloud-enterprise-feature-badge": "", + "cloud-feature-badge": "", "combobox": { "async": { "error": "載入選項時發生錯誤。" @@ -8330,6 +8347,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 +8382,6 @@ "label-wrap-lines": "換行" }, "log-details": { - "close": "關閉紀錄詳細資訊", "fields": "欄位", "links": "連結", "log-line": "日誌行", @@ -8379,6 +8407,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 +8562,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 +11411,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 +11432,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/yarn.lock b/yarn.lock index 8dea57626c7..896049380f9 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 @@ -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" @@ -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": "*" @@ -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" @@ -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": "*" @@ -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" @@ -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" @@ -3083,8 +3083,8 @@ __metadata: fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" lodash: "npm:4.17.21" - marked: "npm:15.0.12" - marked-mangle: "npm:1.1.10" + marked: "npm:16.0.0" + marked-mangle: "npm:1.1.11" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" ol: "npm:7.4.0" @@ -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 @@ -3270,14 +3270,14 @@ __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" 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" @@ -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" @@ -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" @@ -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 @@ -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 @@ -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 @@ -3657,8 +3657,8 @@ __metadata: dependencies: "@grafana/tsconfig": "npm:^2.0.0" "@types/node": "npm:22.15.0" - msw: "npm:2.10.2" - typescript: "npm:5.7.3" + msw: "npm:2.10.3" + typescript: "npm:5.8.3" languageName: unknown linkType: soft @@ -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" @@ -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" @@ -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" @@ -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 @@ -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 @@ -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" @@ -10317,53 +10310,53 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.22.0" +"@typescript-eslint/eslint-plugin@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.35.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.22.0" - "@typescript-eslint/type-utils": "npm:8.22.0" - "@typescript-eslint/utils": "npm:8.22.0" - "@typescript-eslint/visitor-keys": "npm:8.22.0" + "@typescript-eslint/scope-manager": "npm:8.35.1" + "@typescript-eslint/type-utils": "npm:8.35.1" + "@typescript-eslint/utils": "npm:8.35.1" + "@typescript-eslint/visitor-keys": "npm:8.35.1" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.35.1 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/7211ad95f20a27182e2b55ef50102dfee4a7084c267c4e24cca24f0a28daa0360074a38bb71e407dad6d99db1165096b324b708cf35904b1d4f62fc9d5fd0f98 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/22c4ff7503e4919449b996453ff29ba46e5c0024fac883ac41a313482454f13d55937789f499395dc2a7dba25b1ad47ac5295d60b118f2fa54ca768228514662 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/parser@npm:8.22.0" +"@typescript-eslint/parser@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/parser@npm:8.35.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.22.0" - "@typescript-eslint/types": "npm:8.22.0" - "@typescript-eslint/typescript-estree": "npm:8.22.0" - "@typescript-eslint/visitor-keys": "npm:8.22.0" + "@typescript-eslint/scope-manager": "npm:8.35.1" + "@typescript-eslint/types": "npm:8.35.1" + "@typescript-eslint/typescript-estree": "npm:8.35.1" + "@typescript-eslint/visitor-keys": "npm:8.35.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/6b7fee52345e8a32d8cfea1ac4aeb563cb0c44ba46290686afde1cd541b787fcf61bec0e6960559f544e9ba3b72670a68f8eda860384aebb5744101f0f1a68c9 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/d5e0ecdb945c90fc1fea3f7dd375e424f1a6d49a97627ad24330499d573d45f85348e05a97e3a4643aec5ad9d210073487687872bd573abd79923a12fc46e716 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.33.1": - version: 8.33.1 - resolution: "@typescript-eslint/project-service@npm:8.33.1" +"@typescript-eslint/project-service@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/project-service@npm:8.35.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.33.1" - "@typescript-eslint/types": "npm:^8.33.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.35.1" + "@typescript-eslint/types": "npm:^8.35.1" debug: "npm:^4.3.4" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10/c166e21f305eb798ce135477a1ce78e7fac040b597e0acb2e3ecca84380af2aeb4242944dc201bc611aa14f04dd4af0d123d9cf252f7fe39b0a7e158326f705d + checksum: 10/f8ceb1c6ab7cdf2c7bc334e74d0d1cd86b5e563319c5477987a05f47af433543b281912ae0cdd875561dc2cc4d3ba4ed3bdd8d5bb6dba68bcde4f68a7d0967e7 languageName: node linkType: hard @@ -10377,47 +10370,37 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/scope-manager@npm:8.22.0" +"@typescript-eslint/scope-manager@npm:8.35.1, @typescript-eslint/scope-manager@npm:^8.15.0": + version: 8.35.1 + resolution: "@typescript-eslint/scope-manager@npm:8.35.1" dependencies: - "@typescript-eslint/types": "npm:8.22.0" - "@typescript-eslint/visitor-keys": "npm:8.22.0" - checksum: 10/7fb4bae6d9f8b86a43405b24828cd36ba0751cce4346d86821a4827cded93227f92668044e5e6d802a32096b50cfcaf2ce9ab65322310fa68f5e3819bef70168 + "@typescript-eslint/types": "npm:8.35.1" + "@typescript-eslint/visitor-keys": "npm:8.35.1" + checksum: 10/9124302c969126a50c70f9ccbefec0e5a771563b5518318d56fc6242c5cff61da74e7885832370ccd406a048edc300476b1723ad1845d41bd205879d95fbc6b6 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.33.1, @typescript-eslint/scope-manager@npm:^8.15.0": - version: 8.33.1 - resolution: "@typescript-eslint/scope-manager@npm:8.33.1" - dependencies: - "@typescript-eslint/types": "npm:8.33.1" - "@typescript-eslint/visitor-keys": "npm:8.33.1" - checksum: 10/a513fa79eef269a4f3d031cd1fddbb15c793e3efd6b6b64a6ae85ea0fea03570feffdc071e2bbe9241680d23d8a4ca6d63a998e34c2a24dbe185cd509ee2f9cb - languageName: node - linkType: hard - -"@typescript-eslint/tsconfig-utils@npm:8.33.1, @typescript-eslint/tsconfig-utils@npm:^8.33.1": - version: 8.33.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.33.1" +"@typescript-eslint/tsconfig-utils@npm:8.35.1, @typescript-eslint/tsconfig-utils@npm:^8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.1" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10/9e0e348af4d54b7e42a015bf36e08965c8fa389ac7b179c2ccf2bec5fce4b31e951397394dcbaea6fb0ddf782f4f2f2ee71df108173742f756705f82d653cc9d + checksum: 10/6b6176ec7dbfbe53539bce3e7554f062ff4d220aa5cb5793d52067fe6c196d749e77557dca66f5bf1ee23972e948d5c59461fa3e11da9e34a0a27d9fb7585f5a languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/type-utils@npm:8.22.0" +"@typescript-eslint/type-utils@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/type-utils@npm:8.35.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.22.0" - "@typescript-eslint/utils": "npm:8.22.0" + "@typescript-eslint/typescript-estree": "npm:8.35.1" + "@typescript-eslint/utils": "npm:8.35.1" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/1da2447ce12f09370082daeef88f8922842e39d2a7b0abe3def21442f85bf4250524c60cbb97276d5cd876783b976dcb0ed85aeb8c0b100d83b7f3a59cdfccbf + typescript: ">=4.8.4 <5.9.0" + checksum: 10/728df75bac6960192c18436a8340ed7a0f78b472486279f673e4018d493569f2278b7fcac78c5e0f7ccdb873ead227de6d94bc7aebf5cf046c4d8e53c5569bfd languageName: node linkType: hard @@ -10428,17 +10411,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/types@npm:8.22.0" - checksum: 10/b43ea5b05ed0b43dcee8d2fa98b2c3f79c604780cbd56e6ba7f89e3066798b7169848694f59523fd2003e8fa699ddc97f28b0860a4eb04eea26c96d5ac9346bd - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.33.1, @typescript-eslint/types@npm:^8.33.1, @typescript-eslint/types@npm:^8.9.0": - version: 8.33.1 - resolution: "@typescript-eslint/types@npm:8.33.1" - checksum: 10/27bee01122366438ada7919f91f479fec75e23e2f8033e7e74a12dcfeaa2c28a72e83c545bb676be4369c983b3da5eec101cffb431c52abcc2b6664b48255147 +"@typescript-eslint/types@npm:8.35.1, @typescript-eslint/types@npm:^8.35.1, @typescript-eslint/types@npm:^8.9.0": + version: 8.35.1 + resolution: "@typescript-eslint/types@npm:8.35.1" + checksum: 10/2d5b8f40b2ef0b7d439ee119d2ed12372097c4372aea7ff6d46f92fa743fc60619f8619192fbc0df3833d941be5d9bcb5129b8f6d029716ca86ba42514fbeff9 languageName: node linkType: hard @@ -10460,32 +10436,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.22.0" +"@typescript-eslint/typescript-estree@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.35.1" dependencies: - "@typescript-eslint/types": "npm:8.22.0" - "@typescript-eslint/visitor-keys": "npm:8.22.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.0" - peerDependencies: - typescript: ">=4.8.4 <5.8.0" - checksum: 10/e3c0b191e2a0f55101c3e3333904f3a255d635e4ea0d026981cc25e83b62660a3a8a7993ac4a3d0c8756afb7dc272099eec48fd93e100a2b8467a5b80ef0026c - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.33.1": - version: 8.33.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.33.1" - dependencies: - "@typescript-eslint/project-service": "npm:8.33.1" - "@typescript-eslint/tsconfig-utils": "npm:8.33.1" - "@typescript-eslint/types": "npm:8.33.1" - "@typescript-eslint/visitor-keys": "npm:8.33.1" + "@typescript-eslint/project-service": "npm:8.35.1" + "@typescript-eslint/tsconfig-utils": "npm:8.35.1" + "@typescript-eslint/types": "npm:8.35.1" + "@typescript-eslint/visitor-keys": "npm:8.35.1" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -10494,22 +10452,22 @@ __metadata: ts-api-utils: "npm:^2.1.0" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10/2876b66acd72f8a1093f8671e05015554fea736d492dbc83084f74faff71d40d66a96452396272252f5e36c1dab2082679d10c7b8aca9f3e2f1b6084afeef807 + checksum: 10/b38a891a37e1c8d76bdb3e8039482b723df590bf9d192a5480ec6777a316914542f610a1d9070bc53e0642c511ddc4ee1c3c03ac0e04a5510feefa95307f51b7 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/utils@npm:8.22.0" +"@typescript-eslint/utils@npm:8.35.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.13.0, @typescript-eslint/utils@npm:^8.15.0, @typescript-eslint/utils@npm:^8.33.1, @typescript-eslint/utils@npm:^8.9.0": + version: 8.35.1 + resolution: "@typescript-eslint/utils@npm:8.35.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.22.0" - "@typescript-eslint/types": "npm:8.22.0" - "@typescript-eslint/typescript-estree": "npm:8.22.0" + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.35.1" + "@typescript-eslint/types": "npm:8.35.1" + "@typescript-eslint/typescript-estree": "npm:8.35.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/92a5ae5d79a5988e88fdda8d5e88f73e7b9ce24b339098d72698dba766ded274c24d0e2857bcb799c0aa7a59257e54a273eabdaaab39a5cd20283669201eeb53 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/68388898dc095d7813a18049e782d90ed6500496bb68e3ea5efd7e1de24f37732b133bf88faca835b6219383f406693fdf846e16d3c48e9418388121c89dcf48 languageName: node linkType: hard @@ -10531,21 +10489,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.13.0, @typescript-eslint/utils@npm:^8.15.0, @typescript-eslint/utils@npm:^8.33.1, @typescript-eslint/utils@npm:^8.9.0": - version: 8.33.1 - resolution: "@typescript-eslint/utils@npm:8.33.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.33.1" - "@typescript-eslint/types": "npm:8.33.1" - "@typescript-eslint/typescript-estree": "npm:8.33.1" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/ba76f25b178e2b3280d7aad8becf6a1edd3c57f76f36e6a22d951207282ecc8bfce831ed2f2dd95a70637dcfea2932b3d0a39ac19dd8f9881502738464c6179a - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -10556,23 +10499,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.22.0": - version: 8.22.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.22.0" +"@typescript-eslint/visitor-keys@npm:8.35.1": + version: 8.35.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.35.1" dependencies: - "@typescript-eslint/types": "npm:8.22.0" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/1a172620d46e23362c5d1e1e7c8186856dff6b6f1c2697d67f9aac1b3dfd0de96c2c73487e4deed80fad3bfa5cf74cfed3519221657c6ede602b04ac091525a4 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.33.1": - version: 8.33.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.33.1" - dependencies: - "@typescript-eslint/types": "npm:8.33.1" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/8c1ba7373d8d47d2f2183355db278d4042ecdf268a898c5f4736d84aaa716c6b6faf354247eff8dc1337a7f64168f02e34f3781994d11e9fa8c2b0e35c652c49 + "@typescript-eslint/types": "npm:8.35.1" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/0add7a9c00e7b336797bb7378bd02b3ef31368a8e928afb2dbeec0cc4ab9f6413519e477f5c504d62b38d1dae3791f7ffda36d41b403411608628bff8dd123bd languageName: node linkType: hard @@ -13136,17 +13069,10 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 10/5ded6f61f15f1fa0350e691ccec43a28b12fb8e64c8e94715f2a937bc3722d4c3ed41d6e945c971fc4dcc2a7213a43323beaf2e1c28654af63ba70c9968a8643 - languageName: node - linkType: hard - -"clsx@npm:^2.0.0": - version: 2.0.0 - resolution: "clsx@npm:2.0.0" - checksum: 10/943766d1b02fee3538c871e56638d87f973fbc2d6291ce221215ea436fdecb9be97ad323f411839c2d52c45640c449b1a53fbfe7e8b3d529b4e263308b630c9a +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10/cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 languageName: node linkType: hard @@ -15141,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 @@ -16358,10 +16284,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 +"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 languageName: node linkType: hard @@ -17665,10 +17591,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 +17805,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 @@ -18178,7 +18104,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:*" @@ -18257,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" @@ -18302,8 +18227,8 @@ __metadata: "@types/webpack-assets-manifest": "npm:^5" "@types/webpack-env": "npm:^1.18.4" "@types/yargs": "npm:17.0.33" - "@typescript-eslint/eslint-plugin": "npm:8.22.0" - "@typescript-eslint/parser": "npm:8.22.0" + "@typescript-eslint/eslint-plugin": "npm:8.35.1" + "@typescript-eslint/parser": "npm:8.35.1" "@visx/event": "npm:3.12.0" "@visx/gradient": "npm:3.12.0" "@visx/group": "npm:3.12.0" @@ -18341,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" @@ -18371,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" @@ -18402,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" @@ -18412,7 +18337,7 @@ __metadata: moment-timezone: "npm:0.5.47" monaco-editor: "npm:0.34.1" moveable: "npm:0.53.0" - msw: "npm:2.10.2" + msw: "npm:2.10.3" mutationobserver-shim: "npm:0.3.7" nanoid: "npm:^5.0.9" node-forge: "npm:^1.3.1" @@ -18438,7 +18363,7 @@ __metadata: react: "npm:18.3.1" react-diff-viewer-continued: "npm:^3.4.0" react-dom: "npm:18.3.1" - react-draggable: "npm:4.4.6" + react-draggable: "npm:4.5.0" react-dropzone: "npm:^14.2.3" react-grid-layout: "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch" react-highlight-words: "npm:0.21.0" @@ -18484,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.25.2" + 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" @@ -18496,7 +18421,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" @@ -19297,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: @@ -19311,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" @@ -19361,17 +19300,17 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.0.4, ignore@npm:^5.1.1, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": +"ignore@npm:^5.0.4, ignore@npm:^5.1.1, ignore@npm:^5.2.0, ignore@npm:^5.2.4": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard -"ignore@npm:^7.0.3": - version: 7.0.3 - resolution: "ignore@npm:7.0.3" - checksum: 10/ce5e812af3acd6607a3fe0a9f9b5f01d53f009a5ace8cbf5b6491d05a481b55d65186e6a7eaa13126e93f15276bcf3d1e8d6ff3ce5549c312f9bb313fff33365 +"ignore@npm:^7.0.0, ignore@npm:^7.0.3": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 languageName: node linkType: hard @@ -22458,12 +22397,12 @@ __metadata: languageName: node linkType: hard -"marked-mangle@npm:1.1.10": - version: 1.1.10 - resolution: "marked-mangle@npm:1.1.10" +"marked-mangle@npm:1.1.11": + version: 1.1.11 + resolution: "marked-mangle@npm:1.1.11" peerDependencies: - marked: ">=4 <16" - checksum: 10/76ca4736e7f3839a5866329bfa2c274fb5699ef4962ed32b16b10b76c0aeb3ca5f67461041c9d7b8ab56900e84cb377aaa596a097f7327baa89342de442a138b + marked: ">=4 <17" + checksum: 10/e2872cbe62dc60347eaef2494049a3fb65bcef3f1097407dd8e3f946856085d3ce05b516ba0d2307cbf91019fd38ad25704200c9dc601a0da29d64c76c803027 languageName: node linkType: hard @@ -22484,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 @@ -23233,9 +23172,9 @@ __metadata: languageName: node linkType: hard -"msw@npm:2.10.2, msw@npm:^2.10.2": - version: 2.10.2 - resolution: "msw@npm:2.10.2" +"msw@npm:2.10.3, msw@npm:^2.10.2": + version: 2.10.3 + resolution: "msw@npm:2.10.3" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -23262,7 +23201,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/bc90bc34a0b9e978e662f33fa630a0de66c4b6eff3a92b41efa08bf67d79b43e4a961a17b0d274580393c912f4ac6226c76a8bc33148799b1ee43477df712c26 + checksum: 10/ac51d6232d3b0a9b711195a2c3f22035683f9082ee83d915e33f35dbf3a91c4381dab70f9de5358113adaad8ecbe1c58a584377f4ba988e2903fa9770a294b42 languageName: node linkType: hard @@ -26749,16 +26688,16 @@ __metadata: languageName: node linkType: hard -"react-draggable@npm:4.4.6, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5": - version: 4.4.6 - resolution: "react-draggable@npm:4.4.6" +"react-draggable@npm:4.5.0, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5": + version: 4.5.0 + resolution: "react-draggable@npm:4.5.0" dependencies: - clsx: "npm:^1.1.1" + clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" peerDependencies: react: ">= 16.3.0" react-dom: ">= 16.3.0" - checksum: 10/51b9ac7f913797fc1cebc30ae383f346883033c45eb91e9b0b92e9ebd224bb1545b4ae2391825b649b798cc711a38351a5f41be24d949c64c6703ebc24eba661 + checksum: 10/fd4080a082fad199103c6978c8a78c80154a0205dbd14ca2c7bb75634132a6afd83a3e4669f2d8fe54efe68569ef24a992439183289d83e71030b0a1791d3476 languageName: node linkType: hard @@ -26847,11 +26786,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.49.2": - version: 7.58.1 - resolution: "react-hook-form@npm:7.58.1" + version: 7.60.0 + resolution: "react-hook-form@npm:7.60.0" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/94b8af3976f21241adf3ca67d08929c8e66139c45f78632f5e563b83956000c5c30025dfacbe74602670d1f4c13dea5f3d2d8c03344698d193ae0214455196ad + checksum: 10/cc86ef4029f7b06f82dff919c8933156683b70669a6306d2471706443192b8916afaa07bc64452b590ecb18ea94f3d156f3fb38d59ada3462f5b108e89e186c9 languageName: node linkType: hard @@ -26865,8 +26804,8 @@ __metadata: linkType: hard "react-i18next@npm:^15.0.0": - version: 15.5.3 - resolution: "react-i18next@npm:15.5.3" + version: 15.6.0 + resolution: "react-i18next@npm:15.6.0" dependencies: "@babel/runtime": "npm:^7.27.6" html-parse-stringify: "npm:^3.0.1" @@ -26881,7 +26820,7 @@ __metadata: optional: true typescript: optional: true - checksum: 10/7e93c2fe408c0a56ce57be4ff87d8cbf58ea181380d4b366f73142e45ddc10b3a435feb76447ad3c5e5320e7fc10d61596113405e89b97bc23e140613f5b19a4 + checksum: 10/d8549c364eb32ea078be0e87df35ee23b8c9d6a30af4fe2a41099c12f62b5ce4268f9997132a9b5abcb4556e30fb37f4ba7f5548429333548b5b9856627c6a50 languageName: node linkType: hard @@ -30317,9 +30256,9 @@ __metadata: languageName: node linkType: hard -"swagger-ui-react@npm:5.25.2": - version: 5.25.2 - resolution: "swagger-ui-react@npm:5.25.2" +"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" @@ -30357,7 +30296,7 @@ __metadata: peerDependencies: react: ">=16.8.0 <19" react-dom: ">=16.8.0 <19" - checksum: 10/0b145421d757611e28219aedd763a7a76d8e40cec5147f361aa3d90273a0874d52907e3ec7514d6431954f2c179b29a3199ef2e0fdf86b41e1a5eb5205b5eeee + checksum: 10/4ca99974df01fda16adcfd16458ee8703f1f1df5112e80dd2373bcaca0404f188ce1b96ae8ac4838311f2ec5b6073220966ba4089cc67ea378a84990976c6d40 languageName: node linkType: hard @@ -30959,7 +30898,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.0, ts-api-utils@npm:^2.1.0": +"ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" peerDependencies: @@ -31389,13 +31328,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 +31368,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