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.
+
+
+
## 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>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}
+ {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 (
<>
-
}
- 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`] = `
| | | | |