diff --git a/.betterer.results b/.betterer.results index aaa293b9b8b..31648327a82 100644 --- a/.betterer.results +++ b/.betterer.results @@ -697,9 +697,6 @@ exports[`better eslint`] = { "packages/grafana-runtime/src/analytics/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "packages/grafana-runtime/src/components/DataSourcePicker.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "packages/grafana-runtime/src/config.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -785,12 +782,6 @@ exports[`better eslint`] = { "packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], - "packages/grafana-ui/src/components/Alert/Toast.story.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -1151,6 +1142,15 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "5"], [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], + "public/app/core/actions/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`updateNavIndex\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`updateConfigurationSubtitle\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`notifyApp\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`hideAppNotification\`)", "3"] + ], + "public/app/core/components/AccessControl/index.ts:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -1163,9 +1163,6 @@ exports[`better eslint`] = { "public/app/core/components/ForgottenPassword/ChangePassword.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/core/components/ForgottenPassword/ForgottenPassword.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/core/components/GraphNG/GraphNG.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -1178,6 +1175,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "8"], [0, 0, 0, "Do not use any type assertions.", "9"] ], + "public/app/core/components/LocalStorageValueProvider/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./LocalStorageValueProvider\`)", "0"] + ], "public/app/core/components/Login/LoginServiceButtons.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -1233,12 +1233,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "5"], [0, 0, 0, "Styles should be written using objects.", "6"] ], - "public/app/core/components/RolePicker/RolePickerMenu.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], - "public/app/core/components/RolePicker/RolePickerSubMenu.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/core/components/RolePicker/ValueContainer.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -1288,6 +1282,19 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/core/config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] + ], + "public/app/core/core.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`profiler\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`appEvents\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`colors\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`assignModelProperties\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`contextSrv\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`JsonExplorer\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`TimeSeries\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`updateLegendValues\`)", "7"] + ], "public/app/core/navigation/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1387,6 +1394,11 @@ exports[`better eslint`] = { "public/app/core/utils/richHistory.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/core/utils/richHistory.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`RichHistorySearchFilters\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`RichHistorySettings\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`SortOrder\`)", "2"] + ], "public/app/core/utils/ticks.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -1702,6 +1714,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPointMenu.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`app/features/explore/extensions/ToolbarExtensionPointMenu\`)", "0"] + ], "public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -2334,9 +2349,20 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"] ], + "public/app/features/auth-config/index.ts:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/features/auth-config/utils/data.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/browse-dashboards/api/browseDashboardsAPI.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`@reduxjs/toolkit/query/react\`)", "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/canvas/element.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -2344,6 +2370,12 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], + "public/app/features/canvas/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 re-export imported variable (\`./frame\`)", "2"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"] + ], "public/app/features/canvas/runtime/element.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -2364,14 +2396,40 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"] ], + "public/app/features/canvas/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`Placement\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`Constraint\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`HorizontalConstraint\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`VerticalConstraint\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`BackgroundImageSize\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`LineConfig\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`BackgroundConfig\`)", "6"] + ], "public/app/features/connections/components/ConnectionsRedirectNotice/ConnectionsRedirectNotice.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], + "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 (\`./DataSourceDetailsPage\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./DataSourcesListPage\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./DataSourceDashboardsPage\`)", "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/index.tsx:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/features/connections/tabs/ConnectData/CategoryHeader/CategoryHeader.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], + "public/app/features/connections/tabs/ConnectData/CategoryHeader/index.tsx:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/features/connections/tabs/ConnectData/NoAccessModal/NoAccessModal.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -2383,6 +2441,15 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "7"], [0, 0, 0, "Styles should be written using objects.", "8"] ], + "public/app/features/connections/tabs/ConnectData/NoAccessModal/index.tsx:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], + "public/app/features/connections/tabs/ConnectData/Search/index.tsx:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], + "public/app/features/connections/tabs/ConnectData/index.tsx:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/features/correlations/CorrelationsPage.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -2404,6 +2471,10 @@ exports[`better eslint`] = { "public/app/features/correlations/Forms/CorrelationFormNavigation.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "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/dashboard-scene/inspect/HelpWizard/HelpWizard.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -2437,6 +2508,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"] ], + "public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], "public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -2455,16 +2529,19 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"], [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"] + [0, 0, 0, "Unexpected any. Specify a different type.", "11"], + [0, 0, 0, "Unexpected any. Specify a different type.", "12"] ], "public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.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, "Unexpected any. Specify a different type.", "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.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"], + [0, 0, 0, "Do not use any type assertions.", "8"] ], "public/app/features/dashboard-scene/settings/VersionsEditView.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] @@ -2475,6 +2552,10 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.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/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/DashboardLinkList.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -2487,6 +2568,13 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/settings/variables/utils.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "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 (\`./VersionHistoryTable\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryHeader\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "4"] + ], "public/app/features/dashboard-scene/sharing/ShareExportTab.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -2496,6 +2584,9 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/dashboard/components/AddLibraryPanelWidget/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./AddLibraryPanelWidget\`)", "0"] + ], "public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -2513,6 +2604,10 @@ exports[`better eslint`] = { [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], + "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"], @@ -2533,9 +2628,15 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"] ], + "public/app/features/dashboard/components/DashExportModal/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./DashboardExporter\`)", "0"] + ], "public/app/features/dashboard/components/DashNav/DashNavButton.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/features/dashboard/components/DashNav/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`DashNav\`)", "0"] + ], "public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -2562,12 +2663,18 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/dashboard/components/DashboardRow/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./DashboardRow\`)", "0"] + ], "public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], "public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], + "public/app/features/dashboard/components/DashboardSettings/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./DashboardSettings\`)", "0"] + ], "public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -2580,6 +2687,10 @@ 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, "Styles should be written using objects.", "1"], @@ -2763,6 +2874,10 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/features/dashboard/components/ShareModal/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./ShareModal\`)", "0"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] + ], "public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -2943,6 +3058,10 @@ exports[`better eslint`] = { "public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/dashboard/state/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./DashboardModel\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./PanelModel\`)", "1"] + ], "public/app/features/dashboard/state/initDashboard.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -2959,6 +3078,14 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "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/__mocks__/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/BasicSettings.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -3012,6 +3139,15 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "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"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"] + ], "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"] @@ -3098,6 +3234,24 @@ exports[`better eslint`] = { "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "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"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3313,6 +3467,9 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "7"], [0, 0, 0, "Styles should be written using objects.", "8"] ], + "public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"] + ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -3450,6 +3607,9 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"] ], + "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./TimelineHeaderRow\`)", "0"] + ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -3458,6 +3618,9 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`../utils/date\`)", "0"] + ], "public/app/features/explore/TraceView/components/common/BreakableText.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -3479,6 +3642,21 @@ 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 (\`./TraceTimelineViewer\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./settings/SpanBarSettings\`)", "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 re-export imported variable (\`./TraceTimelineViewer/SpanDetail/DetailState\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`./model/transform-trace-data\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`./utils/filter-spans\`)", "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"], + [0, 0, 0, "Do not re-export imported variable (\`PathElem\`)", "1"] + ], "public/app/features/explore/TraceView/components/model/link-patterns.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3500,9 +3678,24 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], + "public/app/features/explore/TraceView/components/types/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`../settings/SpanBarSettings\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./TTraceTimeline\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./TNil\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./links\`)", "4"] + ], "public/app/features/explore/TraceView/components/types/trace.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "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 use export all (\`export * from ...\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./EUpdateTypes\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./DraggableManager\`)", "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"] @@ -3510,6 +3703,9 @@ exports[`better eslint`] = { "public/app/features/explore/extensions/ConfirmNavigationModal.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/explore/hooks/useStateSync/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./external.utils\`)", "0"] + ], "public/app/features/explore/spec/helper/setup.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3796,6 +3992,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], + "public/app/features/migrate-to-cloud/api/index.ts:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] + ], "public/app/features/org/OrgDetailsPage.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -3834,18 +4033,34 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"] ], + "public/app/features/plugins/admin/__mocks__/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./remotePlugin.mock\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./localPlugin.mock\`)", "1"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] + ], "public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/features/plugins/admin/components/Badges/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./PluginAngularBadge\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "5"] + ], "public/app/features/plugins/admin/components/Badges/sharedStyles.ts:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], "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/HorizontalGroup.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -3859,6 +4074,10 @@ exports[`better eslint`] = { [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], + "public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsButton\`)", "1"] + ], "public/app/features/plugins/admin/components/PluginActions.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, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], @@ -4039,9 +4258,8 @@ exports[`better eslint`] = { [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/search/page/components/ActionRow.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, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/search/page/components/SearchResultsTable.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -4065,6 +4283,10 @@ 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/features/search/service/index.ts:5381": [ + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./searcher\`)", "1"] + ], "public/app/features/search/service/sql.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -4235,6 +4457,10 @@ exports[`better eslint`] = { "public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`CalculateFieldTransformerEditor\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`calculateFieldTransformRegistryItem\`)", "1"] + ], "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -4429,6 +4655,9 @@ 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/features/variables/pickers/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./OptionsPicker/OptionsPicker\`)", "0"] + ], "public/app/features/variables/pickers/shared/VariableLink.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -4520,11 +4749,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], "public/app/features/variables/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not re-export imported variable (\`@grafana/data\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`VariableHide\`)", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"] + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], "public/app/features/variables/utils.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -4547,9 +4778,19 @@ 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/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/azure_monitor/azure_monitor_datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./ArgQueryEditor\`)", "0"] + ], + "public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./LogsQueryEditor\`)", "0"] + ], "public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -4559,12 +4800,37 @@ exports[`better eslint`] = { "public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/plugins/datasource/azuremonitor/components/QueryEditor/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./QueryEditor\`)", "0"] + ], + "public/app/plugins/datasource/azuremonitor/components/ResourceField/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./ResourceField\`)", "0"] + ], + "public/app/plugins/datasource/azuremonitor/components/ResourcePicker/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./ResourcePicker\`)", "0"] + ], "public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], + "public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./TracesQueryEditor\`)", "0"] + ], "public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx:5381": [ [0, 0, 0, "* import is invalid because \'Layout,HorizontalGroup,VerticalGroup\' from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "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 (\`AzureQueryType\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "2"] + ], + "public/app/plugins/datasource/azuremonitor/types/templateVariables.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"] + ], "public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -4594,6 +4860,23 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/plugins/datasource/cloud-monitoring/components/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./Project\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./GroupBy\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./Alignment\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./LabelFilter\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./AnnotationsHelp\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`./AlignmentFunction\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`./AliasBy\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`./Aggregation\`)", "7"], + [0, 0, 0, "Do not re-export imported variable (\`./MetricQueryEditor\`)", "8"], + [0, 0, 0, "Do not re-export imported variable (\`./SLOQueryEditor\`)", "9"], + [0, 0, 0, "Do not re-export imported variable (\`./MQLQueryEditor\`)", "10"], + [0, 0, 0, "Do not re-export imported variable (\`./Fields\`)", "11"], + [0, 0, 0, "Do not re-export imported variable (\`./VisualMetricQueryEditor\`)", "12"], + [0, 0, 0, "Do not re-export imported variable (\`./PeriodSelect\`)", "13"], + [0, 0, 0, "Do not re-export imported variable (\`./Preprocessor\`)", "14"] + ], "public/app/plugins/datasource/cloud-monitoring/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -4604,9 +4887,46 @@ 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/types/query.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`QueryType\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "2"] + ], "public/app/plugins/datasource/cloud-monitoring/types/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/cloud-monitoring/webpack.config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] + ], + "public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./empty\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./whitespaceQuery\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./commentOnlyQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`./filterQuery\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`./newCommandQuery\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`./sortQuery\`)", "7"] + ], + "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 (\`./singleLineFullQuery\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineTwoQueries\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./multiLineIncompleteQueryWithoutNamespace\`)", "4"] + ], + "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__/metric-math-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./afterFunctionQuery\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./secondArgQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./secondArgAfterSearchQuery\`)", "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/components/CheatSheet/LogsCheatSheet.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -4623,12 +4943,24 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx:5381": [ [0, 0, 0, "* import is invalid because \'Layout,HorizontalGroup,VerticalGroup\' from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], + "public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./SQLBuilderEditor\`)", "0"] + ], + "public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./MetricsQueryEditor/SQLCodeEditor\`)", "0"] + ], "public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./MetricStatEditor\`)", "0"] + ], "public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/cloudwatch/expressions.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./dataquery.gen\`)", "0"] + ], "public/app/plugins/datasource/cloudwatch/guards.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -4638,13 +4970,14 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "public/app/plugins/datasource/cloudwatch/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], "public/app/plugins/datasource/cloudwatch/utils/datalinks.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -4653,6 +4986,11 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/dashboard/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./runSharedRequest\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./DashboardQueryEditor\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./types\`)", "2"] + ], "public/app/plugins/datasource/dashboard/runSharedRequest.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -4808,6 +5146,10 @@ 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 use export all (\`export * from ...\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./dataquery.gen\`)", "1"] + ], "public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -4837,6 +5179,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"] ], + "public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./StreamingClientEditor\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./RandomWalkEditor\`)", "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"] @@ -4844,6 +5190,9 @@ exports[`better eslint`] = { "public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/grafana-testdata-datasource/webpack.config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] + ], "public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -4860,9 +5209,8 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "3"] ], "public/app/plugins/datasource/grafana/components/TimeRegionEditor.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, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/plugins/datasource/grafana/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -5144,6 +5492,9 @@ exports[`better eslint`] = { "public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"] + ], "public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -5251,6 +5602,14 @@ exports[`better eslint`] = { "public/app/plugins/datasource/loki/streaming.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/loki/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`LokiQueryDirection\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`LokiQueryType\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`SupportingQueryType\`)", "2"] + ], + "public/app/plugins/datasource/mixed/module.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`MixedDatasource\`)", "0"] + ], "public/app/plugins/datasource/opentsdb/components/OpenTsdbQueryEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -5312,39 +5671,8 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], - "public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], - "public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/prometheus/components/PromQueryField.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.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, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"] - ], - "public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] + "public/app/plugins/datasource/parca/webpack.config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] ], "public/app/plugins/datasource/prometheus/configuration/AzureCredentialsConfig.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -5367,165 +5695,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "17"], [0, 0, 0, "Unexpected any. Specify a different type.", "18"] ], - "public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"], - [0, 0, 0, "Styles should be written using objects.", "13"], - [0, 0, 0, "Styles should be written using objects.", "14"], - [0, 0, 0, "Styles should be written using objects.", "15"] - ], - "public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], - "public/app/plugins/datasource/prometheus/datasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"] - ], - "public/app/plugins/datasource/prometheus/language_provider.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "public/app/plugins/datasource/prometheus/language_utils.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/QueryPattern.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/QueryPatternsModal.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilterItem.tsx: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/plugins/datasource/prometheus/querybuilder/components/LabelFilters.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/AdditionalSettings.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/ResultsTable.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/styles.ts:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"], - [0, 0, 0, "Styles should be written using objects.", "13"], - [0, 0, 0, "Styles should be written using objects.", "14"], - [0, 0, 0, "Styles should be written using objects.", "15"], - [0, 0, 0, "Styles should be written using objects.", "16"], - [0, 0, 0, "Styles should be written using objects.", "17"], - [0, 0, 0, "Styles should be written using objects.", "18"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx: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, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Do not use any type assertions.", "7"], - [0, 0, 0, "Do not use any type assertions.", "8"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx: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/prometheus/querybuilder/shared/QueryBuilderHints.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "public/app/plugins/datasource/prometheus/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], "public/app/plugins/datasource/tempo/QueryField.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -5578,6 +5747,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], + "public/app/plugins/datasource/tempo/webpack.config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] + ], "public/app/plugins/datasource/zipkin/ConfigEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -5593,6 +5765,9 @@ exports[`better eslint`] = { "public/app/plugins/datasource/zipkin/utils/transforms.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/datasource/zipkin/webpack.config.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] + ], "public/app/plugins/panel/alertlist/AlertInstances.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -5649,6 +5824,16 @@ 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/plugins/panel/candlestick/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`Options\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`CandlestickColors\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`defaultCandlestickColors\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`CandleStyle\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`ColorStrategy\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`VizDisplayMode\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`CandlestickFieldMap\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`FieldConfig\`)", "7"] + ], "public/app/plugins/panel/canvas/components/CanvasTooltip.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -5759,6 +5944,9 @@ 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/panel/geomap/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] + ], "public/app/plugins/panel/geomap/utils/layers.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -5795,7 +5983,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/plugins/panel/heatmap/types.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/plugins/panel/heatmap/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -5836,6 +6025,9 @@ exports[`better eslint`] = { "public/app/plugins/panel/logs/LogsPanel.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/plugins/panel/logs/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] + ], "public/app/plugins/panel/nodeGraph/Edge.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -5890,10 +6082,16 @@ exports[`better eslint`] = { "public/app/plugins/panel/nodeGraph/editor/ArcOptionsEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], + "public/app/plugins/panel/nodeGraph/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./NodeGraph\`)", "0"] + ], "public/app/plugins/panel/nodeGraph/layout.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/panel/nodeGraph/types.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] + ], "public/app/plugins/panel/nodeGraph/useContextMenu.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -6052,6 +6250,9 @@ exports[`better eslint`] = { "public/app/plugins/panel/xychart/v2/utils.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/sdk.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`loadPluginCss\`)", "0"] + ], "public/app/store/configureStore.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -6059,6 +6260,9 @@ 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/types/acl.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`OrgRole\`)", "0"] + ], "public/app/types/alerting.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -6093,6 +6297,30 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "11"], [0, 0, 0, "Unexpected any. Specify a different type.", "12"] ], + "public/app/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"], + [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"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "8"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "9"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "10"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "11"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "12"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "13"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "14"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "15"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "16"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "17"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "18"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "19"], + [0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"], + [0, 0, 0, "Do not re-export imported variable (\`CoreEvents\`)", "21"] + ], "public/app/types/jquery/jquery.d.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -6108,6 +6336,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/types/teams.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`TeamDTO\`)", "0"] + ], "public/app/types/unified-alerting-dto.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -6732,25 +6963,6 @@ exports[`no gf-form usage`] = { [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], - "public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], - "public/app/plugins/datasource/prometheus/components/PromQueryField.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], - "public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], "public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx:5381": [ [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], @@ -6777,41 +6989,6 @@ exports[`no gf-form usage`] = { [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], - "public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], - "public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], - "public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], "public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilter.tsx:5381": [ [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], diff --git a/.betterer.results.json b/.betterer.results.json index d5db475d6b1..5b11f01cfca 100644 --- a/.betterer.results.json +++ b/.betterer.results.json @@ -1,8166 +1,7427 @@ { - "better eslint": { - "/e2e/utils/support/types.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/dataframe/ArrayDataFrame.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/dataframe/CircularDataFrame.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/dataframe/DataFrameJSON.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/dataframe/DataFrameView.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/dataframe/MutableDataFrame.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/packages/grafana-data/src/dataframe/StreamingDataFrame.ts": [ - { - "message": "Do not use any type assertions.", - "count": 6 - } - ], - "/packages/grafana-data/src/dataframe/dimensions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/dataframe/processDataFrame.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/dataframe/processDataFrame.ts": [ - { - "message": "Do not use any type assertions.", - "count": 18 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/packages/grafana-data/src/datetime/datemath.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/datetime/durationutil.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/datetime/formatter.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/datetime/moment_wrapper.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 8 - } - ], - "/packages/grafana-data/src/datetime/parser.ts": [ - { - "message": "Do not use any type assertions.", - "count": 9 - } - ], - "/packages/grafana-data/src/datetime/rangeutil.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/events/EventBus.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/events/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/packages/grafana-data/src/field/displayProcessor.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/packages/grafana-data/src/field/overrides/processors.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/packages/grafana-data/src/geo/layer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/panel/PanelPlugin.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/panel/registryFactories.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/themes/colorManipulator.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/themes/createColors.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/transformations/standardTransformersRegistry.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/transformations/transformDataFrame.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/transformations/transformers/reduce.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/transformations/transformers/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/packages/grafana-data/src/types/ScopedVars.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/annotations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/packages/grafana-data/src/types/app.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/config.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/dashboard.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/types/data.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/packages/grafana-data/src/types/dataFrame.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/packages/grafana-data/src/types/dataLink.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/packages/grafana-data/src/types/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 25 - } - ], - "/packages/grafana-data/src/types/explore.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/fieldOverrides.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 9 - } - ], - "/packages/grafana-data/src/types/flot.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/graph.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/types/legacyEvents.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/live.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/options.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/panel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 14 - } - ], - "/packages/grafana-data/src/types/plugin.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/select.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/templateVars.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/types/trace.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/transformations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/packages/grafana-data/src/types/variables.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/types/vector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/utils/OptionsUIBuilders.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 27 - } - ], - "/packages/grafana-data/src/utils/Registry.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/utils/arrayUtils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/utils/csv.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-data/src/utils/dataLinks.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/utils/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/utils/flotPairs.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/utils/location.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-data/src/utils/url.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/utils/valueMappings.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/vector/AppendedVectors.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-data/src/vector/ArrayVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-data/src/vector/CircularVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/vector/ConstantVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/vector/FormattedVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/src/vector/FunctionalVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 10 - } - ], - "/packages/grafana-data/src/vector/SortedVector.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-data/test/__mocks__/pluginMocks.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/packages/grafana-flamegraph/src/FlameGraphHeader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-o11y-ds-frontend/src/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/components/PromExemplarField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-prometheus/src/components/PromQueryField.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/components/PromQueryField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 12 - } - ], - "/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 16 - } - ], - "/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - } - ], - "/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/language_provider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/packages/grafana-prometheus/src/language_utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/metric_find_query.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/packages/grafana-prometheus/src/query_hints.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-prometheus/src/querybuilder/binaryScalarOperations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 19 - } - ], - "/packages/grafana-prometheus/src/querybuilder/operationUtils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-prometheus/src/querybuilder/shared/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-prometheus/src/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-runtime/src/analytics/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-runtime/src/config.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-runtime/src/services/AngularLoader.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-runtime/src/services/EchoSrv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/packages/grafana-runtime/src/services/LocationService.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-runtime/src/services/backendSrv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - } - ], - "/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-runtime/src/utils/queryResponse.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-schema/src/veneer/common.types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-schema/src/veneer/dashboard.types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 5 - } - ], - "/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/MatchersUI/FieldValueMatcher.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Modal/ModalsContext.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/PanelChrome/index.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Select/SelectBase.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/Select/ValueContainer.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Select/resetSelectStyles.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/packages/grafana-ui/src/components/Select/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 15 - }, - { - "message": "Do not use any type assertions.", - "count": 5 - } - ], - "/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/Table/Filter.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/FilterPopup.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/FooterRow.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/HeaderRow.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/Table.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/TableCell.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 3 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/reducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Table/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Table/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/packages/grafana-ui/src/components/Tags/Tag.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/VizLegend/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/uPlot/Plot.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/uPlot/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/uPlot/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/graveyard/Graph/GraphContextMenu.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/graveyard/Graph/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/graveyard/GraphNG/GraphNG.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 6 - } - ], - "/packages/grafana-ui/src/graveyard/GraphNG/hooks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/graveyard/GraphNG/nullInsertThreshold.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-ui/src/graveyard/GraphNG/nullToUndefThreshold.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/options/builder/axis.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/options/builder/hideSeries.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/slate-plugins/braces.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/slate-plugins/suggestions.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/themes/ThemeContext.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/packages/grafana-ui/src/themes/stylesFactory.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/types/forms.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/types/jquery.d.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 9 - } - ], - "/packages/grafana-ui/src/types/mdx.d.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/types/react-table-config.d.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/utils/debug.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/packages/grafana-ui/src/utils/dom.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-ui/src/utils/logger.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/packages/grafana-ui/src/utils/storybook/withTheme.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/packages/grafana-ui/src/utils/useAsyncDependency.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/plugins-bundled/internal/input-datasource/src/InputDatasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/TableModel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/core/components/DynamicImports/SafeDynamicImport.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/components/GraphNG/GraphNG.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 6 - } - ], - "/public/app/core/components/GraphNG/hooks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/core/components/NestedFolderPicker/Trigger.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/core/components/OptionsUI/registry.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 11 - } - ], - "/public/app/core/components/PageHeader/PageHeader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/core/components/RolePicker/RolePickerInput.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/core/components/RolePicker/ValueContainer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/core/components/TagFilter/TagFilter.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/core/components/TagFilter/TagOption.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/core/components/TimeSeries/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/core/components/Upgrade/ProBadge.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/core/components/Upgrade/UpgradeBox.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 18 - } - ], - "/public/app/core/components/connectWithCleanUp.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/core/navigation/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/reducers/root.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/services/ResponseQueue.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/services/backend_srv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 9 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/core/services/context_srv.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/core/specs/backend_srv.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/time_series2.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 19 - } - ], - "/public/app/core/utils/ConfigProvider.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/core/utils/connectWithReduxStore.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/core/utils/deferred.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/utils/fetch.ts": [ - { - "message": "Do not use any type assertions.", - "count": 5 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/utils/flatten.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/utils/object.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/core/utils/richHistory.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/core/utils/ticks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/admin/LicenseChrome.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/admin/OrgRolePicker.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/admin/UpgradePage.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/admin/UserOrgs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 17 - } - ], - "/public/app/features/admin/UserPermissions.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/admin/UserProfile.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/AlertTab.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/AlertTabCtrl.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 33 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/EditNotificationChannelPage.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/StateHistory.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/TestRuleResult.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/alerting/components/NotificationChannelForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/components/NotificationChannelOptions.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/components/OptionElement.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/state/ThresholdMapper.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/state/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/alerting/state/alertDef.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/state/query_part.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 11 - } - ], - "/public/app/features/alerting/state/reducers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/AlertGroups.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/AlertWarning.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/AlertsFolderView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 19 - } - ], - "/public/app/features/alerting/unified/NotificationPolicies.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/PanelAlertTabContent.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/RedirectToRuleViewer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/RuleList.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/AlertLabel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/AlertLabels.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/AlertManagerPicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/AlertStateDot.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/Authorize.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/DetailsField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/DynamicTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/alerting/unified/components/EmptyArea.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/Expression.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/HoverCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/Label.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/MetaText.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/Spacer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/StateColoredText.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/StateTag.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/alerting/unified/components/Tokenize.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/Well.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/admin/AlertmanagerConfigSelector.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/export/FileExportPreview.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/expressions/Expression.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 25 - } - ], - "/public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeRange.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/notification-policies/Matchers.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/notification-policies/Policy.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/notification-policies/PromDurationDocs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/notification-policies/formStyles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 18 - } - ], - "/public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/CloudAlertPreview.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/CloudEvaluationBehavior.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 15 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/ExpressionsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/NeedHelpInfo.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/QueryOptions.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPolicyMatchers.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rule-viewer/RuleViewerVisualization.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rules/ActionButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/CloudRules.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetails.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleHealth.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/rules/RuleState.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/rules/RulesGroup.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 12 - } - ], - "/public/app/features/alerting/unified/components/rules/RulesTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/alerting/unified/components/rules/state-history/StateHistory.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/silences/Matchers.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/silences/MatchersField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/silences/SilenceDetails.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencePeriod.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/components/silences/SilencesTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/home/GettingStarted.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 15 - } - ], - "/public/app/features/alerting/unified/hooks/useAlertmanagerConfig.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/mocks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 5 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/alerting/unified/state/actions.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/styles/notifications.ts": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/styles/pagination.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/styles/table.ts": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/alerting/unified/types/receiver-form.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/alerting/unified/utils/misc.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/alerting/unified/utils/misc.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/utils/receiver-form.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/utils/redux.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 7 - } - ], - "/public/app/features/alerting/unified/utils/rulerClient.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/alerting/unified/utils/rules.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/annotations/events_processing.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/annotations/standardAnnotationSupport.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/api-keys/ApiKeysTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/auth-config/utils/data.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/canvas/element.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/features/canvas/elements/ellipse.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/canvas/runtime/element.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/features/canvas/runtime/frame.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/canvas/runtime/root.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/canvas/runtime/scene.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/connections/components/ConnectionsRedirectNotice/ConnectionsRedirectNotice.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/connections/tabs/ConnectData/CategoryHeader/CategoryHeader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/connections/tabs/ConnectData/NoAccessModal/NoAccessModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/correlations/CorrelationsPage.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/correlations/Forms/AddCorrelationForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/correlations/Forms/ConfigureCorrelationBasicInfoForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - } - ], - "/public/app/features/dashboard-scene/saving/getSaveDashboardChange.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/dashboard-scene/saving/shared.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 10 - } - ], - "/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts": [ - { - "message": "Do not use any type assertions.", - "count": 7 - } - ], - "/public/app/features/dashboard-scene/settings/variables/components/VariableSelectField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/settings/variables/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard-scene/utils/test-utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/DashNav/DashNavButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 7 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/Inspector/PanelInspector.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - } - ], - "/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/PanelEditor/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 3 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/ShareModal/ShareExport.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SubMenu/SubMenu.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/containers/DashboardPage.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/dashboard/containers/DashboardPageProxy.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard/services/DashboardLoaderSrv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/dashboard/state/DashboardMigrator.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 12 - } - ], - "/public/app/features/dashboard/state/DashboardMigrator.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 27 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/features/dashboard/state/DashboardModel.repeat.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/dashboard/state/DashboardModel.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/state/DashboardModel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 23 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dashboard/state/PanelModel.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/state/PanelModel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 20 - }, - { - "message": "Do not use any type assertions.", - "count": 5 - } - ], - "/public/app/features/dashboard/state/TimeModel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/dashboard/state/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/state/initDashboard.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/utils/getPanelMenu.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/utils/getPanelMenu.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dashboard/utils/panelMerge.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/datasources/components/BasicSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/datasources/components/DataSourceTestingStatus.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/datasources/components/DataSourceTypeCard.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/datasources/components/DataSourceTypeCardList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/datasources/components/EditDataSource.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/datasources/components/picker/DataSourceCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/datasources/components/picker/DataSourceList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/datasources/components/picker/DataSourceLogo.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/datasources/components/picker/DataSourceModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/features/datasources/state/actions.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/datasources/state/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/datasources/state/navModel.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/datasources/state/reducers.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/datasources/state/selectors.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/dimensions/editors/ColorDimensionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dimensions/editors/FileUploader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/dimensions/editors/FolderPickerTab.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dimensions/editors/ResourceCards.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/dimensions/editors/ResourcePicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/dimensions/editors/ResourcePickerPopover.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dimensions/editors/TextDimensionEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/dimensions/editors/URLPickerTab.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/dimensions/scale.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dimensions/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/dimensions/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/explore/ContentOutline/ContentOutline.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/explore/Logs/LiveLogs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/explore/Logs/Logs.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/explore/Logs/LogsMetaRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/explore/Logs/LogsNavigation.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/Logs/LogsNavigationPages.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/explore/Logs/LogsSamplePanel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/Logs/LogsVolumePanel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/Logs/LogsVolumePanelList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/Logs/utils/LogsCrossFadeTransition.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/NodeGraph/NodeGraphContainer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/PrometheusListView/ItemLabels.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/PrometheusListView/ItemValues.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/PrometheusListView/RawListContainer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/explore/PrometheusListView/RawListItem.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/PrometheusListView/RawListItemAttributes.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/RichHistory/RichHistoryCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 14 - } - ], - "/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 14 - } - ], - "/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/explore/TraceView/TraceView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/Actions/ActionButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/GraphTicks.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/Scrubber.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/TickLabels.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 22 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 14 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanFlameGraph.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/common/BreakableText.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/common/CopyIcon.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/common/LabeledList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/explore/TraceView/components/common/NewWindowIcon.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/common/TraceName.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/demo/trace-generators.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/model/link-patterns.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 10 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/components/settings/SpanBarSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/explore/TraceView/components/types/trace.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/explore/TraceView/createSpanLink.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/explore/spec/helper/setup.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/explore/state/time.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/explore/state/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 4 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/expressions/ExpressionDatasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/expressions/components/Condition.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/expressions/components/Math.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/expressions/guards.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/geo/editor/GazetteerPathEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/geo/editor/locationModeEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/geo/gazetteer/gazetteer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/geo/utils/frameVectorSource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/inspector/DetailText.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/inspector/InspectDataOptions.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/inspector/InspectDataTab.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/inspector/InspectJSONTab.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/inspector/InspectStatsTab.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/inspector/InspectStatsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/inspector/InspectStatsTraceIdsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/inspector/QueryInspector.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - } - ], - "/public/app/features/inspector/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/features/library-panels/components/LibraryPanelsView/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/library-panels/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/library-panels/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/live/LiveConnectionWarning.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/live/centrifuge/LiveDataStream.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/live/centrifuge/channel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/live/centrifuge/serviceWorkerProxy.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/live/data/amendTimeSeries.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/features/logs/components/LoadingIndicator.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/logs/components/LogDetailsRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/logs/components/LogLabelStats.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/logs/components/LogLabelStatsRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/logs/components/LogLabels.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/logs/components/LogRowMessageDisplayedFields.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/logs/components/getLogRowStyles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 36 - } - ], - "/public/app/features/logs/components/log-context/LogRowContextModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/public/app/features/logs/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/manage-dashboards/DashboardImportPage.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/manage-dashboards/state/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 14 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/manage-dashboards/state/reducers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/org/state/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/org/state/reducers.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/panel/panellinks/linkSuppliers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/panel/panellinks/link_srv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/playlist/EmptyQueryListBanner.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/playlist/PlaylistForm.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - } - ], - "/public/app/features/playlist/PlaylistTableRows.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/Badges/sharedStyles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/HorizontalGroup.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/PluginActions.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/PluginDetailsBody.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/plugins/admin/components/PluginDetailsPage.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/plugins/admin/components/PluginSubtitle.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/PluginUsage.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/plugins/admin/components/VersionList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/plugins/admin/hooks/useHistory.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/hooks/usePluginInfo.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/pages/Browse.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/plugins/admin/state/actions.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/plugins/datasource_srv.ts": [ - { - "message": "Do not use any type assertions.", - "count": 5 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - } - ], - "/public/app/features/plugins/sandbox/distortion_map.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts": [ - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/plugins/tests/datasource_srv.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/plugins/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/features/profile/ChangePasswordForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/query/components/QueryEditorRow.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 4 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/query/components/QueryEditorRowHeader.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 8 - } - ], - "/public/app/features/query/components/QueryGroup.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/query/components/QueryGroupOptions.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/query/state/DashboardQueryRunner/PublicAnnotationsDataSource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/query/state/DashboardQueryRunner/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/query/state/PanelQueryRunner.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/query/state/runRequest.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/query/state/updateQueries.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 11 - } - ], - "/public/app/features/search/page/components/ActionRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/search/page/components/SearchResultsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/public/app/features/search/page/components/columns.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/search/service/bluge.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/search/service/sql.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/search/service/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/search/state/SearchStateManager.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/search/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/search/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/serviceaccounts/components/CreateTokenModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/features/serviceaccounts/components/ServiceAccountProfile.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/serviceaccounts/components/ServiceAccountTokensTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/serviceaccounts/state/reducers.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/storage/Breadcrumb.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/storage/FileView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/storage/FolderView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/storage/RootView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/storage/StoragePage.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/features/storage/UploadButton.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/storage/storage.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/teams/TeamGroupSync.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/teams/state/reducers.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/teams/state/selectors.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/templating/fieldAccessorCache.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/templating/formatVariableValue.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/templating/templateProxies.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/templating/template_srv.mock.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/templating/template_srv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 10 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/calculateHeatmap/editor/helper.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/calculateHeatmap/heatmap.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/GroupByTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/transformers/editors/ReduceTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/editors/SortByTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/transformers/extractFields/components/JSONPathEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/transformers/extractFields/extractFields.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/extractFields/fieldExtractors.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/transformers/lookupGazetteer/fieldLookup.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/transformers/spatial/optionsHelper.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/features/transformers/standardTransformers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/users/TokenRevokedModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/features/variables/adapters.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/constant/reducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/variables/custom/reducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/variables/datasource/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/editor/VariableEditorContainer.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/variables/editor/VariableEditorEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/editor/VariableEditorList.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/features/variables/editor/VariableEditorListRow.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/features/variables/editor/getVariableQueryEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/editor/reducer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/editor/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/guard.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/inspect/NetworkGraph.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/public/app/features/variables/inspect/VariablesUnknownTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/features/variables/inspect/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/variables/pickers/OptionsPicker/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/pickers/shared/VariableLink.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/features/variables/pickers/shared/VariableOptions.tsx": [ - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - } - ], - "/public/app/features/variables/query/QueryVariableEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/query/VariableQueryRunner.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/variables/query/actions.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/query/actions.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/features/variables/query/operators.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/query/queryRunners.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/query/queryRunners.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/query/reducer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/query/variableQueryObserver.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/shared/formatVariable.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/variables/shared/testing/optionsVariableBuilder.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/variables/shared/testing/variableBuilder.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/variables/state/actions.ts": [ - { - "message": "Do not use any type assertions.", - "count": 7 - } - ], - "/public/app/features/variables/state/keyedVariablesReducer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/state/sharedReducer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/features/variables/state/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/features/variables/state/upgradeLegacyQueries.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/features/variables/system/adapter.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/features/variables/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/features/variables/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/features/visualization/data-hover/DataHoverRows.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/alertmanager/DataSource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/alertmanager/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/functions.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/types/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/XrayLinkConfig.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryFieldOld.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/DynamicLabelsField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/guards.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/cloudwatch/memoizedDebounce.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloudwatch/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/dashboard/runSharedRequest.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 32 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/LanguageProvider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/elasticsearch/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 3 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationQueryEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/nodeGraphUtils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana/components/QueryEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana/components/TimePickerInput.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/grafana/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 7 - } - ], - "/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/plugins/datasource/graphite/components/TagsSection.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/components/helpers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/datasource.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/graphite/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 51 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/datasource/graphite/gfunc.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 9 - }, - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/graphite/graphite_query.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 19 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/lexer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/public/app/plugins/datasource/graphite/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/specs/store.test.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/graphite/state/context.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/graphite/state/helpers.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/graphite/state/store.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/graphite/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/graphite/utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/flux/FluxQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 14 - } - ], - "/public/app/plugins/datasource/influxdb/influx_query_model.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - } - ], - "/public/app/plugins/datasource/influxdb/influx_series.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 19 - } - ], - "/public/app/plugins/datasource/influxdb/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/mocks.ts": [ - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/datasource/influxdb/query_part.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 16 - } - ], - "/public/app/plugins/datasource/influxdb/response_parser.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/jaeger/CheatSheet.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/jaeger/configuration/ConfigEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/jaeger/configuration/TraceIdTimeParams.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/jaeger/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/jaeger/testResponse.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/jaeger/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/LanguageProvider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/loki/LiveStreams.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/components/LokiContextUi.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 11 - } - ], - "/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 14 - } - ], - "/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/configuration/DerivedField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/plugins/datasource/loki/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/public/app/plugins/datasource/loki/queryUtils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/loki/streaming.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/opentsdb/components/OpenTsdbQueryEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/opentsdb/datasource.d.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/opentsdb/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 57 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/opentsdb/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/parca/QueryEditor/LabelsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 12 - } - ], - "/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/AzureCredentialsConfig.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 18 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 16 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/datasource.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - } - ], - "/public/app/plugins/datasource/prometheus/language_provider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/plugins/datasource/prometheus/language_utils.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/metric_find_query.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/plugins/datasource/prometheus/query_hints.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/QueryPattern.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/QueryPatternsModal.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilterItem.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilters.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/AdditionalSettings.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/ResultsTable.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 13 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 19 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/operationUtils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 8 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryBuilderHints.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/prometheus/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/tempo/LokiSearch.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/LanguageProvider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/monaco-query-field/MonacoQueryField.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - }, - { - "message": "Use data-testid for E2E selectors instead of aria-label", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/monaco-query-field/monaco-completion-provider/CompletionDataProvider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/monaco-query-field/monaco-completion-provider/index.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/language_utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/store.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/test/helpers/createFetchResponse.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/datasource/tempo/configuration/TraceQLSearchSettings.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/plugins/datasource/tempo/language_provider.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/resultTransformer.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/datasource/zipkin/ConfigEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/datasource/zipkin/QueryField.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/zipkin/datasource.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/datasource/zipkin/utils/testResponse.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/datasource/zipkin/utils/transforms.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/alertGroups/AlertGroup.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/alertlist/AlertInstances.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/alertlist/AlertList.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Styles should be written using objects.", - "count": 10 - } - ], - "/public/app/plugins/panel/alertlist/AlertListMigrationHandler.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/alertlist/UnifiedAlertList.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 17 - } - ], - "/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/annolist/AnnoListPanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/barchart/BarChartPanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/panel/barchart/bars.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/barchart/module.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/barchart/quadtree.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/candlestick/CandlestickPanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/element/PlacementEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/canvas/editor/layer/layerEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/canvas/globalStyles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/dashlist/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/datagrid/utils.ts": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/panel/debug/CursorView.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/debug/EventBusLogger.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/gauge/GaugeMigrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/components/MarkersLegend.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 2 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/editor/StyleEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 12 - } - ], - "/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/layers/basemaps/esri.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/layers/registry.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/geomap/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/geomap/utils/layers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/geomap/utils/tooltip.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/plugins/panel/gettingstarted/components/Step.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 4 - } - ], - "/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/heatmap/HeatmapPanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/heatmap/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/heatmap/palettes.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/heatmap/types.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/heatmap/utils.ts": [ - { - "message": "Do not use any type assertions.", - "count": 17 - } - ], - "/public/app/plugins/panel/histogram/Histogram.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/live/LiveChannelEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/live/LivePanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/live/types.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/logs/LogsPanel.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/nodeGraph/Edge.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/nodeGraph/Legend.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/nodeGraph/Marker.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 3 - } - ], - "/public/app/plugins/panel/nodeGraph/Node.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/plugins/panel/nodeGraph/NodeGraph.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 10 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/panel/nodeGraph/ViewControls.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/nodeGraph/editor/ArcOptionsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/nodeGraph/layout.ts": [ - { - "message": "Do not use any type assertions.", - "count": 2 - } - ], - "/public/app/plugins/panel/nodeGraph/useContextMenu.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/piechart/PieChart.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 5 - } - ], - "/public/app/plugins/panel/piechart/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/stat/StatMigrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/state-timeline/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 2 - } - ], - "/public/app/plugins/panel/table/TablePanel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/table/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/public/app/plugins/panel/text/TextPanel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/text/TextPanelEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/text/textPanelMigrationHandler.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/TimezonesEditor.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/timeseries/migrations.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - }, - { - "message": "Do not use any type assertions.", - "count": 4 - } - ], - "/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin2.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 7 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 9 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationMarker2.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/plugins/panel/timeseries/plugins/styles.ts": [ - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/traces/TracesPanel.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 1 - } - ], - "/public/app/plugins/panel/welcome/Welcome.tsx": [ - { - "message": "Styles should be written using objects.", - "count": 6 - } - ], - "/public/app/plugins/panel/xychart/AutoEditor.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Styles should be written using objects.", - "count": 2 - } - ], - "/public/app/plugins/panel/xychart/ManualEditor.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/xychart/TooltipView.tsx": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/xychart/XYChartPanel.tsx": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/plugins/panel/xychart/scatter.ts": [ - { - "message": "Do not use any type assertions.", - "count": 7 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 4 - } - ], - "/public/app/store/configureStore.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/store/store.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - }, - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/types/alerting.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 5 - } - ], - "/public/app/types/appEvent.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 7 - } - ], - "/public/app/types/dashboard.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/app/types/events.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 13 - } - ], - "/public/app/types/jquery/jquery.d.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 9 - } - ], - "/public/app/types/store.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - }, - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/app/types/unified-alerting-dto.ts": [ - { - "message": "Do not use any type assertions.", - "count": 1 - } - ], - "/public/test/core/redux/reduxTester.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 6 - } - ], - "/public/test/core/thunk/thunkTester.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 8 - } - ], - "/public/test/global-jquery-shim.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/test/helpers/getDashboardModel.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/test/helpers/initTemplateSrv.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/test/jest-setup.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/test/lib/common.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 1 - } - ], - "/public/test/specs/helpers.ts": [ - { - "message": "Unexpected any. Specify a different type.", - "count": 11 - } - ] - }, - "no undocumented stories": { - "/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Segment/Segment.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Slider/Slider.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx": [ - { - "message": "No undocumented stories are allowed, please add an .mdx file with some documentation", - "count": 1 - } - ] - }, - "no gf-form usage": { - "/e2e/utils/flows/addDataSource.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-e2e/src/flows/addDataSource.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/packages/grafana-prometheus/src/components/PromQueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 6 - } - ], - "/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-prometheus/src/configuration/PromSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 25 - } - ], - "/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 16 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/FormField/FormField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 6 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 8 - } - ], - "/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Segment/Segment.story.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/packages/grafana-ui/src/components/Segment/SegmentInput.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/angular/components/code_editor/code_editor.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/angular/components/form_dropdown/form_dropdown.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/angular/components/info_popover.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/angular/components/switch.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 8 - } - ], - "/public/app/angular/dropdown_typeahead.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/angular/metric_segment.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/angular/misc.ts": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/angular/panel/partials/query_editor_row.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/angular/partials/tls_auth_settings.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 35 - } - ], - "/public/app/core/components/AccessControl/PermissionList.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/core/components/PageHeader/PageHeader.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/features/admin/UserLdapSyncInfo.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/features/admin/partials/edit_org.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 8 - } - ], - "/public/app/features/admin/partials/styleguide.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/features/alerting/AlertRuleList.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/features/alerting/partials/alert_tab.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 71 - } - ], - "/public/app/features/annotations/partials/event_editor.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 7 - } - ], - "/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/datasources/components/BasicSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/features/datasources/components/ButtonRow.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/datasources/components/DataSourceLoadError.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/datasources/components/DataSourcePluginState.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/features/datasources/components/DataSourceTestingStatus.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/plugins/admin/components/AppConfigWrapper.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/query/components/QueryEditorRow.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/query/components/QueryGroupOptions.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 17 - } - ], - "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 5 - } - ], - "/public/app/features/variables/adhoc/picker/AdHocFilter.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/features/variables/adhoc/picker/ConditionSegment.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/features/variables/pickers/PickerRenderer.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 5 - } - ], - "/public/app/features/variables/pickers/shared/VariableInput.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/partials/confirm_modal.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/partials/reset_password.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 13 - } - ], - "/public/app/partials/signup_invited.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 14 - } - ], - "/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/plugins/datasource/cloud-monitoring/components/AnnotationsHelp.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/XrayLinkConfig.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryFieldOld.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/grafana-pyroscope-datasource/ConfigEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/annotation/AnnotationEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 8 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/QueryEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/flux/FluxQueryEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 5 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 5 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/influxdb/components/editor/variable/VariableQueryEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/loki/components/LokiQueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/loki/configuration/DerivedField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/opentsdb/components/AnnotationEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 4 - } - ], - "/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 6 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/AzureCredentialsForm.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 21 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 25 - } - ], - "/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilter.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterKey.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterValue.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 1 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/ConditionSegment.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/loki/LokiQueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 3 - } - ], - "/public/app/plugins/datasource/zipkin/QueryField.tsx": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 2 - } - ], - "/public/app/plugins/panel/graph/axes_editor.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 50 - } - ], - "/public/app/plugins/panel/graph/tab_display.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 56 - } - ], - "/public/app/plugins/panel/graph/tab_legend.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 42 - } - ], - "/public/app/plugins/panel/graph/tab_series_overrides.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 10 - } - ], - "/public/app/plugins/panel/graph/thresholds_form.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 31 - } - ], - "/public/app/plugins/panel/graph/time_regions_form.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 32 - } - ], - "/public/app/plugins/panel/heatmap/partials/axes_editor.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 53 - } - ], - "/public/app/plugins/panel/heatmap/partials/display_editor.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 51 - } - ], - "/public/app/plugins/panel/table-old/column_options.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 91 - } - ], - "/public/app/plugins/panel/table-old/editor.html": [ - { - "message": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", - "count": 22 - } - ] - } + "better eslint": [ + { + "name": "Do not use any type assertions.", + "files": [ + { + "path": "/e2e/utils/support/types.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/dataframe/DataFrameView.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/dataframe/MutableDataFrame.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/dataframe/StreamingDataFrame.ts", + "count": 5 + }, + { + "path": "/packages/grafana-data/src/dataframe/processDataFrame.ts", + "count": 18 + }, + { + "path": "/packages/grafana-data/src/datetime/datemath.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/datetime/durationutil.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/datetime/formatter.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/datetime/moment_wrapper.ts", + "count": 8 + }, + { + "path": "/packages/grafana-data/src/datetime/parser.ts", + "count": 9 + }, + { + "path": "/packages/grafana-data/src/datetime/rangeutil.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/field/displayProcessor.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/field/overrides/processors.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/panel/PanelPlugin.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/panel/registryFactories.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/themes/createColors.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/transformations/fieldReducer.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/transformations/transformers/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/app.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/live.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/plugin.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/csv.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/utils/datasource.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/utils/location.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/url.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/utils/valueMappings.ts", + "count": 2 + }, + { + "path": "/packages/grafana-o11y-ds-frontend/src/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts", + "count": 4 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/language_utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-runtime/src/config.ts", + "count": 3 + }, + { + "path": "/packages/grafana-runtime/src/services/LocationService.ts", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/utils/queryResponse.ts", + "count": 1 + }, + { + "path": "/packages/grafana-schema/src/veneer/dashboard.types.ts", + "count": 5 + }, + { + "path": "/packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/MatchersUI/FieldValueMatcher.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/PanelChrome/index.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Select/SelectBase.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts", + "count": 5 + }, + { + "path": "/packages/grafana-ui/src/components/Table/FooterRow.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/Table.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/TableCell.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/Table/reducer.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Tags/Tag.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/Plot.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/utils.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/GraphNG.tsx", + "count": 6 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/hooks.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/nullInsertThreshold.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/nullToUndefThreshold.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/options/builder/hideSeries.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/slate-plugins/braces.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/slate-plugins/suggestions.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/themes/ThemeContext.tsx", + "count": 3 + }, + { + "path": "/public/app/core/components/GraphNG/GraphNG.tsx", + "count": 4 + }, + { + "path": "/public/app/core/components/TimeSeries/utils.ts", + "count": 1 + }, + { + "path": "/public/app/core/components/connectWithCleanUp.tsx", + "count": 1 + }, + { + "path": "/public/app/core/services/backend_srv.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/context_srv.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts", + "count": 2 + }, + { + "path": "/public/app/core/utils/connectWithReduxStore.tsx", + "count": 2 + }, + { + "path": "/public/app/core/utils/fetch.ts", + "count": 5 + }, + { + "path": "/public/app/core/utils/object.ts", + "count": 3 + }, + { + "path": "/public/app/core/utils/ticks.ts", + "count": 2 + }, + { + "path": "/public/app/features/admin/OrgRolePicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/state/alertDef.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/RuleList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/Authorize.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/hooks/useAlertmanagerConfig.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/mocks.ts", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/state/actions.ts", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/utils/receiver-form.ts", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/utils/redux.ts", + "count": 6 + }, + { + "path": "/public/app/features/alerting/unified/utils/rulerClient.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/utils/rules.ts", + "count": 3 + }, + { + "path": "/public/app/features/annotations/standardAnnotationSupport.ts", + "count": 1 + }, + { + "path": "/public/app/features/auth-config/utils/data.ts", + "count": 1 + }, + { + "path": "/public/app/features/canvas/runtime/element.tsx", + "count": 5 + }, + { + "path": "/public/app/features/canvas/runtime/frame.tsx", + "count": 1 + }, + { + "path": "/public/app/features/canvas/runtime/root.tsx", + "count": 1 + }, + { + "path": "/public/app/features/canvas/runtime/scene.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/saving/getDashboardChanges.ts", + "count": 2 + }, + { + "path": "/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts", + "count": 8 + }, + { + "path": "/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx", + "count": 7 + }, + { + "path": "/public/app/features/dashboard/components/Inspector/PanelInspector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/utils.ts", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/containers/DashboardPage.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/containers/DashboardPageProxy.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/DashboardMigrator.ts", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/state/DashboardModel.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/PanelModel.ts", + "count": 5 + }, + { + "path": "/public/app/features/dashboard/utils/getPanelMenu.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/utils/panelMerge.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/datasources/state/navModel.ts", + "count": 1 + }, + { + "path": "/public/app/features/datasources/state/reducers.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/state/selectors.ts", + "count": 2 + }, + { + "path": "/public/app/features/dimensions/editors/FolderPickerTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/TextDimensionEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/scale.ts", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/explore/ContentOutline/ContentOutline.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/TraceView.tsx", + "count": 2 + }, + { + "path": "/public/app/features/explore/TraceView/components/demo/trace-generators.ts", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/model/link-patterns.tsx", + "count": 2 + }, + { + "path": "/public/app/features/explore/TraceView/components/model/transform-trace-data.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/createSpanLink.tsx", + "count": 2 + }, + { + "path": "/public/app/features/explore/spec/helper/setup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/state/utils.ts", + "count": 4 + }, + { + "path": "/public/app/features/expressions/ExpressionDatasource.ts", + "count": 2 + }, + { + "path": "/public/app/features/expressions/guards.ts", + "count": 1 + }, + { + "path": "/public/app/features/geo/gazetteer/gazetteer.ts", + "count": 1 + }, + { + "path": "/public/app/features/geo/utils/frameVectorSource.ts", + "count": 2 + }, + { + "path": "/public/app/features/inspector/InspectDataOptions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/live/centrifuge/LiveDataStream.ts", + "count": 1 + }, + { + "path": "/public/app/features/live/centrifuge/serviceWorkerProxy.ts", + "count": 1 + }, + { + "path": "/public/app/features/live/data/amendTimeSeries.ts", + "count": 4 + }, + { + "path": "/public/app/features/logs/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/manage-dashboards/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/manage-dashboards/state/reducers.ts", + "count": 1 + }, + { + "path": "/public/app/features/org/state/reducers.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsBody.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/pages/Browse.tsx", + "count": 2 + }, + { + "path": "/public/app/features/plugins/admin/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/datasource_srv.ts", + "count": 5 + }, + { + "path": "/public/app/features/plugins/sandbox/distortion_map.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts", + "count": 3 + }, + { + "path": "/public/app/features/plugins/utils.ts", + "count": 4 + }, + { + "path": "/public/app/features/query/components/QueryEditorRow.tsx", + "count": 4 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts", + "count": 1 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/PublicAnnotationsDataSource.ts", + "count": 1 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts", + "count": 1 + }, + { + "path": "/public/app/features/query/state/PanelQueryRunner.ts", + "count": 2 + }, + { + "path": "/public/app/features/query/state/runRequest.ts", + "count": 1 + }, + { + "path": "/public/app/features/search/page/components/columns.tsx", + "count": 1 + }, + { + "path": "/public/app/features/search/service/bluge.ts", + "count": 2 + }, + { + "path": "/public/app/features/search/service/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/search/state/SearchStateManager.ts", + "count": 1 + }, + { + "path": "/public/app/features/search/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/serviceaccounts/state/reducers.ts", + "count": 1 + }, + { + "path": "/public/app/features/teams/state/reducers.ts", + "count": 1 + }, + { + "path": "/public/app/features/templating/template_srv.mock.ts", + "count": 4 + }, + { + "path": "/public/app/features/templating/template_srv.ts", + "count": 3 + }, + { + "path": "/public/app/features/trails/MetricScene.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/calculateHeatmap/heatmap.ts", + "count": 2 + }, + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/GroupByTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/ReduceTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/SortByTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/transformers/extractFields/extractFields.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/spatial/optionsHelper.tsx", + "count": 3 + }, + { + "path": "/public/app/features/variables/constant/reducer.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/custom/reducer.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorContainer.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/inspect/utils.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/VariableQueryRunner.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/shared/formatVariable.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/shared/testing/optionsVariableBuilder.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/shared/testing/variableBuilder.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/state/actions.ts", + "count": 7 + }, + { + "path": "/public/app/features/variables/state/sharedReducer.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/system/adapter.ts", + "count": 4 + }, + { + "path": "/public/app/features/variables/utils.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/datasource.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/functions.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/guards.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/dashboard/runSharedRequest.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/QueryEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana/datasource.ts", + "count": 7 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/datasource.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/graphite/gfunc.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/graphite_query.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/state/context.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/state/store.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/datasource.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/influxdb/mocks.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/prometheus/configuration/AzureCredentialsConfig.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/language_utils.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/configuration/TraceQLSearchSettings.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/resultTransformer.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/zipkin/QueryField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/zipkin/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/annolist/AnnoListPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/barchart/BarChartPanel.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/barchart/bars.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/barchart/module.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/candlestick/CandlestickPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/layer/layerEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/debug/CursorView.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/components/MarkersLegend.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/editor/StyleEditor.tsx", + "count": 12 + }, + { + "path": "/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/layers/basemaps/esri.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/utils/tooltip.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/HeatmapPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/palettes.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/utils.ts", + "count": 17 + }, + { + "path": "/public/app/plugins/panel/live/LivePanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/logs/LogsPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/Edge.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/NodeGraph.tsx", + "count": 4 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/layout.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/timeseries/migrations.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/panel/xychart/ManualEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/xychart/XYChartPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/xychart/scatter.ts", + "count": 7 + }, + { + "path": "/public/app/plugins/panel/xychart/v2/SeriesEditor.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/panel/xychart/v2/migrations.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/xychart/v2/scatter.ts", + "count": 6 + }, + { + "path": "/public/app/store/store.ts", + "count": 1 + }, + { + "path": "/public/app/types/store.ts", + "count": 1 + }, + { + "path": "/public/app/types/unified-alerting-dto.ts", + "count": 1 + } + ] + }, + { + "name": "Unexpected any. Specify a different type.", + "files": [ + { + "path": "/packages/grafana-data/src/dataframe/ArrayDataFrame.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/dataframe/CircularDataFrame.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/dataframe/DataFrameView.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/dataframe/MutableDataFrame.ts", + "count": 9 + }, + { + "path": "/packages/grafana-data/src/dataframe/dimensions.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/dataframe/processDataFrame.test.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/dataframe/processDataFrame.ts", + "count": 5 + }, + { + "path": "/packages/grafana-data/src/datetime/datemath.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/datetime/moment_wrapper.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/events/EventBus.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/events/types.ts", + "count": 5 + }, + { + "path": "/packages/grafana-data/src/field/fieldState.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/field/overrides/processors.ts", + "count": 6 + }, + { + "path": "/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts", + "count": 7 + }, + { + "path": "/packages/grafana-data/src/geo/layer.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/panel/PanelPlugin.ts", + "count": 6 + }, + { + "path": "/packages/grafana-data/src/themes/colorManipulator.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/transformations/standardTransformersRegistry.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/transformations/transformDataFrame.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/transformations/transformers/reduce.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/transformations/transformers/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts", + "count": 7 + }, + { + "path": "/packages/grafana-data/src/types/ScopedVars.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/annotations.ts", + "count": 6 + }, + { + "path": "/packages/grafana-data/src/types/app.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/config.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/dashboard.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/types/data.ts", + "count": 4 + }, + { + "path": "/packages/grafana-data/src/types/dataFrame.ts", + "count": 4 + }, + { + "path": "/packages/grafana-data/src/types/dataLink.ts", + "count": 7 + }, + { + "path": "/packages/grafana-data/src/types/datasource.ts", + "count": 25 + }, + { + "path": "/packages/grafana-data/src/types/explore.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/fieldOverrides.ts", + "count": 9 + }, + { + "path": "/packages/grafana-data/src/types/flot.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/graph.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/types/legacyEvents.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/live.ts", + "count": 7 + }, + { + "path": "/packages/grafana-data/src/types/options.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/panel.ts", + "count": 14 + }, + { + "path": "/packages/grafana-data/src/types/plugin.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/select.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/templateVars.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/types/trace.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/types/transformations.ts", + "count": 5 + }, + { + "path": "/packages/grafana-data/src/types/variables.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/OptionsUIBuilders.ts", + "count": 27 + }, + { + "path": "/packages/grafana-data/src/utils/Registry.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/arrayUtils.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/utils/csv.ts", + "count": 2 + }, + { + "path": "/packages/grafana-data/src/utils/dataLinks.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/flotPairs.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/utils/url.ts", + "count": 7 + }, + { + "path": "/packages/grafana-data/src/utils/valueMappings.ts", + "count": 3 + }, + { + "path": "/packages/grafana-data/src/vector/CircularVector.ts", + "count": 1 + }, + { + "path": "/packages/grafana-data/src/vector/FunctionalVector.ts", + "count": 9 + }, + { + "path": "/packages/grafana-data/test/__mocks__/pluginMocks.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromQueryField.test.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromQueryField.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/datasource.ts", + "count": 9 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/language_provider.ts", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/shared/types.ts", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/types.ts", + "count": 3 + }, + { + "path": "/packages/grafana-runtime/src/analytics/types.ts", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/config.ts", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/services/AngularLoader.ts", + "count": 3 + }, + { + "path": "/packages/grafana-runtime/src/services/EchoSrv.ts", + "count": 4 + }, + { + "path": "/packages/grafana-runtime/src/services/LocationService.ts", + "count": 5 + }, + { + "path": "/packages/grafana-runtime/src/services/backendSrv.ts", + "count": 13 + }, + { + "path": "/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts", + "count": 1 + }, + { + "path": "/packages/grafana-schema/src/veneer/common.types.ts", + "count": 1 + }, + { + "path": "/packages/grafana-schema/src/veneer/dashboard.types.ts", + "count": 2 + }, + { + "path": "/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/types.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Modal/ModalsContext.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Select/SelectBase.tsx", + "count": 7 + }, + { + "path": "/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/Select/ValueContainer.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Select/resetSelectStyles.ts", + "count": 4 + }, + { + "path": "/packages/grafana-ui/src/components/Select/types.ts", + "count": 6 + }, + { + "path": "/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts", + "count": 15 + }, + { + "path": "/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/Table/Filter.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/FilterPopup.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/FooterRow.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/HeaderRow.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/Table.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Table/TableCell.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/reducer.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Table/types.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Table/utils.ts", + "count": 6 + }, + { + "path": "/packages/grafana-ui/src/components/VizLegend/types.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/uPlot/types.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/Graph/GraphContextMenu.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/Graph/utils.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/GraphNG.tsx", + "count": 6 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/nullInsertThreshold.ts", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/graveyard/GraphNG/nullToUndefThreshold.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/slate-plugins/suggestions.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/themes/stylesFactory.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/types/forms.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/types/jquery.d.ts", + "count": 9 + }, + { + "path": "/packages/grafana-ui/src/types/mdx.d.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/types/react-table-config.d.ts", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/utils/debug.ts", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/utils/dom.ts", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/utils/logger.ts", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/utils/storybook/withTheme.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/utils/useAsyncDependency.ts", + "count": 1 + }, + { + "path": "/public/app/core/TableModel.ts", + "count": 7 + }, + { + "path": "/public/app/core/components/DynamicImports/SafeDynamicImport.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/GraphNG/GraphNG.tsx", + "count": 6 + }, + { + "path": "/public/app/core/components/OptionsUI/registry.tsx", + "count": 11 + }, + { + "path": "/public/app/core/components/TagFilter/TagFilter.tsx", + "count": 5 + }, + { + "path": "/public/app/core/components/TagFilter/TagOption.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/TimeSeries/utils.ts", + "count": 2 + }, + { + "path": "/public/app/core/components/connectWithCleanUp.tsx", + "count": 1 + }, + { + "path": "/public/app/core/navigation/types.ts", + "count": 1 + }, + { + "path": "/public/app/core/reducers/root.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/ResponseQueue.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/backend_srv.ts", + "count": 9 + }, + { + "path": "/public/app/core/services/context_srv.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts", + "count": 1 + }, + { + "path": "/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts", + "count": 2 + }, + { + "path": "/public/app/core/specs/backend_srv.test.ts", + "count": 1 + }, + { + "path": "/public/app/core/time_series2.ts", + "count": 19 + }, + { + "path": "/public/app/core/utils/ConfigProvider.tsx", + "count": 2 + }, + { + "path": "/public/app/core/utils/connectWithReduxStore.tsx", + "count": 8 + }, + { + "path": "/public/app/core/utils/deferred.ts", + "count": 1 + }, + { + "path": "/public/app/core/utils/fetch.ts", + "count": 1 + }, + { + "path": "/public/app/core/utils/flatten.ts", + "count": 1 + }, + { + "path": "/public/app/core/utils/object.ts", + "count": 2 + }, + { + "path": "/public/app/core/utils/richHistory.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/routes.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/state/ThresholdMapper.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/state/alertDef.ts", + "count": 4 + }, + { + "path": "/public/app/features/alerting/state/query_part.ts", + "count": 11 + }, + { + "path": "/public/app/features/alerting/state/reducers.ts", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/notification-policies/Policy.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/mocks.ts", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/types/receiver-form.ts", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/utils/misc.test.ts", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/utils/receiver-form.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/utils/redux.ts", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/utils/rules.ts", + "count": 1 + }, + { + "path": "/public/app/features/annotations/events_processing.ts", + "count": 1 + }, + { + "path": "/public/app/features/annotations/standardAnnotationSupport.ts", + "count": 2 + }, + { + "path": "/public/app/features/canvas/element.ts", + "count": 5 + }, + { + "path": "/public/app/features/canvas/runtime/element.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts", + "count": 2 + }, + { + "path": "/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts", + "count": 4 + }, + { + "path": "/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts", + "count": 13 + }, + { + "path": "/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/variables/components/VariableSelectField.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/variables/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts", + "count": 6 + }, + { + "path": "/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts", + "count": 7 + }, + { + "path": "/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/utils.ts", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/ShareExport.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/containers/DashboardPage.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/services/DashboardLoaderSrv.ts", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/state/DashboardMigrator.test.ts", + "count": 12 + }, + { + "path": "/public/app/features/dashboard/state/DashboardMigrator.ts", + "count": 27 + }, + { + "path": "/public/app/features/dashboard/state/DashboardModel.repeat.test.ts", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/state/DashboardModel.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/DashboardModel.ts", + "count": 23 + }, + { + "path": "/public/app/features/dashboard/state/PanelModel.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/PanelModel.ts", + "count": 20 + }, + { + "path": "/public/app/features/dashboard/state/TimeModel.ts", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/state/initDashboard.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/utils/getPanelMenu.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/utils/getPanelMenu.ts", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/utils/panelMerge.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/state/actions.test.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/datasources/state/navModel.ts", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/scale.ts", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/explore/Logs/Logs.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/Logs/LogsMetaRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/model/link-patterns.tsx", + "count": 10 + }, + { + "path": "/public/app/features/explore/TraceView/components/types/trace.ts", + "count": 1 + }, + { + "path": "/public/app/features/explore/spec/helper/setup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/state/time.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/explore/state/utils.ts", + "count": 2 + }, + { + "path": "/public/app/features/geo/gazetteer/gazetteer.ts", + "count": 1 + }, + { + "path": "/public/app/features/inspector/QueryInspector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/components/LibraryPanelsView/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/live/centrifuge/channel.ts", + "count": 1 + }, + { + "path": "/public/app/features/live/data/amendTimeSeries.ts", + "count": 1 + }, + { + "path": "/public/app/features/manage-dashboards/state/actions.ts", + "count": 14 + }, + { + "path": "/public/app/features/manage-dashboards/state/reducers.ts", + "count": 4 + }, + { + "path": "/public/app/features/org/state/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/panel/panellinks/linkSuppliers.ts", + "count": 1 + }, + { + "path": "/public/app/features/panel/panellinks/link_srv.ts", + "count": 4 + }, + { + "path": "/public/app/features/plugins/admin/hooks/useHistory.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/datasource_srv.ts", + "count": 8 + }, + { + "path": "/public/app/features/plugins/tests/datasource_srv.test.ts", + "count": 3 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts", + "count": 1 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts", + "count": 2 + }, + { + "path": "/public/app/features/query/state/DashboardQueryRunner/utils.ts", + "count": 4 + }, + { + "path": "/public/app/features/query/state/updateQueries.test.ts", + "count": 11 + }, + { + "path": "/public/app/features/search/service/sql.ts", + "count": 1 + }, + { + "path": "/public/app/features/search/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/storage/storage.ts", + "count": 2 + }, + { + "path": "/public/app/features/teams/TeamGroupSync.tsx", + "count": 2 + }, + { + "path": "/public/app/features/teams/state/selectors.ts", + "count": 1 + }, + { + "path": "/public/app/features/templating/fieldAccessorCache.ts", + "count": 1 + }, + { + "path": "/public/app/features/templating/formatVariableValue.ts", + "count": 3 + }, + { + "path": "/public/app/features/templating/templateProxies.ts", + "count": 1 + }, + { + "path": "/public/app/features/templating/template_srv.ts", + "count": 10 + }, + { + "path": "/public/app/features/trails/ActionTabs/utils.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts", + "count": 3 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts", + "count": 3 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/calculateHeatmap/editor/helper.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/transformers/extractFields/extractFields.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/extractFields/fieldExtractors.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts", + "count": 4 + }, + { + "path": "/public/app/features/transformers/lookupGazetteer/fieldLookup.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts", + "count": 2 + }, + { + "path": "/public/app/features/transformers/spatial/optionsHelper.tsx", + "count": 3 + }, + { + "path": "/public/app/features/transformers/standardTransformers.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/adapters.ts", + "count": 3 + }, + { + "path": "/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/datasource/actions.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/getVariableQueryEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/editor/reducer.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/guard.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/inspect/NetworkGraph.tsx", + "count": 7 + }, + { + "path": "/public/app/features/variables/inspect/utils.ts", + "count": 8 + }, + { + "path": "/public/app/features/variables/pickers/OptionsPicker/actions.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/QueryVariableEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/VariableQueryRunner.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/query/actions.test.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/query/actions.ts", + "count": 5 + }, + { + "path": "/public/app/features/variables/query/operators.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/queryRunners.test.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/query/queryRunners.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/reducer.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/query/variableQueryObserver.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/state/keyedVariablesReducer.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/state/sharedReducer.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/state/types.ts", + "count": 1 + }, + { + "path": "/public/app/features/variables/state/upgradeLegacyQueries.test.ts", + "count": 2 + }, + { + "path": "/public/app/features/variables/system/adapter.ts", + "count": 4 + }, + { + "path": "/public/app/features/variables/types.ts", + "count": 5 + }, + { + "path": "/public/app/features/variables/utils.ts", + "count": 6 + }, + { + "path": "/public/app/plugins/datasource/alertmanager/DataSource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/alertmanager/types.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/datasource.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/types/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/types.ts", + "count": 7 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts", + "count": 30 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/LanguageProvider.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts", + "count": 8 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/datasource.ts", + "count": 5 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationQueryEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/datasource.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/helpers.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/datasource.test.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/graphite/datasource.ts", + "count": 49 + }, + { + "path": "/public/app/plugins/datasource/graphite/gfunc.ts", + "count": 9 + }, + { + "path": "/public/app/plugins/datasource/graphite/graphite_query.ts", + "count": 19 + }, + { + "path": "/public/app/plugins/datasource/graphite/lexer.ts", + "count": 7 + }, + { + "path": "/public/app/plugins/datasource/graphite/migrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/specs/store.test.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/types.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/utils.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/datasource.ts", + "count": 12 + }, + { + "path": "/public/app/plugins/datasource/influxdb/influx_query_model.ts", + "count": 12 + }, + { + "path": "/public/app/plugins/datasource/influxdb/influx_series.ts", + "count": 19 + }, + { + "path": "/public/app/plugins/datasource/influxdb/migrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/query_part.ts", + "count": 15 + }, + { + "path": "/public/app/plugins/datasource/influxdb/response_parser.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/LanguageProvider.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/datasource.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/loki/streaming.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/datasource.d.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/datasource.ts", + "count": 44 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/migrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/prometheus/configuration/AzureCredentialsConfig.ts", + "count": 18 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/types.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/tempo/datasource.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/tempo/language_provider.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/resultTransformer.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/zipkin/QueryField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/zipkin/datasource.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/zipkin/utils/transforms.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/barchart/quadtree.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/candlestick/CandlestickPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/layer/layerEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/debug/CursorView.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/debug/EventBusLogger.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/gauge/GaugeMigrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/components/MarkersLegend.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/layers/registry.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/geomap/migrations.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/geomap/utils/layers.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/HeatmapPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/migrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/palettes.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/histogram/migrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/live/LiveChannelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/ViewControls.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/piechart/migrations.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/stat/StatMigrations.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/state-timeline/migrations.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/table/migrations.ts", + "count": 6 + }, + { + "path": "/public/app/plugins/panel/text/TextPanelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/text/textPanelMigrationHandler.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/migrations.ts", + "count": 6 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin2.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationMarker2.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/xychart/AutoEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/xychart/XYChartPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/xychart/scatter.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/panel/xychart/v2/scatter.ts", + "count": 4 + }, + { + "path": "/public/app/plugins/panel/xychart/v2/utils.ts", + "count": 1 + }, + { + "path": "/public/app/store/configureStore.ts", + "count": 1 + }, + { + "path": "/public/app/store/store.ts", + "count": 1 + }, + { + "path": "/public/app/types/alerting.ts", + "count": 5 + }, + { + "path": "/public/app/types/appEvent.ts", + "count": 7 + }, + { + "path": "/public/app/types/dashboard.ts", + "count": 1 + }, + { + "path": "/public/app/types/events.ts", + "count": 13 + }, + { + "path": "/public/app/types/jquery/jquery.d.ts", + "count": 9 + }, + { + "path": "/public/app/types/store.ts", + "count": 1 + }, + { + "path": "/public/test/core/redux/reduxTester.ts", + "count": 6 + }, + { + "path": "/public/test/core/thunk/thunkTester.ts", + "count": 8 + }, + { + "path": "/public/test/global-jquery-shim.ts", + "count": 1 + }, + { + "path": "/public/test/helpers/getDashboardModel.ts", + "count": 1 + }, + { + "path": "/public/test/helpers/initTemplateSrv.ts", + "count": 1 + }, + { + "path": "/public/test/jest-setup.ts", + "count": 1 + }, + { + "path": "/public/test/lib/common.ts", + "count": 1 + }, + { + "path": "/public/test/specs/helpers.ts", + "count": 11 + } + ] + }, + { + "name": "Styles should be written using objects.", + "files": [ + { + "path": "/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx", + "count": 5 + }, + { + "path": "/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx", + "count": 6 + }, + { + "path": "/packages/grafana-flamegraph/src/FlameGraphHeader.tsx", + "count": 13 + }, + { + "path": "/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromExemplarField.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx", + "count": 12 + }, + { + "path": "/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx", + "count": 16 + }, + { + "path": "/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx", + "count": 9 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx", + "count": 13 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts", + "count": 19 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx", + "count": 5 + }, + { + "path": "/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/NestedFolderPicker/Trigger.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/PageHeader/PageHeader.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx", + "count": 7 + }, + { + "path": "/public/app/core/components/RolePicker/RolePickerInput.tsx", + "count": 7 + }, + { + "path": "/public/app/core/components/RolePicker/ValueContainer.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/TagFilter/TagFilter.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/TagFilter/TagOption.tsx", + "count": 2 + }, + { + "path": "/public/app/core/components/Upgrade/ProBadge.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/Upgrade/UpgradeBox.tsx", + "count": 18 + }, + { + "path": "/public/app/features/admin/LicenseChrome.tsx", + "count": 3 + }, + { + "path": "/public/app/features/admin/UpgradePage.tsx", + "count": 5 + }, + { + "path": "/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx", + "count": 8 + }, + { + "path": "/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx", + "count": 2 + }, + { + "path": "/public/app/features/admin/UserOrgs.tsx", + "count": 17 + }, + { + "path": "/public/app/features/admin/UserPermissions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/admin/UserProfile.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/AlertWarning.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/AlertsFolderView.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx", + "count": 15 + }, + { + "path": "/public/app/features/alerting/unified/NotificationPolicies.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/PanelAlertTabContent.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/RedirectToRuleViewer.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/RuleList.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/AlertLabel.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/AlertLabels.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/AlertManagerPicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/AlertStateDot.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/DetailsField.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/DynamicTable.tsx", + "count": 7 + }, + { + "path": "/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx", + "count": 6 + }, + { + "path": "/public/app/features/alerting/unified/components/EmptyArea.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/Expression.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/Label.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/MetaText.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/Spacer.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/StateColoredText.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/StateTag.tsx", + "count": 9 + }, + { + "path": "/public/app/features/alerting/unified/components/Tokenize.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/Well.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/admin/AlertmanagerConfigSelector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/export/FileExportPreview.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/expressions/Expression.tsx", + "count": 25 + }, + { + "path": "/public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeRange.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/notification-policies/Matchers.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/notification-policies/PromDurationDocs.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/notification-policies/formStyles.ts", + "count": 6 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx", + "count": 18 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx", + "count": 6 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx", + "count": 11 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/CloudAlertPreview.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/CloudEvaluationBehavior.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx", + "count": 15 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/ExpressionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx", + "count": 11 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/NeedHelpInfo.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/QueryOptions.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPolicyMatchers.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.tsx", + "count": 7 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx", + "count": 9 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx", + "count": 11 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/ActionButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/CloudRules.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx", + "count": 7 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetails.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleHealth.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleState.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RulesGroup.tsx", + "count": 12 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RulesTable.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx", + "count": 6 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/state-history/StateHistory.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/Matchers.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/MatchersField.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilenceDetails.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencePeriod.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx", + "count": 4 + }, + { + "path": "/public/app/features/alerting/unified/components/silences/SilencesTable.tsx", + "count": 5 + }, + { + "path": "/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx", + "count": 2 + }, + { + "path": "/public/app/features/alerting/unified/styles/notifications.ts", + "count": 3 + }, + { + "path": "/public/app/features/alerting/unified/styles/pagination.ts", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/styles/table.ts", + "count": 5 + }, + { + "path": "/public/app/features/api-keys/ApiKeysTable.tsx", + "count": 2 + }, + { + "path": "/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx", + "count": 3 + }, + { + "path": "/public/app/features/canvas/runtime/scene.tsx", + "count": 2 + }, + { + "path": "/public/app/features/connections/components/ConnectionsRedirectNotice/ConnectionsRedirectNotice.tsx", + "count": 2 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/CategoryHeader/CategoryHeader.tsx", + "count": 2 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/NoAccessModal/NoAccessModal.tsx", + "count": 9 + }, + { + "path": "/public/app/features/correlations/CorrelationsPage.tsx", + "count": 4 + }, + { + "path": "/public/app/features/correlations/Forms/AddCorrelationForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/correlations/Forms/ConfigureCorrelationBasicInfoForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashNav/DashNavButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx", + "count": 10 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx", + "count": 5 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx", + "count": 10 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx", + "count": 10 + }, + { + "path": "/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx", + "count": 6 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx", + "count": 6 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/SubMenu.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx", + "count": 10 + }, + { + "path": "/public/app/features/datasources/components/BasicSettings.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/DataSourceTestingStatus.tsx", + "count": 2 + }, + { + "path": "/public/app/features/datasources/components/DataSourceTypeCardList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/picker/DataSourceCard.tsx", + "count": 9 + }, + { + "path": "/public/app/features/datasources/components/picker/DataSourceList.tsx", + "count": 4 + }, + { + "path": "/public/app/features/datasources/components/picker/DataSourceLogo.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/picker/DataSourceModal.tsx", + "count": 10 + }, + { + "path": "/public/app/features/dimensions/editors/ColorDimensionEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dimensions/editors/FileUploader.tsx", + "count": 8 + }, + { + "path": "/public/app/features/dimensions/editors/FolderPickerTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ResourceCards.tsx", + "count": 5 + }, + { + "path": "/public/app/features/dimensions/editors/ResourcePicker.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dimensions/editors/ResourcePickerPopover.tsx", + "count": 5 + }, + { + "path": "/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx", + "count": 8 + }, + { + "path": "/public/app/features/dimensions/editors/URLPickerTab.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/Logs/LiveLogs.tsx", + "count": 5 + }, + { + "path": "/public/app/features/explore/Logs/LogsMetaRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/Logs/LogsNavigation.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/Logs/LogsNavigationPages.tsx", + "count": 5 + }, + { + "path": "/public/app/features/explore/Logs/LogsSamplePanel.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/Logs/LogsVolumePanel.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/Logs/LogsVolumePanelList.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/Logs/utils/LogsCrossFadeTransition.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/NodeGraph/NodeGraphContainer.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/PrometheusListView/ItemLabels.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/PrometheusListView/ItemValues.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/PrometheusListView/RawListContainer.tsx", + "count": 6 + }, + { + "path": "/public/app/features/explore/PrometheusListView/RawListItem.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/PrometheusListView/RawListItemAttributes.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/RichHistory/RichHistoryCard.tsx", + "count": 14 + }, + { + "path": "/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx", + "count": 14 + }, + { + "path": "/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx", + "count": 5 + }, + { + "path": "/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx", + "count": 7 + }, + { + "path": "/public/app/features/explore/TraceView/TraceView.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/Actions/ActionButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx", + "count": 6 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx", + "count": 5 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx", + "count": 6 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/GraphTicks.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/Scrubber.tsx", + "count": 5 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/TickLabels.tsx", + "count": 2 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx", + "count": 8 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx", + "count": 9 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx", + "count": 22 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx", + "count": 8 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx", + "count": 14 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx", + "count": 6 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx", + "count": 11 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx", + "count": 2 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx", + "count": 6 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/Ticks.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx", + "count": 8 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx", + "count": 8 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx", + "count": 3 + }, + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/common/BreakableText.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/common/CopyIcon.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/common/LabeledList.tsx", + "count": 4 + }, + { + "path": "/public/app/features/explore/TraceView/components/common/NewWindowIcon.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/common/TraceName.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/settings/SpanBarSettings.tsx", + "count": 2 + }, + { + "path": "/public/app/features/expressions/components/Condition.tsx", + "count": 3 + }, + { + "path": "/public/app/features/expressions/components/Math.tsx", + "count": 7 + }, + { + "path": "/public/app/features/geo/editor/GazetteerPathEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/geo/editor/locationModeEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/inspector/DetailText.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectStatsTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectStatsTable.tsx", + "count": 2 + }, + { + "path": "/public/app/features/inspector/InspectStatsTraceIdsTable.tsx", + "count": 2 + }, + { + "path": "/public/app/features/inspector/QueryInspector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/styles.ts", + "count": 11 + }, + { + "path": "/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx", + "count": 3 + }, + { + "path": "/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/styles.ts", + "count": 6 + }, + { + "path": "/public/app/features/live/LiveConnectionWarning.tsx", + "count": 2 + }, + { + "path": "/public/app/features/logs/components/LoadingIndicator.tsx", + "count": 1 + }, + { + "path": "/public/app/features/logs/components/LogDetailsRow.tsx", + "count": 7 + }, + { + "path": "/public/app/features/logs/components/LogLabelStats.tsx", + "count": 5 + }, + { + "path": "/public/app/features/logs/components/LogLabelStatsRow.tsx", + "count": 8 + }, + { + "path": "/public/app/features/logs/components/LogLabels.tsx", + "count": 3 + }, + { + "path": "/public/app/features/logs/components/LogRowMessageDisplayedFields.tsx", + "count": 1 + }, + { + "path": "/public/app/features/logs/components/getLogRowStyles.ts", + "count": 36 + }, + { + "path": "/public/app/features/logs/components/log-context/LogRowContextModal.tsx", + "count": 13 + }, + { + "path": "/public/app/features/manage-dashboards/DashboardImportPage.tsx", + "count": 3 + }, + { + "path": "/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx", + "count": 2 + }, + { + "path": "/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx", + "count": 2 + }, + { + "path": "/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx", + "count": 6 + }, + { + "path": "/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx", + "count": 5 + }, + { + "path": "/public/app/features/playlist/PlaylistTableRows.tsx", + "count": 4 + }, + { + "path": "/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/Badges/sharedStyles.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/HorizontalGroup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginActions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx", + "count": 2 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx", + "count": 2 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsPage.tsx", + "count": 3 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx", + "count": 4 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginSubtitle.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginUsage.tsx", + "count": 2 + }, + { + "path": "/public/app/features/plugins/admin/components/VersionList.tsx", + "count": 3 + }, + { + "path": "/public/app/features/plugins/admin/hooks/usePluginInfo.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryEditorRowHeader.tsx", + "count": 8 + }, + { + "path": "/public/app/features/query/components/QueryGroup.tsx", + "count": 7 + }, + { + "path": "/public/app/features/query/components/QueryGroupOptions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/search/page/components/ActionRow.tsx", + "count": 2 + }, + { + "path": "/public/app/features/search/page/components/SearchResultsTable.tsx", + "count": 13 + }, + { + "path": "/public/app/features/serviceaccounts/components/ServiceAccountProfile.tsx", + "count": 1 + }, + { + "path": "/public/app/features/serviceaccounts/components/ServiceAccountProfileRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/serviceaccounts/components/ServiceAccountTokensTable.tsx", + "count": 7 + }, + { + "path": "/public/app/features/storage/Breadcrumb.tsx", + "count": 1 + }, + { + "path": "/public/app/features/storage/FileView.tsx", + "count": 7 + }, + { + "path": "/public/app/features/storage/FolderView.tsx", + "count": 5 + }, + { + "path": "/public/app/features/storage/RootView.tsx", + "count": 2 + }, + { + "path": "/public/app/features/storage/StoragePage.tsx", + "count": 6 + }, + { + "path": "/public/app/features/storage/UploadButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/GroupByTransformerEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/transformers/extractFields/components/JSONPathEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/features/users/TokenRevokedModal.tsx", + "count": 3 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorListRow.tsx", + "count": 7 + }, + { + "path": "/public/app/features/variables/inspect/VariablesUnknownTable.tsx", + "count": 5 + }, + { + "path": "/public/app/features/variables/pickers/shared/VariableLink.tsx", + "count": 2 + }, + { + "path": "/public/app/features/visualization/data-hover/DataHoverRows.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/XrayLinkConfig.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/DynamicLabelsField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/styles.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx", + "count": 6 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/TimePickerInput.tsx", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx", + "count": 10 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/TagsSection.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/flux/FluxQueryEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/CheatSheet.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/jaeger/configuration/ConfigEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/configuration/TraceIdTimeParams.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/loki/components/LokiContextUi.tsx", + "count": 11 + }, + { + "path": "/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx", + "count": 14 + }, + { + "path": "/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/loki/configuration/DerivedField.tsx", + "count": 7 + }, + { + "path": "/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/components/OpenTsdbQueryEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/parca/QueryEditor/LabelsEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/zipkin/ConfigEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/alertlist/AlertInstances.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/alertlist/UnifiedAlertList.tsx", + "count": 17 + }, + { + "path": "/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx", + "count": 7 + }, + { + "path": "/public/app/plugins/panel/annolist/AnnoListPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/globalStyles.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/dashlist/styles.ts", + "count": 7 + }, + { + "path": "/public/app/plugins/panel/datagrid/utils.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx", + "count": 6 + }, + { + "path": "/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/live/LiveChannelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/live/LivePanel.tsx", + "count": 7 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/Legend.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/Marker.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/Node.tsx", + "count": 9 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/NodeGraph.tsx", + "count": 10 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/ViewControls.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/editor/ArcOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/useContextMenu.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/piechart/PieChart.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/panel/table/TablePanel.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/text/TextPanel.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/text/TextPanelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/TimezonesEditor.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx", + "count": 7 + }, + { + "path": "/public/app/plugins/panel/traces/TracesPanel.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/welcome/Welcome.tsx", + "count": 6 + }, + { + "path": "/public/app/plugins/panel/xychart/AutoEditor.tsx", + "count": 2 + } + ] + }, + { + "name": "'HorizontalGroup' import from '@grafana/ui' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-runtime/src/components/DataSourcePicker.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Layout/Layout.story.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx", + "count": 1 + }, + { + "path": "/public/app/features/admin/UserOrgs.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/admin/AlertmanagerConfigSelector.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx", + "count": 1 + }, + { + "path": "/public/app/features/alerting/unified/components/rules/RulesGroup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/api-keys/ApiKeysTable.tsx", + "count": 1 + }, + { + "path": "/public/app/features/correlations/Forms/CorrelationFormNavigation.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/VersionsEditView.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/CorrelationEditorModeBar.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx", + "count": 1 + }, + { + "path": "/public/app/features/geo/editor/locationModeEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectDataOptions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginActions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginListItemBadges.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/components/PluginsErrorsInfo.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryGroup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/serviceaccounts/ServiceAccountPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/storage/RootView.tsx", + "count": 1 + }, + { + "path": "/public/app/features/storage/StoragePage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/EnumMappingEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/EnumMappingRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/FilterByRefIdTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/joinByLabels/JoinByLabelsTransformerEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/partitionByValues/PartitionByValuesEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/inspect/VariablesUnknownTable.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/code/RawInfluxQLEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/QueryField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/SearchTraceQLEditor/DurationInput.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/zipkin/QueryField.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/barchart/TickSpacingEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/ParamsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/PlacementEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/panZoomHelp.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/editor/StyleEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/ViewControls.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/InsertNullsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/LineStyleEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/SpanNullsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx", + "count": 1 + } + ] + }, + { + "name": "'VerticalGroup' import from '@grafana/ui' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Alert/Toast.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Layout/Layout.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/AppNotifications/AppNotificationList.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/ForgottenPassword/ChangePassword.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/Login/LoginServiceButtons.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/Login/UserSignup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/extensions/ConfirmNavigationModal.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectDataOptions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx", + "count": 1 + }, + { + "path": "/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx", + "count": 1 + }, + { + "path": "/public/app/features/org/OrgDetailsPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginActions.tsx", + "count": 1 + }, + { + "path": "/public/app/features/profile/UserProfileEditPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/sandbox/BenchmarksPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/storage/RootView.tsx", + "count": 1 + }, + { + "path": "/public/app/features/transformers/editors/EnumMappingEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/inspect/VariablesUnknownTable.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/ParamsEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/element/PlacementEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/canvas/editor/panZoomHelp.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/geomap/editor/MapViewEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/ViewControls.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx", + "count": 1 + } + ] + }, + { + "name": "'VerticalGroup' import from '../Layout/Layout' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/packages/grafana-ui/src/components/Dropdown/Dropdown.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Link/TextLink.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Menu/Menu.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Text/Text.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx", + "count": 1 + } + ] + }, + { + "name": "Use data-testid for E2E selectors instead of aria-label", + "files": [ + { + "path": "/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx", + "count": 4 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx", + "count": 3 + }, + { + "path": "/public/app/features/dashboard/containers/DashboardPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/DataSourceTypeCard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectDataTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectJSONTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/InspectStatsTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/inspector/QueryInspector.tsx", + "count": 2 + }, + { + "path": "/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/playlist/PlaylistForm.tsx", + "count": 2 + }, + { + "path": "/public/app/features/query/components/QueryEditorRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryEditorRowHeader.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryGroup.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorList.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/editor/VariableEditorListRow.tsx", + "count": 4 + }, + { + "path": "/public/app/features/variables/pickers/shared/VariableOptions.tsx", + "count": 1 + } + ] + }, + { + "name": "'HorizontalGroup' import from './Layout/Layout' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/packages/grafana-ui/src/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "'VerticalGroup' import from './Layout/Layout' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/packages/grafana-ui/src/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`updateNavIndex`)", + "files": [ + { + "path": "/public/app/core/actions/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`updateConfigurationSubtitle`)", + "files": [ + { + "path": "/public/app/core/actions/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`notifyApp`)", + "files": [ + { + "path": "/public/app/core/actions/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`hideAppNotification`)", + "files": [ + { + "path": "/public/app/core/actions/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not use export all (`export * from ...`)", + "files": [ + { + "path": "/public/app/core/components/AccessControl/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/auth-config/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/browse-dashboards/state/index.ts", + "count": 3 + }, + { + "path": "/public/app/features/canvas/index.ts", + "count": 3 + }, + { + "path": "/public/app/features/connections/components/ConnectionsRedirectNotice/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/CardGrid/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/CategoryHeader/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/NoAccessModal/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/Search/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/connections/tabs/ConnectData/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/correlations/components/Wizard/index.ts", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/dataframe-import/index.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/__mocks__/index.ts", + "count": 2 + }, + { + "path": "/public/app/features/datasources/state/index.ts", + "count": 7 + }, + { + "path": "/public/app/features/dimensions/editors/index.ts", + "count": 6 + }, + { + "path": "/public/app/features/dimensions/index.ts", + "count": 8 + }, + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 3 + }, + { + "path": "/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/migrate-to-cloud/api/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/__mocks__/index.ts", + "count": 1 + }, + { + "path": "/public/app/features/search/service/index.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/azureMetadata/index.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/types/index.ts", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/heatmap/types.ts", + "count": 1 + }, + { + "path": "/public/app/types/index.ts", + "count": 21 + } + ] + }, + { + "name": "Do not re-export imported variable (`./LocalStorageValueProvider`)", + "files": [ + { + "path": "/public/app/core/components/LocalStorageValueProvider/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`config`)", + "files": [ + { + "path": "/public/app/core/config.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/webpack.config.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/webpack.config.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/parca/webpack.config.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/webpack.config.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/zipkin/webpack.config.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`profiler`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`appEvents`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`colors`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`assignModelProperties`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`contextSrv`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`JsonExplorer`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`TimeSeries`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`updateLegendValues`)", + "files": [ + { + "path": "/public/app/core/core.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`RichHistorySearchFilters`)", + "files": [ + { + "path": "/public/app/core/utils/richHistory.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`RichHistorySettings`)", + "files": [ + { + "path": "/public/app/core/utils/richHistory.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`SortOrder`)", + "files": [ + { + "path": "/public/app/core/utils/richHistory.ts", + "count": 1 + } + ] + }, + { + "name": "'HorizontalGroup' import from '@grafana/ui/src' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx", + "count": 1 + }, + { + "path": "/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`app/features/explore/extensions/ToolbarExtensionPointMenu`)", + "files": [ + { + "path": "/public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPointMenu.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`@reduxjs/toolkit/query/react`)", + "files": [ + { + "path": "/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./frame`)", + "files": [ + { + "path": "/public/app/features/canvas/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`Placement`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`Constraint`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`HorizontalConstraint`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`VerticalConstraint`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`BackgroundImageSize`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`LineConfig`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`BackgroundConfig`)", + "files": [ + { + "path": "/public/app/features/canvas/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AddNewConnectionPage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DataSourceDetailsPage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DataSourcesListPage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DataSourceDashboardsPage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./EditDataSourcePage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./NewDataSourcePage`)", + "files": [ + { + "path": "/public/app/features/connections/pages/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AnnotationSettingsEdit`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/annotations/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AnnotationSettingsList`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/annotations/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/AnnotationSettings/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./HistorySrv`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/version-history/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./VersionHistoryTable`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/version-history/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./VersionHistoryHeader`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/version-history/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./VersionHistoryButtons`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/version-history/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./VersionHistoryComparison`)", + "files": [ + { + "path": "/public/app/features/dashboard-scene/settings/version-history/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AddLibraryPanelWidget`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/AddLibraryPanelWidget/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DashboardExporter`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/DashExportModal/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`DashNav`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/DashNav/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DashboardRow`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/DashboardRow/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DashboardSettings`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/DashboardSettings/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./LinkSettingsEdit`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/LinksSettings/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./LinkSettingsList`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/LinksSettings/index.tsx", + "count": 1 + } + ] + }, + { + "name": "'Layout' import from '@grafana/ui/src/components/Layout/Layout' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/PluginDetailsPage.tsx", + "count": 1 + } + ] + }, + { + "name": "'VerticalGroup' import from '@grafana/ui/src' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./ShareModal`)", + "files": [ + { + "path": "/public/app/features/dashboard/components/ShareModal/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DashboardModel`)", + "files": [ + { + "path": "/public/app/features/dashboard/state/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PanelModel`)", + "files": [ + { + "path": "/public/app/features/dashboard/state/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TracePageHeader`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/TracePageHeader/index.tsx", + "count": 1 + }, + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TimelineHeaderRow`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`../utils/date`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TraceTimelineViewer`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./settings/SpanBarSettings`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TraceTimelineViewer/SpanDetail/DetailState`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./model/transform-trace-data`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./utils/filter-spans`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PathElem`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/model/ddg/types.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`PathElem`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/model/ddg/types.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./trace`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/types/index.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`../settings/SpanBarSettings`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/types/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TTraceTimeline`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/types/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TNil`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/types/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./links`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/types/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DraggableManagerDemo`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./EUpdateTypes`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DraggableManager`)", + "files": [ + { + "path": "/public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./external.utils`)", + "files": [ + { + "path": "/public/app/features/explore/hooks/useStateSync/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./remotePlugin.mock`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/__mocks__/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./localPlugin.mock`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/__mocks__/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginDisabledBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginInstallBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginEnterpriseBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginUpdateAvailableBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginAngularBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PluginDeprecatedBadge`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/Badges/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./GetStartedWithPlugin`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./InstallControlsWarning`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/InstallControls/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./InstallControlsButton`)", + "files": [ + { + "path": "/public/app/features/plugins/admin/components/InstallControls/index.tsx", + "count": 1 + } + ] + }, + { + "name": "* import is invalid because 'Layout,HorizontalGroup,VerticalGroup' from '@grafana/ui' is restricted from being used by a pattern. Use Stack component instead.", + "files": [ + { + "path": "/public/app/features/plugins/loader/sharedDependencies.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./searcher`)", + "files": [ + { + "path": "/public/app/features/search/service/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`CalculateFieldTransformerEditor`)", + "files": [ + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`calculateFieldTransformRegistryItem`)", + "files": [ + { + "path": "/public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./OptionsPicker/OptionsPicker`)", + "files": [ + { + "path": "/public/app/features/variables/pickers/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`@grafana/data`)", + "files": [ + { + "path": "/public/app/features/variables/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`VariableHide`)", + "files": [ + { + "path": "/public/app/features/variables/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./ArgQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./LogsQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./QueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/QueryEditor/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./ResourceField`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/ResourceField/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./ResourcePicker`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./TracesQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`AzureQueryType`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/types/query.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`../dataquery.gen`)", + "files": [ + { + "path": "/public/app/plugins/datasource/azuremonitor/types/query.ts", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/azuremonitor/types/templateVariables.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloud-monitoring/types/query.ts", + "count": 2 + } + ] + }, + { + "name": "Do not re-export imported variable (`./Project`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./GroupBy`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./Alignment`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./LabelFilter`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AnnotationsHelp`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AlignmentFunction`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./AliasBy`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./Aggregation`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./MetricQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./SLOQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./MQLQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./Fields`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./VisualMetricQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./PeriodSelect`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./Preprocessor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`QueryType`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloud-monitoring/types/query.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./empty`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./whitespaceQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./commentOnlyQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./singleLineFullQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./multiLineFullQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./filterQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./newCommandQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./sortQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./singleLineEmptyQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./singleLineTwoQueries`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./multiLineIncompleteQueryWithoutNamespace`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./afterLabelValue`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./insideLabelValue`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./afterFunctionQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./secondArgQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./secondArgAfterSearchQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./thirdArgAfterSearchQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./withinStringQuery`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./SQLBuilderEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/index.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./MetricsQueryEditor/SQLCodeEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./MetricStatEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./dataquery.gen`)", + "files": [ + { + "path": "/public/app/plugins/datasource/cloudwatch/expressions.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./runSharedRequest`)", + "files": [ + { + "path": "/public/app/plugins/datasource/dashboard/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./DashboardQueryEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/dashboard/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./types`)", + "files": [ + { + "path": "/public/app/plugins/datasource/dashboard/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./StreamingClientEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./RandomWalkEditor`)", + "files": [ + { + "path": "/public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`LokiQueryDirection`)", + "files": [ + { + "path": "/public/app/plugins/datasource/loki/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`LokiQueryType`)", + "files": [ + { + "path": "/public/app/plugins/datasource/loki/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`SupportingQueryType`)", + "files": [ + { + "path": "/public/app/plugins/datasource/loki/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`MixedDatasource`)", + "files": [ + { + "path": "/public/app/plugins/datasource/mixed/module.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`Options`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`CandlestickColors`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`defaultCandlestickColors`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`CandleStyle`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`ColorStrategy`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`VizDisplayMode`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`CandlestickFieldMap`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`FieldConfig`)", + "files": [ + { + "path": "/public/app/plugins/panel/candlestick/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./panelcfg.gen`)", + "files": [ + { + "path": "/public/app/plugins/panel/geomap/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/logs/types.ts", + "count": 1 + }, + { + "path": "/public/app/plugins/panel/nodeGraph/types.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`./NodeGraph`)", + "files": [ + { + "path": "/public/app/plugins/panel/nodeGraph/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`loadPluginCss`)", + "files": [ + { + "path": "/public/app/plugins/sdk.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`OrgRole`)", + "files": [ + { + "path": "/public/app/types/acl.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`CoreEvents`)", + "files": [ + { + "path": "/public/app/types/index.ts", + "count": 1 + } + ] + }, + { + "name": "Do not re-export imported variable (`TeamDTO`)", + "files": [ + { + "path": "/public/app/types/teams.ts", + "count": 1 + } + ] + } + ], + "no undocumented stories": [ + { + "name": "No undocumented stories are allowed, please add an .mdx file with some documentation", + "files": [ + { + "path": "/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/Segment.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Slider/Slider.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx", + "count": 1 + } + ] + } + ], + "no gf-form usage": [ + { + "name": "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", + "files": [ + { + "path": "/e2e/utils/flows/addDataSource.ts", + "count": 1 + }, + { + "path": "/packages/grafana-e2e/src/flows/addDataSource.ts", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-prometheus/src/components/PromQueryField.tsx", + "count": 6 + }, + { + "path": "/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-prometheus/src/configuration/PromSettings.tsx", + "count": 27 + }, + { + "path": "/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx", + "count": 16 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/FormField/FormField.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx", + "count": 3 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx", + "count": 6 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx", + "count": 4 + }, + { + "path": "/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx", + "count": 8 + }, + { + "path": "/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/Segment.story.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx", + "count": 2 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx", + "count": 1 + }, + { + "path": "/packages/grafana-ui/src/components/Segment/SegmentInput.tsx", + "count": 2 + }, + { + "path": "/public/app/angular/components/code_editor/code_editor.ts", + "count": 1 + }, + { + "path": "/public/app/angular/components/form_dropdown/form_dropdown.ts", + "count": 4 + }, + { + "path": "/public/app/angular/components/info_popover.ts", + "count": 2 + }, + { + "path": "/public/app/angular/components/switch.ts", + "count": 8 + }, + { + "path": "/public/app/angular/dropdown_typeahead.ts", + "count": 4 + }, + { + "path": "/public/app/angular/metric_segment.ts", + "count": 4 + }, + { + "path": "/public/app/angular/misc.ts", + "count": 2 + }, + { + "path": "/public/app/angular/panel/partials/query_editor_row.html", + "count": 1 + }, + { + "path": "/public/app/angular/partials/tls_auth_settings.html", + "count": 35 + }, + { + "path": "/public/app/core/components/AccessControl/PermissionList.tsx", + "count": 1 + }, + { + "path": "/public/app/core/components/PageHeader/PageHeader.tsx", + "count": 2 + }, + { + "path": "/public/app/features/admin/UserLdapSyncInfo.tsx", + "count": 3 + }, + { + "path": "/public/app/features/admin/partials/edit_org.html", + "count": 8 + }, + { + "path": "/public/app/features/admin/partials/styleguide.html", + "count": 4 + }, + { + "path": "/public/app/features/annotations/partials/event_editor.html", + "count": 7 + }, + { + "path": "/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx", + "count": 1 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx", + "count": 2 + }, + { + "path": "/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/BasicSettings.tsx", + "count": 3 + }, + { + "path": "/public/app/features/datasources/components/ButtonRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/DataSourceLoadError.tsx", + "count": 1 + }, + { + "path": "/public/app/features/datasources/components/DataSourcePluginState.tsx", + "count": 4 + }, + { + "path": "/public/app/features/datasources/components/DataSourceTestingStatus.tsx", + "count": 1 + }, + { + "path": "/public/app/features/plugins/admin/components/AppConfigWrapper.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryEditorRow.tsx", + "count": 1 + }, + { + "path": "/public/app/features/query/components/QueryGroupOptions.tsx", + "count": 17 + }, + { + "path": "/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx", + "count": 5 + }, + { + "path": "/public/app/features/variables/adhoc/picker/AdHocFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx", + "count": 3 + }, + { + "path": "/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx", + "count": 1 + }, + { + "path": "/public/app/features/variables/adhoc/picker/ConditionSegment.tsx", + "count": 2 + }, + { + "path": "/public/app/features/variables/pickers/PickerRenderer.tsx", + "count": 5 + }, + { + "path": "/public/app/features/variables/pickers/shared/VariableInput.tsx", + "count": 1 + }, + { + "path": "/public/app/partials/confirm_modal.html", + "count": 1 + }, + { + "path": "/public/app/partials/reset_password.html", + "count": 13 + }, + { + "path": "/public/app/partials/signup_invited.html", + "count": 14 + }, + { + "path": "/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/XrayLinkConfig.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/grafana-pyroscope-datasource/ConfigEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/annotation/AnnotationEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/QueryEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/flux/FluxQueryEditor.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx", + "count": 5 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/influxdb/components/editor/variable/VariableQueryEditor.tsx", + "count": 4 + }, + { + "path": "/public/app/plugins/datasource/loki/components/LokiQueryField.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/loki/configuration/DerivedField.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/opentsdb/components/AnnotationEditor.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/prometheus/configuration/AzureCredentialsForm.tsx", + "count": 21 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilter.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterKey.tsx", + "count": 3 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterValue.tsx", + "count": 1 + }, + { + "path": "/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/ConditionSegment.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/datasource/zipkin/QueryField.tsx", + "count": 2 + }, + { + "path": "/public/app/plugins/panel/graph/axes_editor.html", + "count": 50 + }, + { + "path": "/public/app/plugins/panel/graph/tab_display.html", + "count": 56 + }, + { + "path": "/public/app/plugins/panel/graph/tab_legend.html", + "count": 42 + }, + { + "path": "/public/app/plugins/panel/graph/tab_series_overrides.html", + "count": 10 + }, + { + "path": "/public/app/plugins/panel/graph/thresholds_form.html", + "count": 31 + }, + { + "path": "/public/app/plugins/panel/graph/time_regions_form.html", + "count": 32 + }, + { + "path": "/public/app/plugins/panel/heatmap/partials/axes_editor.html", + "count": 53 + }, + { + "path": "/public/app/plugins/panel/heatmap/partials/display_editor.html", + "count": 51 + }, + { + "path": "/public/app/plugins/panel/table-old/column_options.html", + "count": 91 + }, + { + "path": "/public/app/plugins/panel/table-old/editor.html", + "count": 22 + } + ] + } + ] } diff --git a/.betterer.ts b/.betterer.ts index 0fd570add6b..44267d58a5a 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -105,6 +105,16 @@ function countEslintErrors() { '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }], }; + const grafanaRules: Partial = { + ...nonTestFilesRules, + 'no-barrel-files/no-barrel-files': 'error', + }; + + const testFilesAndGrafanaRules: Partial = { + ...baseRules, + 'no-barrel-files/no-barrel-files': 'error', + }; + // group files by eslint config file // this will create two file groups for each eslint config file // one for test files and one for non-test files @@ -117,10 +127,16 @@ function countEslintErrors() { filePath.endsWith('.test.ts') || filePath.includes('__mocks__') || filePath.includes('public/test/'); + const isGrafanaFile = filePath.includes('public/app/'); - if (isTestFile) { + if (isGrafanaFile && isTestFile) { + configPath += '-test-grafana'; + } else if (isGrafanaFile) { + configPath += '-grafana'; + } else if (isTestFile) { configPath += '-test'; } + if (!fileGroups[configPath]) { fileGroups[configPath] = []; } @@ -128,7 +144,16 @@ function countEslintErrors() { } for (const configPath of Object.keys(fileGroups)) { - const rules = configPath.endsWith('-test') ? baseRules : nonTestFilesRules; + let rules; + if (configPath.endsWith('-test-grafana')) { + rules = testFilesAndGrafanaRules; + } else if (configPath.endsWith('-test')) { + rules = baseRules; + } else if (configPath.endsWith('-grafana')) { + rules = grafanaRules; + } else { + rules = nonTestFilesRules; + } // this is by far the slowest part of this code. It takes eslint about 2 seconds just to find the config const linterOptions = (await cli.calculateConfigForFile(fileGroups[configPath][0])) as Linter.Config; const runner = new ESLint({ diff --git a/.drone.yml b/.drone.yml index 624324422f1..6f896b5526f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -658,7 +658,8 @@ steps: repo: - grafana/grafana - commands: - - sleep 10s + - npx wait-on@7.0.1 http://$HOST:$PORT + - yarn playwright install --with-deps chromium - yarn e2e:playwright depends_on: - grafana-server @@ -666,7 +667,7 @@ steps: HOST: grafana-server PORT: "3001" PROV_DIR: /grafana/scripts/grafana-server/tmp/conf/provisioning - image: mcr.microsoft.com/playwright:v1.43.0-jammy + image: node:20-bookworm name: playwright-plugin-e2e - commands: - apt-get update @@ -1988,7 +1989,8 @@ steps: repo: - grafana/grafana - commands: - - sleep 10s + - npx wait-on@7.0.1 http://$HOST:$PORT + - yarn playwright install --with-deps chromium - yarn e2e:playwright depends_on: - grafana-server @@ -1996,7 +1998,7 @@ steps: HOST: grafana-server PORT: "3001" PROV_DIR: /grafana/scripts/grafana-server/tmp/conf/provisioning - image: mcr.microsoft.com/playwright:v1.43.0-jammy + image: node:20-bookworm name: playwright-plugin-e2e - commands: - apt-get update @@ -4635,6 +4637,7 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.21.9-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20.9.0-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20-bookworm - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana-ci-deploy:1.3.3 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.19.1 @@ -4656,7 +4659,6 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:13.1.0 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM jwilder/dockerize:0.6.1 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM koalaman/shellcheck:stable - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM mcr.microsoft.com/playwright:v1.43.0-jammy depends_on: - authenticate-gcr image: aquasec/trivy:0.21.0 @@ -4670,6 +4672,7 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.21.9-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:20.9.0-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL node:20-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana-ci-deploy:1.3.3 - trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.19.1 @@ -4691,7 +4694,6 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:13.1.0 - trivy --exit-code 1 --severity HIGH,CRITICAL jwilder/dockerize:0.6.1 - trivy --exit-code 1 --severity HIGH,CRITICAL koalaman/shellcheck:stable - - trivy --exit-code 1 --severity HIGH,CRITICAL mcr.microsoft.com/playwright:v1.43.0-jammy depends_on: - authenticate-gcr environment: @@ -4923,6 +4925,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 2f4db495ad0b69ef6afd86eac7aea7101f0b8f6ec1aadea0e2860b196304a258 +hmac: fbd59890dac44eb6fb34562f2b2b2db0fc0a8a50d451f9887f815ee35757e0f6 ... diff --git a/.eslintrc b/.eslintrc index b64d731e0c9..dac72ca97e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "extends": ["@grafana/eslint-config"], "root": true, - "plugins": ["@emotion", "lodash", "jest", "import", "jsx-a11y", "@grafana"], + "plugins": ["@emotion", "lodash", "jest", "import", "jsx-a11y", "@grafana", "no-barrel-files"], "settings": { "import/internal-regex": "^(app/)|(@grafana)", "import/external-module-folders": ["node_modules", ".yarn"] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c3a77ff27cd..a6f3f4fd147 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -151,7 +151,6 @@ /pkg/tsdb/grafanads/ @grafana/backend-platform /pkg/tsdb/legacydata/ @grafana/backend-platform /pkg/tsdb/opentsdb/ @grafana/backend-platform -/pkg/tsdb/sqleng/ @grafana/partner-datasources @grafana/oss-big-tent /pkg/util/ @grafana/backend-platform /pkg/web/ @grafana/backend-platform @@ -376,7 +375,6 @@ /.levignore.js @grafana/plugins-platform-frontend playwright.config.ts @grafana/plugins-platform-frontend - # public folder /public/app/core/ @grafana/grafana-frontend-platform /public/app/core/components/TimePicker/ @grafana/grafana-frontend-platform @@ -385,6 +383,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/core/components/TimeSeries/ @grafana/dataviz-squad /public/app/core/components/TimelineChart/ @grafana/dataviz-squad /public/app/core/components/Form/ @grafana/grafana-frontend-platform +/public/app/core/history/ @grafana/explore-squad /public/app/features/all.ts @grafana/grafana-frontend-platform /public/app/features/admin/ @grafana/identity-access-team @@ -403,7 +402,6 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/dashboard/ @grafana/dashboards-squad /public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad /public/app/features/dashboard-scene/ @grafana/dashboards-squad -/public/app/features/scopes/ @grafana/dashboards-squad /public/app/features/datasources/ @grafana/plugins-platform-frontend @mikkancso /public/app/features/dimensions/ @grafana/dataviz-squad /public/app/features/dataframe-import/ @grafana/dataviz-squad @@ -537,6 +535,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /scripts/generate-icon-bundle.js @grafana/plugins-platform-frontend @grafana/grafana-frontend-platform /scripts/generate-rtk-apis.ts @grafana/grafana-frontend-platform /scripts/levitate-parse-json-report.js @grafana/plugins-platform-frontend +/scripts/codemods/explicit-barrel-imports.cjs @grafana/frontend-ops /scripts/**/generate-transformations* @grafana/dataviz-squad /scripts/webpack/ @grafana/frontend-ops @@ -695,8 +694,6 @@ embed.go @grafana/grafana-as-code /.github/workflows/core-plugins-build-and-release.yml @grafana/plugins-platform-frontend @grafana/plugins-platform-backend /.github/workflows/i18n-crowdin-upload.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform -/.github/workflows/feature-toggle-cleanup.yml @tolzhabayev -/.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.js @tolzhabayev /.github/workflows/pr-go-workspace-check.yml @grafana/grafana-app-platform-squad # Generated files not requiring owner approval diff --git a/.github/bot.md b/.github/bot.md index 9477ab291e2..6eae334f4fa 100644 --- a/.github/bot.md +++ b/.github/bot.md @@ -4,8 +4,8 @@ The bot is configured via [commands.json](https://github.com/grafana/grafana/blo Comment commands: -* Write the word `/duplicate #` anywhere in a comment and the bot will add the correct label and standard message. -* Write the word `/needsMoreInfo` anywhere in a comment and the bot will add the correct label and standard message. +* Write the word `/duplicate #` anywhere in a comment and the bot will add the correct label and standard message. +* Write the word `/needsMoreInfo` anywhere in a comment and the bot will add the correct label and standard message. Label commands: diff --git a/.github/pr-checks.json b/.github/pr-checks.json index b97dbc137ec..3566f6a65df 100644 --- a/.github/pr-checks.json +++ b/.github/pr-checks.json @@ -1,11 +1,4 @@ [ - { - "type": "check-milestone", - "title": "Milestone Check", - "targetUrl": "https://github.com/grafana/grafana/blob/main/contribute/merge-pull-request.md#assign-a-milestone", - "success": "Milestone set", - "failure": "Milestone not set" - }, { "type": "check-changelog", "title": "Changelog Check", diff --git a/.github/workflows/feature-toggle-cleanup.yml b/.github/workflows/feature-toggle-cleanup.yml deleted file mode 100644 index 0f30fb54ce9..00000000000 --- a/.github/workflows/feature-toggle-cleanup.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Feature Toggle Cleanup - -on: - workflow_dispatch: - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '30 10 * * *' -jobs: - feature-toggle-cleanup: - runs-on: ubuntu-latest - # This check is here to prevent this workflow from running on forks. - if: github.repository == 'grafana/grafana' - steps: - - name: Check out the code - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20.x" - - run: npm install csv-parse - - name: Parse CVS file to see which Feature Toggles should be notified about - id: parse-csv-file - uses: actions/github-script@v7 - env: - FEATURE_TOGGLES_CSV_FILE_PATH: "pkg/services/featuremgmt/toggles_gen.csv" - with: - script: | - const { default: cleanupFeatureFlags } = await import('${{ github.workspace }}/.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.mjs') - await cleanupFeatureFlags({github, context, core}) \ No newline at end of file diff --git a/.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.js b/.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.js deleted file mode 100644 index 35bc2938c10..00000000000 --- a/.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.js +++ /dev/null @@ -1,38 +0,0 @@ -import { parse } from 'csv-parse/sync'; -import fs from 'fs'; - -/*** - * Feauture Flag Structure example - * Name: 'disableEnvelopeEncryption', - Stage: 'GA', - Owner: '@grafana/grafana-as-code', - Created: '2022-05-24', - requiresDevMode: 'false', - RequiresLicense: 'false', - RequiresRestart: 'false', - FrontendOnly: 'false' - * - */ - - -export default function cleanupFeatureFlags() { - const today = new Date(); - const sixMonthAgo = today.setMonth(today.getMonth() - 6); - const inputFileContents = fs.readFileSync(process.env.FEATURE_TOGGLES_CSV_FILE_PATH); - const parsedFeatureFlags = parse(inputFileContents, { - columns: true, - skip_empty_lines: true, - cast: true, - cast_date: true, - }); - - // Here we can have the custom logic of how to handle what type of feature flag - e.g. GA can be treated differently than experimental and so on. - for (const flag of parsedFeatureFlags) { - if (flag.Created < sixMonthAgo) { - console.log(`The flag ${flag.Name} was created more than 6 months ago. It should be checked.`); - console.log(flag); - } - } - - -} diff --git a/.golangci.toml b/.golangci.toml index 4429f9e6595..fd4fb00286f 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -60,6 +60,8 @@ files = [ "**/pkg/tsdb/azuremonitor/**/*", "**/pkg/tsdb/cloud-monitoring/*", "**/pkg/tsdb/cloud-monitoring/**/*", + "**/pkg/tsdb/mysql/*", + "**/pkg/tsdb/mysql/**/*", "**/pkg/tsdb/parca/*", "**/pkg/tsdb/parca/**/*", "**/pkg/tsdb/tempo/*", diff --git a/conf/defaults.ini b/conf/defaults.ini index 20e35fd9136..43ebf88b59f 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -104,6 +104,8 @@ address = "127.0.0.1:10000" use_tls = false cert_file = key_file = +# this will log the request and response for each unary gRPC call +enable_logging = false #################################### Database ############################ [database] @@ -1837,4 +1839,4 @@ fetch_access_policy_timeout = 5s # How long to wait for a request to create to delete an access policy to complete delete_access_policy_timeout = 5s # The domain name used to access cms -domain = grafana-dev.net \ No newline at end of file +domain = grafana-dev.net diff --git a/conf/provisioning/alerting/sample.yaml b/conf/provisioning/alerting/sample.yaml index 51836f25b3f..e56095123b2 100644 --- a/conf/provisioning/alerting/sample.yaml +++ b/conf/provisioning/alerting/sample.yaml @@ -191,7 +191,7 @@ apiVersion: 1 # - orgID: 1 # # name of the template, must be unique # name: my_first_template -# # content of the the template +# # content of the template # template: Alerting with a custome text template # # List of templates that should be deleted diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index 6df07b690e0..120e8a0daa9 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -333,6 +333,10 @@ ulimit: open files: cannot modify limit: Operation not permitted If that happens to you, chances are you've already set a lower limit and your shell won't let you set a higher one. Try looking in your shell initialization files (~/.bashrc typically), if there's already an ulimit command that you can tweak. +### Getting `AggregateError` when building frontend tests + +If you encounter an `AggregateError` when building new tests, this is probably due to a call to our client [backend service](https://github.com/grafana/grafana/blob/main/public/app/core/services/backend_srv.ts) not being mocked. Our backend service anticipates multiple responses being returned and was built to return errors as an array. A test encountering errors from the service will group those errors as an `AggregateError` without breaking down the individual errors within. `backend_srv.processRequestError` is called once per error and is a great place to return information on what the individual errors might contain. + ## Next steps - Read our [style guides](/contribute/style-guides). diff --git a/devenv/docker/blocks/self-instrumentation/tempo.yaml b/devenv/docker/blocks/self-instrumentation/tempo.yaml index ad49ea13a58..acc5c1b8ea6 100644 --- a/devenv/docker/blocks/self-instrumentation/tempo.yaml +++ b/devenv/docker/blocks/self-instrumentation/tempo.yaml @@ -35,7 +35,7 @@ storage: trace: backend: local # backend configuration to use wal: - path: /tmp/tempo/wal # where to store the the wal locally + path: /tmp/tempo/wal # where to store the wal locally local: path: /tmp/tempo/blocks diff --git a/devenv/docker/blocks/tempo/tempo.yaml b/devenv/docker/blocks/tempo/tempo.yaml index f94d4ca96e7..7cfb1230a3f 100644 --- a/devenv/docker/blocks/tempo/tempo.yaml +++ b/devenv/docker/blocks/tempo/tempo.yaml @@ -44,7 +44,7 @@ storage: v2_index_downsample_bytes: 1000 # number of bytes per index record v2_encoding: zstd # block encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 wal: - path: /tmp/tempo/wal # where to store the the wal locally + path: /tmp/tempo/wal # where to store the wal locally v2_encoding: snappy # wal encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 local: path: /tmp/tempo/blocks diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 4d6e5aa67f6..434b4569a78 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -28,7 +28,7 @@ The following tables list permissions associated with basic and fixed roles. | Grafana Admin | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:maintainer`
`fixed:authentication.config:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer` | Default [Grafana server administrator]({{< relref "../../#grafana-server-administrators" >}}) assignments. | | Admin | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:dashboards.public:writer`
`fixed:folders:reader`
`fixed:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning.secrets:reader`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Grafana organization administrator]({{< relref "../#basic-roles" >}}) assignments. | | Editor | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled
`fixed:alerting:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:creator`
`fixed:library.panels:general.reader`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Editor]({{< relref "../#basic-roles" >}}) assignments. | -| Viewer | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader` | Default [Viewer]({{< relref "../#basic-roles" >}}) assignments. | +| Viewer | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader`
`fixed:datasources:explorer` if the `viewers_can_edit` configuration flag is enabled | Default [Viewer]({{< relref "../#basic-roles" >}}) assignments. | | No Basic Role | | Default [No Basic Role]({{< relref "../#basic-roles" >}}) | ## Fixed role definitions diff --git a/docs/sources/administration/user-management/manage-dashboard-permissions/index.md b/docs/sources/administration/user-management/manage-dashboard-permissions/index.md index 1bad26e3515..e87d0193fcc 100644 --- a/docs/sources/administration/user-management/manage-dashboard-permissions/index.md +++ b/docs/sources/administration/user-management/manage-dashboard-permissions/index.md @@ -69,10 +69,6 @@ By default, the viewer organization role does not allow viewers to create dashbo This modification is useful for public Grafana installations where you want anonymous users to be able to edit panels and queries but not save or create new dashboards. -{{% admonition type="note" %}} -If you use Grafana Enterprise and customize users' permissions using RBAC, the RBAC permissions override the functionality enabled by the `viewers_can_edit` flag. -{{% /admonition %}} - ### Before you begin - Ensure that you have access to the Grafana server diff --git a/docs/sources/administration/user-management/server-user-management/grant-editor-admin-permissions/index.md b/docs/sources/administration/user-management/server-user-management/grant-editor-admin-permissions/index.md index 875c7babec7..cfefd775975 100644 --- a/docs/sources/administration/user-management/server-user-management/grant-editor-admin-permissions/index.md +++ b/docs/sources/administration/user-management/server-user-management/grant-editor-admin-permissions/index.md @@ -5,30 +5,21 @@ labels: products: - enterprise - oss -title: Grant editors administrator permissions +title: Grant editors team creator permissions weight: 60 --- -# Grant editors administrator permissions +# Grant editors team creator permissions -By default, the editor organization role does not allow editors to manage dashboard folders, dashboards, and teams, which you can change by modifying a configuration parameter. You can allow them to do so using the `editors_can_admin` configuration option. +By default, the editor organization role does not allow editors to creator and manage teams. You can allow them to do so using the `editors_can_admin` configuration option. -This setting can be used to enable self-organizing teams to administer their own dashboards. - -When `editors_can_admin` is enabled: - -- Users with the Editor role in an organization are Administrators for new dashboards and folders they create, meaning they can edit dashboard permissions. To learn more about dashboard permissions, refer to [Manage dashboard permissions]({{< relref "../../manage-dashboard-permissions/" >}}). -- Users with the Editor role in an organization can create teams, and they are Administrators of the teams they create. To learn more about team permissions, refer to [Team management]({{< relref "../../../team-management/" >}}). - -{{% admonition type="note" %}} -If you use Grafana Enterprise and customize users' permissions using RBAC, the RBAC permissions override the functionality enabled by the `editors_can_admin` flag. -{{% /admonition %}} +When `editors_can_admin` is enabled, users with the Editor role in an organization can create teams, and they are Administrators of the teams they create. To learn more about team permissions, refer to [Team management]({{< relref "../../../team-management/" >}}). ## Before you begin - Ensure that you have access to the Grafana server -**To enable editors with administrator permissions**: +**To enable editors with team creator permissions**: 1. Log in to the Grafana server and open the Grafana configuration file. diff --git a/docs/sources/alerting/set-up/configure-roles/index.md b/docs/sources/alerting/set-up/configure-roles/index.md new file mode 100644 index 00000000000..f682c99c47f --- /dev/null +++ b/docs/sources/alerting/set-up/configure-roles/index.md @@ -0,0 +1,76 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/set-up/configure-roles/ +description: Configure roles and permissions for Grafana Alerting +keywords: + - grafana + - alerting + - set up + - configure + - roles and permissions +labels: + products: + - oss +title: Configure roles and permissions +weight: 150 +--- + +# Configure roles and permissions + +A user is any individual who can log in to Grafana. Each user is associated with a role that includes permissions. Permissions determine the tasks a user can perform in the system. For example, the Admin role includes permissions for an administrator to create and delete users. + +For more information, refer to [Organization roles](https://grafana.com/docs/grafana//administration/roles-and-permissions/#organization-roles). + +## Manage access using roles + +For Grafana OSS, there are three roles: Admin, Editor, and Viewer. + +Details of the roles and the access they provide for Grafana Alerting are below. + +| Role | Access | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Admin | Write access to alert rules, notification resources (notification API, contact points, templates, time intervals, notification policies, and silences), and provisioning. | +| Editor | Write access to alert rules, notification resources (notification API, contact points, templates, time intervals, notification policies, and silences), and provisioning. | +| Viewer | Read access to alert rules, notification resources (notification API, contact points, templates, time intervals, notification policies, and silences). | + +## Assign roles + +To assign roles, admins need to complete the following steps. + +1. Navigate to **Administration** > **Users and access** > **Users, Teams, or Service Accounts**. +1. Search for the user, team or service account you want to add a role for. +1. Add the role you want to assign. + +## Manage access using folder permissions + +You can further customize access for alert rules, simplified alert routing, and provisioning by assigning permissions to individual folders. + +This prevents every user from having access to modify all alert rules and gives them access to the folders with the alert rules they're working on. + +For example, if you are using simplified alert routing and adding contact points to your alert rules, it also helps you avoid the scenario where someone from another team accidentally removes the wrong notification policy or adds a competing one, and all of a sudden you stop getting your notifications. + +In this case, you would assign the **Viewer** role and then add **Editor** permission to the folder. +Adding the **Editor** permission to the folder doesn't overwrite the **Viewer** role. + +Details on the adding folder permissions as well as roles and the access that provides for Grafana Alerting is below. + +| Role | Folder permission | Access | +| ------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Admin | - | Write access to alert rules in all folders. | +| Editor | - | Write access to alert rules in all folders. | +| Viewer | Admin | Read access to alert rules in all folders. Write access to alert rules **only** in the folders where the Admin permission is added. | +| Viewer | Editor | Read access to alert rules in all folders. Write access to alert rules **only** in the folders where the Editor permission is added. | +| Viewer | Viewer | Read access to alert rules in all folders. | + +{{< admonition type="note" >}} +You can't use folders to customize access to notification resources. +{{< /admonition >}} + +To manage folder permissions, complete the following steps. + +1. In the left-side menu, click **Dashboards**. +1. Hover your mouse cursor over a folder and click **Go to folder**. +1. Click the **Permissions** tab. +1. Click **Add a permission**. +1. Select the user, service account, team, or role. +1. Select **Viewer, Editor or Admin**. +1. Click **Save**. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md index bfa4ee493c5..eeac8b1749f 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md @@ -186,7 +186,7 @@ In this section, we'll create Terraform configurations for each alerting resourc To link the alert rule group with its respective data source and folder in this example, replace the following field values: - `` with the terraform name of the previously defined data source. - - `` with the terraform name of the the previously defined folder. + - `` with the terraform name of the previously defined folder. 1. Continue to add more Grafana resources or [use the Terraform CLI for provisioning](#provision-grafana-resources-with-terraform). @@ -312,7 +312,7 @@ Since the policy tree is a single resource, provisioning the `grafana_notificati To configure the mute timing and contact point previously created in the notification policy tree, replace the following field values: - `` with the terraform name of the previously defined contact point. - - `` with the terraform name of the the previously defined mute timing. + - `` with the terraform name of the previously defined mute timing. 1. Continue to add more Grafana resources or [use the Terraform CLI for provisioning](#provision-grafana-resources-with-terraform). diff --git a/docs/sources/dashboards/_index.md b/docs/sources/dashboards/_index.md index 0207994cd73..79ffef5454c 100644 --- a/docs/sources/dashboards/_index.md +++ b/docs/sources/dashboards/_index.md @@ -9,69 +9,63 @@ labels: title: Dashboards weight: 70 description: Create and manage dashboards +hero: + title: Dashboards + level: 1 + width: 110 + height: 110 + description: >- + Dashboards allow you to query, transform, visualize, and understand your data no matter where it’s stored. +cards: + title_class: pt-0 lh-1 + items: + - title: Build dashboards + href: ./build-dashboards/ + description: Get step-by-step directions for how to create or import your first dashboard and modify dashboard settings. Learn about reusable library panels, dashboard links, annotatations, and dashboard JSON. + height: 24 + - title: Manage dashboards + href: ./manage-dashboards/ + description: Learn about dashboard and folder management, as well as generative AI features for dashboards. + height: 24 + - title: Variables + href: ./variables/ + description: Add variables to metric queries and panel titles to create interactive and dynamic dashboards. + height: 24 + - title: Public dashboards + href: ./dashboard-public/ + description: Make your Grafana dashboards public and share them with anyone without requiring access to your Grafana organization. + height: 24 + - title: Reporting + href: ./create-reports/ + description: Automatically generate and share PDF reports from your Grafana dashboards. + height: 24 + - title: Sharing + href: ./share-dashboards-panels/ + description: Share Grafana dashboards and panels within your organization using links, snapshots, and JSON exports. + height: 24 --- -# Dashboards +{{< docs/hero-simple key="hero" >}} -A dashboard is a set of one or more [panels][] organized and arranged into one or more rows. Grafana ships with a variety of panels making it easy to construct the right queries, and customize the visualization so that you can create the perfect dashboard for your need. Each panel can interact with data from any configured Grafana [data source][]. +--- -Dashboard snapshots are static. Queries and expressions cannot be re-executed from snapshots. As a result, if you update any variables in your query or expression, it will not change your dashboard data. +## Overview -Before you begin, ensure that you have configured a data source. See also: +A Grafana dashboard is a set of one or more [panels][], organized and arranged into one or more rows, that provide an at-a-glance view of related information. These panels are created using components that query and transform raw data from a data source into charts, graphs, and other visualizations. -- [Use dashboards][] -- [Build dashboards][] -- [Create dashboard folders][] -- [Manage dashboards][] -- [Public dashboards][] -- [Annotations][] -- [Playlist][] -- [Reporting][] -- [Version history][] -- [Import][] -- [Export and share][] -- [JSON model][] +A data source can be an SQL database, Grafana Loki, Grafana Mimir, or a JSON-based API. It can even be a basic CSV file. Data source plugins take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. + +Queries allow you to reduce the entirety of your data to a specific dataset, providing a more manageable visualization. Since data sources have their own distinct query languages, Grafana dashboards provide you with a query editor to accommodate these differences. + +A panel is the container that displays the visualization and provides you with various controls to manipulate it. Panel options let you customize many aspects of a visualization and the options differ based on which visualization you select. When the data format in a visualization doesn’t meet your requirements, you can apply a transformation that manipulates the data returned by a query. + +With 150+ data source plugins, you can unify all your data sources into a single dashboard to streamline data monitoring and troubleshooting. With Grafana, you can translate, transform, and visualize data in flexible and versatile dashboards. + +## Explore + +{{< card-grid key="cards" type="simple" >}} {{% docs/reference %}} -[data source]: "/docs/grafana/ -> /docs/grafana//datasources" -[data source]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/connect-externally-hosted/data-sources" - -[Reporting]: "/docs/grafana/ -> /docs/grafana//dashboards/create-reports" -[Reporting]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/create-reports" - -[Public dashboards]: "/docs/grafana/ -> /docs/grafana//dashboards/dashboard-public" -[Public dashboards]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/dashboard-public" - -[Version history]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards/manage-version-history" -[Version history]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards/manage-version-history" - -[panels]: "/docs/grafana/ -> /docs/grafana//panels-visualizations" -[panels]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations" - -[Annotations]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards/annotate-visualizations" -[Annotations]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards/annotate-visualizations" - -[Create dashboard folders]: "/docs/grafana/ -> /docs/grafana//dashboards/manage-dashboards#create-a-dashboard-folder" -[Create dashboard folders]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/manage-dashboards#create-a-dashboard-folder" - -[JSON model]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards/view-dashboard-json-model" -[JSON model]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards/view-dashboard-json-model" - -[Import]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards/import-dashboards" -[Import]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards/import-dashboards" - -[Export and share]: "/docs/grafana/ -> /docs/grafana//dashboards/share-dashboards-panels" -[Export and share]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels" - -[Manage dashboards]: "/docs/grafana/ -> /docs/grafana//dashboards/manage-dashboards" -[Manage dashboards]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/manage-dashboards" - -[Build dashboards]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards" -[Build dashboards]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards" - -[Use dashboards]: "/docs/grafana/ -> /docs/grafana//dashboards/use-dashboards" -[Use dashboards]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/use-dashboards" - -[Playlist]: "/docs/grafana/ -> /docs/grafana//dashboards/create-manage-playlists" -[Playlist]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/create-manage-playlists" +[panels]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/panel-overview" +[panels]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/panel-overview" {{% /docs/reference %}} diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md index d9711ffffab..966a6097874 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md @@ -24,6 +24,8 @@ Annotations provide a way to mark points on a visualization with rich events. Th {{< figure src="/static/img/docs/v46/annotations.png" max-width="800px" alt="Annotated visualization with annotation context menu open" >}} +{{< docs/play title="Annotations" url="https://play.grafana.org/d/000000010/" >}} + You can annotate visualizations in three ways: - Directly in the panel, using the [built-in annotations query](#built-in-query) @@ -49,6 +51,10 @@ To add annotations directly in the panel: - The dashboard must already be saved. - The built-in query must be enabled. Learn more in [Built-in query](#built-in-query). +Watch the following video for a quick tutorial on creating annotations: + +{{< youtube id="N5iOlyYyK6Q" >}} + ### Add an annotation To add an annotation, complete the following steps: diff --git a/docs/sources/dashboards/build-dashboards/best-practices/index.md b/docs/sources/dashboards/build-dashboards/best-practices/index.md index b0677c24b13..e50b24c62af 100644 --- a/docs/sources/dashboards/build-dashboards/best-practices/index.md +++ b/docs/sources/dashboards/build-dashboards/best-practices/index.md @@ -69,7 +69,7 @@ This method is similar to the RED method, but it includes saturation. - **Errors -** Rate of requests that are failing - **Saturation -** How "full" your system is -[Here's an example from Grafana Play](https://play.grafana.org/d/000000109/the-four-golden-signals?orgId=1). +{{< docs/play title="The Four Golden Signals" url="https://play.grafana.org/d/000000109/" >}} ## Dashboard management maturity model diff --git a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md index 2d2abaf6c7e..9c15dc95099 100644 --- a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md +++ b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md @@ -47,6 +47,24 @@ Adjust dashboard time settings when you want to change the dashboard timezone, t - **Now delay:** Override the `now` time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. - **Hide time picker:** Select this option if you do not want Grafana to display the time picker. +## Add tags + +You can add metadata to your dashboards using tags. Tags also give you the ability to filter the list of dashboards. + +Tags can be up to 50 characters long, including spaces. + +To add tags to a dashboard, follow these steps: + +1. On the **Dashboard settings** page, scroll down to the **Tags** section. +1. In the field, enter a new or existing tag. + + If you're entering an existing tag, make sure that you spell it the same way or a new tag is created. + +1. Click **Add** or press the Enter key. +1. Save the dashboard. + +When you're on the **Dashboards** page, any tags you've entered show up under the **Tags** column. + ## Add an annotation query An annotation query is a query that queries for events. These events can be visualized in graphs across the dashboard as vertical lines along with a small diff --git a/docs/sources/dashboards/variables/_index.md b/docs/sources/dashboards/variables/_index.md index 8b6c4a397f6..ad8eae57c86 100644 --- a/docs/sources/dashboards/variables/_index.md +++ b/docs/sources/dashboards/variables/_index.md @@ -30,7 +30,9 @@ the dashboard. These dropdowns make it easy to change the data being displayed i {{< figure src="/static/img/docs/v50/variables_dashboard.png" alt="Variable drop-down open and two values selected" >}} -These can be especially useful for administrators who want to allow Grafana viewers to quickly adjust visualizations but do not want to give them full editing permissions. Grafana Viewers can use variables. +{{< docs/play title="Templating - Global variables and interpolation" url="https://play.grafana.org/d/HYaGDGIMk/" >}} + +Variables are useful for administrators who want to allow Grafana viewers to adjust visualizations without giving them full editing permissions. Grafana viewers can use variables. Variables and templates also allow you to single-source dashboards. If you have multiple identical data sources or servers, you can make one dashboard and use variables to change what you are viewing. This simplifies maintenance and upkeep enormously. @@ -46,7 +48,7 @@ wmi_system_threads{instance=~"$server"} Variable values are always synced to the URL using the syntax `var-=value`. -## Examples +## Additional Examples Variables are listed in drop-down lists across the top of the screen. Select different variables to see how the visualizations change. @@ -56,12 +58,9 @@ Variables can be used in titles, descriptions, text panels, and queries. Queries The following dashboards in Grafana Play provide examples of template variables: -- [Graphite Templated Nested](https://play.grafana.org/d/000000056/templated-dynamic-dashboard?orgId=1&var-app=backend&var-server=backend_01&var-server=backend_02&var-server=backend_03&var-server=backend_04&var-interval=1h) - Uses query variables, chained query variables, an interval variable, and a repeated panel. -- [Global variables and interpolation](https://play.grafana.org/d/HYaGDGIMk/templating-global-variables-and-interpolation?orgId=1&var-Server=A%27A%22A&var-Server=BB%5CB) -- [Elasticsearch Dummy Flight Data](https://play.grafana.org/d/z8OZC66nk/elasticsearch-8-2-0-sample-flight-data?orgId=1&var-Filters=Carrier%7C%3D%7CLogstash%20Airways&var-query0=) - Uses ad hoc filters. -- [Templating, repeated panels](https://play.grafana.org/d/000000025/templating-repeated-panels?orgId=1) - Two sets of repeated panels use query variables. -- [Template Redux](https://play.grafana.org/d/p-k6QtkGz/template-redux?orgId=1) - Uses query variables, chained query variables, an interval variable, a text box variable, a custom variable, and a data source variable. -- [Nested Variables Drilldown](https://play.grafana.org/d/testdata-nested-variables-drilldown/templating-nested-variables-drilldown?orgId=1&var-datacenter=A&var-server=AA&var-server=AC&var-pod=All) +- [Templating, repeated panels](https://play.grafana.org/d/000000025/) - Using query variables to control how many panels appear. +- [Templated Dynamic Dashboard](https://play.grafana.org/d/000000056/) - Uses query variables, chained query variables, an interval variable, and a repeated panel. +- [Templating - Nested Variables Drilldown](https://play.grafana.org/d/testdata-nested-variables-drilldown/) ## Variable best practices diff --git a/docs/sources/datasources/_index.md b/docs/sources/datasources/_index.md index b738aa80a0c..8179d4388c2 100644 --- a/docs/sources/datasources/_index.md +++ b/docs/sources/datasources/_index.md @@ -76,14 +76,24 @@ For general information about querying in Grafana, and common options and user i Grafana includes three special data sources: -- **Grafana:** A built-in data source that generates random walk data and can poll the [Testdata]({{< relref "./testdata/" >}}) data source. Additionally, it can list files and get other data from a Grafana installation. This can be helpful for testing visualizations and running experiments. -- **Mixed:** An abstraction that lets you query multiple data sources in the same panel. - When you select Mixed, you can then select a different data source for each new query that you add. - - The first query uses the data source that was selected before you selected **Mixed**. - - You can't change an existing query to use the **Mixed** data source. - - Grafana Play example: [Mixed data sources](https://play.grafana.org/d/000000100/mixed-datasources?orgId=1) -- **Dashboard:** A data source that uses the result set from another panel in the same dashboard. The dashboard data source can use data either directly from the selected panel or from annotations attached to the selected panel. - - Grafana Play example: [Panel as Data source](https://play.grafana.org/d/ede8zps8ndb0gc/panel-as-data-source?orgId=1) +### Grafana + +A built-in data source that generates random walk data and can poll the [Testdata]({{< relref "./testdata/" >}}) data source. Additionally, it can list files and get other data from a Grafana installation. This can be helpful for testing visualizations and running experiments. + +### Mixed + +An abstraction that lets you query multiple data sources in the same panel. When you select Mixed, you can then select a different data source for each new query that you add. + +- The first query uses the data source that was selected before you selected **Mixed**. +- You can't change an existing query to use the **Mixed** data source. + +{{< docs/play title="Mixed Datasources Example" url="https://play.grafana.org/d/000000100/" >}} + +### Dashboard + +A data source that uses the result set from another panel in the same dashboard. The dashboard data source can use data either directly from the selected panel or from annotations attached to the selected panel. + +{{< docs/play title="Panel as a Data Source" url="https://play.grafana.org/d/ede8zps8ndb0gc/" >}} ## Built-in core data sources diff --git a/docs/sources/datasources/graphite/template-variables/index.md b/docs/sources/datasources/graphite/template-variables/index.md index 1490b9aaddb..49aacaee33d 100644 --- a/docs/sources/datasources/graphite/template-variables/index.md +++ b/docs/sources/datasources/graphite/template-variables/index.md @@ -143,7 +143,7 @@ The Graphite data source supports two variable syntaxes for use in the **Query** ### Templated dashboard example -To view an example templated dashboard, refer to [Graphite Templated Dashboard](https://play.grafana.org/dashboard/db/graphite-templated-nested). +To view an example templated dashboard, refer to [Graphite Templated Nested dashboard](https://play.grafana.org/d/cvDFGseGz/graphite-templated-nested). {{% docs/reference %}} [add-template-variables]: "/docs/grafana/ -> /docs/grafana//dashboards/variables/add-template-variables" diff --git a/docs/sources/datasources/influxdb/template-variables/index.md b/docs/sources/datasources/influxdb/template-variables/index.md index 4395741f93c..908a2c0fd4e 100644 --- a/docs/sources/datasources/influxdb/template-variables/index.md +++ b/docs/sources/datasources/influxdb/template-variables/index.md @@ -87,7 +87,7 @@ When you enable the **Multi-value** or **Include all value** options, Grafana co ### Templated dashboard example -To view an example templated dashboard, refer to [InfluxDB Templated Dashboard](https://play.grafana.org/dashboard/db/influxdb-templated). +To view an example templated dashboard, refer to this [InfluxDB templated dashboard](https://play.grafana.org/d/f62a0410-5abb-4dd8-9dfc-caddfc3e2ffd/eccb2445-b0a2-5e83-8e0f-6d5ea53ad575). {{% docs/reference %}} [add-template-variables-chained-variables]: "/docs/grafana/ -> /docs/grafana//dashboards/variables/add-template-variables#chained-variables" diff --git a/docs/sources/explore/explore-metrics.md b/docs/sources/explore/explore-metrics.md index a72d0a242ef..7433d18c455 100644 --- a/docs/sources/explore/explore-metrics.md +++ b/docs/sources/explore/explore-metrics.md @@ -14,9 +14,7 @@ weight: 200 Grafana Explore Metrics is a query-less experience for browsing **Prometheus-compatible** metrics. Quickly find related metrics with just a few simple clicks, without needing to write PromQL queries to retrieve metrics. -{{% admonition type="warning" %}} -Explore Metrics is currently in [private preview](/docs/release-life-cycle/). Grafana Labs offers support on a best-effort basis, and breaking changes might occur prior to the feature being made generally available. -{{% /admonition %}} +{{< docs/public-preview product="Explore Metrics" >}} With Explore Metrics, you can: diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index 59408b56765..fbf20324291 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -24,6 +24,8 @@ Supported data sources are: - [X-Ray](https://grafana.com/grafana/plugins/grafana-x-ray-datasource) - [Azure Monitor Application Insights]({{< relref "../datasources/azure-monitor/" >}}) - [ClickHouse](https://github.com/grafana/clickhouse-datasource) +- [New Relic](https://grafana.com/grafana/plugins/grafana-newrelic-datasource) +- [Infinity](https://grafana.com/grafana/plugins/yesoreyeram-infinity-datasource) For information on how to configure queries for the data sources listed above, refer to the documentation for specific data source. diff --git a/docs/sources/panels-visualizations/_index.md b/docs/sources/panels-visualizations/_index.md index dbb1f5d71c7..654b4b41096 100644 --- a/docs/sources/panels-visualizations/_index.md +++ b/docs/sources/panels-visualizations/_index.md @@ -17,35 +17,55 @@ menuTitle: Panels and visualizations title: Panels and visualizations description: Learn about and configure panels and visualizations weight: 80 +hero: + title: Panels and visualizations + level: 1 + width: 110 + height: 110 + description: >- + Easily collect, correlate, and visualize data so you can make informed decisions in real time. +cards: + title_class: pt-0 lh-1 + items: + - title: Visualizations + href: ./visualizations/ + description: Learn about all the visualizations available in Grafana, including which visualizations are ideal for different datasets and how to configure their options. + height: 24 + - title: Panel overview + href: ./panel-overview/ + description: Learn about the features of the panel. + height: 24 + - title: Panel editor + href: ./panel-editor-overview/ + description: Learn about the features of the panel editor and how to begin editing a panel. + height: 24 + - title: Configure standard options + href: ./configure-standard-options/ + description: Learn about configuring standard options like units, field display names, and colors. + height: 24 + - title: Query and transform data + href: ./query-transform-data/ + description: Learn about querying and transforming your data to refine your visualizations. + height: 24 --- -# Panels and visualizations +{{< docs/hero-simple key="hero" >}} -The _panel_ is the basic visualization building block in Grafana. -Each panel has a query editor specific to the data source selected in the panel. -The query editor allows you to build a query that returns the data you want to visualize. +--- -There are a wide variety of styling and formatting options for each panel. -Panels can be dragged, dropped, and resized to rearrange them on the dashboard. +## Overview -Before you add a panel, ensure that you have configured a data source. +Panels are the basic building block in Grafana dashboards, composed of a [query][] and a visualization, a graphical representation of query results. -- For details about using data sources, refer to [Data sources][]. +Visualizations provide you several different ways to present your data within a panel, depending on what best suits the data and your needs. Grafana’s growing suite of visualizations, ranging from time series graphs to heatmaps to cutting-edge 3D charts, help you decode complex datasets. -- For more information about managing data sources as an administrator, refer to [Data source management][]. +Panels offer a wide variety of formatting and styling options from applying colors based on field values to custom units. Each visualization also comes with options specific to it that give you further control over how your data is displayed. With Grafana panels and visualizations, you can easily get the information you need from your data and optimize performance. - {{% admonition type="note" %}} - [Data source management](https://grafana.com/docs/grafana//administration/data-source-management/) is only available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](https://grafana.com/docs/grafana-cloud/). - {{% /admonition %}} +## Explore -This section includes the following sub topics: - -{{< section >}} +{{< card-grid key="cards" type="simple" >}} {{% docs/reference %}} -[Data source management]: "/docs/grafana/ -> /docs/grafana//administration/data-source-management" -[Data source management]: "/docs/grafana-cloud/ -> /docs/grafana//administration/data-source-management" - -[Data sources]: "/docs/grafana/ -> /docs/grafana//datasources" -[Data sources]: "/docs/grafana-cloud/ -> /docs/grafana//datasources" +[query]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data" +[query]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data" {{% /docs/reference %}} diff --git a/docs/sources/panels-visualizations/configure-thresholds/index.md b/docs/sources/panels-visualizations/configure-thresholds/index.md index fe6e7a5bd5c..92557208917 100644 --- a/docs/sources/panels-visualizations/configure-thresholds/index.md +++ b/docs/sources/panels-visualizations/configure-thresholds/index.md @@ -38,6 +38,8 @@ You can also use thresholds to: - Color markers in a geomap - Color cell text or background in a table +{{< docs/play title="Threshold example" url="https://play.grafana.org/d/000000167/" >}} + ## Supported visualizations You can set thresholds in the following visualizations: diff --git a/docs/sources/panels-visualizations/panel-editor-overview/index.md b/docs/sources/panels-visualizations/panel-editor-overview/index.md index 54ddeda2028..7c638b18ef8 100644 --- a/docs/sources/panels-visualizations/panel-editor-overview/index.md +++ b/docs/sources/panels-visualizations/panel-editor-overview/index.md @@ -15,99 +15,54 @@ keywords: - panel - dashboard - dynamic - - rows - add labels: products: - cloud - enterprise - oss -menuTitle: Panel editor overview -title: Panel editor overview +menuTitle: Panel editor +title: Panel editor description: Learn about the features of the panel editor weight: 20 --- -# Panel editor overview +# Panel editor In the panel editor, you can update all the elements of a visualization including the data source, queries, time range, and visualization display options. ![Panel editor](/media/docs/grafana/panels-visualizations/screenshot-panel-editor-view.png) -To add a panel in a new dashboard click **+ Add visualization** in the middle of the dashboard. To add a panel to an existing dashboard, click **Add** in the dashboard header and select **Visualization** in the drop-down: +This following sections describe the areas of the Grafana panel editor. -![Add dropdown](/media/docs/grafana/dashboards/screenshot-add-dropdown-10.0.png) +## Panel header -## Panel menu +The header section lists the dashboard in which the panel appears and the following controls: -To access the panel editor, hover over the top-right corner of any panel. Click the panel menu icon that appears and select **Edit**. The panel menu gives you access to the following actions: +- **Discard:** Discards changes you have made to the panel since you last saved the dashboard. +- **Save:** Saves changes you made to the panel. +- **Apply:** Applies changes you made and closes the panel editor, returning you to the dashboard. You'll have to save the dashboard to persist the applied changes. -- **View**: View the panel in full screen. -- **Edit**: Open the panel editor to edit panel and visualization options. -- **Share**: Share the panel as a link, embed, or library panel. -- **Explore**: Open the panel in **Explore**, where you can focus on your query. -- **Inspect**: Open the **Inspect** drawer, where you can review the panel data, stats, metadata, JSON, and query. - - **Data**: Open the **Inspect** drawer in the **Data** tab. - - **Query**: Open the **Inspect** drawer in the **Query** tab. - - **Panel JSON**: Open the **Inspect** drawer in the **JSON** tab. -- **Extensions**: Access other actions provided by installed applications, such as declaring an incident. Note that this option doesn't appear unless you have app plugins installed which contribute an [extension](https://grafana.com/developers/plugin-tools/ui-extensions/) to the panel menu. -- **More**: Access other panel actions. - - **Duplicate**: Make a copy of the panel. Duplicated panels query data separately from the original panel. You can use the special `Dashboard` data source to [share the same query results across panels][] instead. - - **Copy**: Copy the panel to the clipboard. - - **Create library panel**: Create a panel that can be imported into other dashboards. - - **Create alert**: Open the alert rule configuration page in **Alerting**, where you can [create a Grafana-managed alert] based on the panel queries. - - **Hide legend**: Hide the panel legend. - - **Get help**: Send a snapshot or panel data to Grafana Labs Technical Support. -- **Remove**: Remove the panel from the dashboard. +## Visualization preview -## Panel editor +The visualization preview section contains the following options: -This section describes the areas of the Grafana panel editor. +- **Table view:** Convert any visualization to a table so you can see the data. Table views are helpful for troubleshooting. This view only contains the raw data. It doesn't include transformations you might have applied to the data or the formatting options available in the [Table][] visualization. +- **Fill:** The visualization preview fills the available space. If you change the width of the side pane or height of the bottom pane the visualization changes to fill the available space. +- **Actual:** The visualization preview has the exact size as the size on the dashboard. If not enough space is available, the visualization scales down preserving the aspect ratio. +- **Time range controls:** **Default** is either the browser local timezone or the timezone selected at a higher level. -1. Panel header: The header section lists the dashboard in which the panel appears and the following controls: +## Data section - - **Discard:** Discards changes you have made to the panel since you last saved the dashboard. - - **Save:** Saves changes you made to the panel. - - **Apply:** Applies changes you made and closes the panel editor, returning you to the dashboard. You'll have to save the dashboard to persist the applied changes. +The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable). -1. Visualization preview: The visualization preview section contains the following options: +- **Query tab:** Select your data source and enter queries here. For more information, refer to [Add a query][]. When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Query** tab. +- **Transform tab:** Apply data transformations. For more information, refer to [Transform data][]. +- **Alert tab:** Write alert rules. For more information, refer to [the overview of Grafana Alerting][]. - - **Table view:** Convert any visualization to a table so you can see the data. Table views are helpful for troubleshooting. This view only contains the raw data. It doesn't include transformations you might have applied to the data or the formatting options available in the [Table][] visualization. - - **Fill:** The visualization preview fills the available space. If you change the width of the side pane or height of the bottom pane the visualization changes to fill the available space. - - **Actual:** The visualization preview has the exact size as the size on the dashboard. If not enough space is available, the visualization scales down preserving the aspect ratio. - - **Time range controls:** **Default** is either the browser local timezone or the timezone selected at a higher level. +## Panel display options -1. Data section: The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable). - - - **Query tab:** Select your data source and enter queries here. For more information, refer to [Add a query][]. When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Query** tab. - - **Transform tab:** Apply data transformations. For more information, refer to [Transform data][]. - - **Alert tab:** Write alert rules. For more information, refer to [the overview of Grafana Alerting][]. - -1. Panel display options: The display options section contains tabs where you configure almost every aspect of your data visualization. - -## Panel inspect drawer - -The inspect drawer helps you understand and troubleshoot your panels. You can view the raw data for any panel, export that data to a comma-separated values (CSV) file, view query requests, and export panel and data JSON. - -To access the panel inspect drawer from the edit view, hover over any part of the panel to display the actions menu on the top right corner. Click the menu and select **Inspect**. - -{{% admonition type="note" %}} -Not all panel types include all tabs. For example, dashboard list panels don't have raw data to inspect, so they don't display the Stats, Data, or Query tabs. -{{% /admonition %}} - -The panel inspector consists of the following options: - -1. The panel inspect drawer displays opens a drawer on the right side. Click the arrow in the upper right corner to expand or reduce the drawer pane. - -1. **Data tab -** Shows the raw data returned by the query with transformations applied. Field options such as overrides and value mappings aren't applied by default. - -1. **Stats tab -** Shows how long your query takes and how much it returns. - -1. **JSON tab -** Allows you to view and copy the panel JSON, panel data JSON, and data frame structure JSON. This is useful if you are provisioning or administering Grafana. - -1. **Query tab -** Shows you the requests to the server sent when Grafana queries the data source. - -1. **Error tab -** Shows the error. Only visible when query returns error. +The display options section contains tabs where you configure almost every aspect of your data visualization. {{% docs/reference %}} [Table]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/table" @@ -121,10 +76,4 @@ The panel inspector consists of the following options: [the overview of Grafana Alerting]: "/docs/grafana/ -> /docs/grafana//alerting" [the overview of Grafana Alerting]: "/docs/grafana-cloud/ -> /docs/grafana//alerting" - -[share the same query results across panels]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/share-query" -[share the same query results across panels]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/share-query" - -[create a Grafana-managed alert]: "/docs/grafana/ -> /docs/grafana//alerting/alerting-rules/create-grafana-managed-rule#create-alerts-from-panels" -[create a Grafana-managed alert]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/create-grafana-managed-rule#create-alerts-from-panels" {{% /docs/reference %}} diff --git a/docs/sources/panels-visualizations/panel-overview/index.md b/docs/sources/panels-visualizations/panel-overview/index.md new file mode 100644 index 00000000000..bcdbd4f2e02 --- /dev/null +++ b/docs/sources/panels-visualizations/panel-overview/index.md @@ -0,0 +1,161 @@ +--- +keywords: + - transform + - query + - panel + - dashboard + - rows + - dynamic + - add +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Panel overview +title: Panel overview +description: Learn about the features of the panel +weight: 15 +--- + +# Panel overview + +A Grafana panel is a visual representation of data composed of a [query][] and a [visualization][]. Within panels, you can apply [transformations][], which process the results of a query before they're passed on for visualization. You can also further customize a panel by formatting data and configuring visualization options. + +Each panel has a query editor specific to the data source selected in the panel. The query editor allows you to build a query that returns the data you want to visualize. + +Panels offer a wide variety of formatting and styling options, from applying colors based on field values to creating custom units. Each visualization also comes with options specific to it that give you further control over how your data is displayed. Panels can also be dragged, dropped, and resized to rearrange them on the dashboard. + +To get started adding panels, ensure that you have configured a data source: + +- For details about using data sources, refer to [Data sources][]. +- For more information about managing data sources as an administrator, refer to [Data source management][]. + + {{% admonition type="note" %}} + [Data source management](https://grafana.com/docs/grafana//administration/data-source-management/) is only available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](https://grafana.com/docs/grafana-cloud/). + {{% /admonition %}} + +## Panel feature overview + +The panel has the following features: + + + + + +- **Panel title** - You can create your own panel titles or have Grafana create them for you using [generative AI features][ai]. +- **Links** - Add [panel links][] to other dashboards, panels, or external sites. +- **Panel menu** - In the [panel menu](#panel-menu), access actions such as **View**, **Edit**, **Inspect**, and **Remove**. +- **Tooltips** - View [tooltips][] to get more information about data points. +- **Legend** - Change series colors, y-axis and series visibility directly from the [legend][]. + +## Panel menu + +To access the panel editor, hover over the top-right corner of any panel. Click the panel menu icon that appears and select **Edit**. The panel menu gives you access to the following actions: + +- **View**: View the panel in full screen. +- **Edit**: Open the panel editor to edit panel and visualization options. +- **Share**: Share the panel as a link, embed, or library panel. +- **Explore**: Open the panel in **Explore**, where you can focus on your query. +- **Inspect**: Open the **Inspect** drawer, where you can review the panel data, stats, metadata, JSON, and query. + - **Data**: Open the **Inspect** drawer in the **Data** tab. + - **Query**: Open the **Inspect** drawer in the **Query** tab. + - **Panel JSON**: Open the **Inspect** drawer in the **JSON** tab. +- **Extensions**: Access other actions provided by installed applications, such as declaring an incident. Note that this option doesn't appear unless you have app plugins installed which contribute an [extension](https://grafana.com/developers/plugin-tools/ui-extensions/) to the panel menu. +- **More**: Access other panel actions. + - **Duplicate**: Make a copy of the panel. Duplicated panels query data separately from the original panel. You can use the special `Dashboard` data source to [share the same query results across panels][share] instead. + - **Copy**: Copy the panel to the clipboard. + - **Create library panel**: Create a panel that can be imported into other dashboards. + - **Create alert**: Open the alert rule configuration page in **Alerting**, where you can [create a Grafana-managed alert][create] based on the panel queries. + - **Hide legend**: Hide the panel legend. + - **Get help**: Send a snapshot or panel data to Grafana Labs Technical Support. +- **Remove**: Remove the panel from the dashboard. + +## Keyboard shortcuts + +Grafana has a number of keyboard shortcuts available specifically for panels. Press `?` on your keyboard to display all keyboard shortcuts available in your version of Grafana. + +By hovering over a panel with the mouse you can use some shortcuts that will target that panel. + +- `e`: Toggle panel edit view +- `v`: Toggle panel fullscreen view +- `ps`: Open Panel Share Modal +- `pd`: Duplicate Panel +- `pr`: Remove Panel +- `pl`: Toggle panel legend + +## Add a panel + +To add a panel in a new dashboard click **+ Add visualization** in the middle of the dashboard: + +![Empty dashboard state](/media/docs/grafana/dashboards/empty-dashboard-10.2.png) + +To add a panel to an existing dashboard, click **Add** in the dashboard header and select **Visualization** in the drop-down: + +![Add dropdown](/media/docs/grafana/dashboards/screenshot-add-dropdown-10.0.png) + +## Panel configuration + +To configure panels, refer to the following subtopics: + +- [Configure panel options][] +- [Configure standard options][] +- [Configure a legend][legend] +- [Configure tooltips][tooltips] +- [Configure data links][] +- [Configure value mappings][] +- [Configure thresholds][] +- [Configure field overrides][] + +{{% docs/reference %}} +[query]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data" +[query]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data" + +[visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations" +[visualization]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/visualizations" + +[transformations]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/transform-data" +[transformations]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/transform-data" + +[Data source management]: "/docs/grafana/ -> /docs/grafana//administration/data-source-management" +[Data source management]: "/docs/grafana-cloud/ -> /docs/grafana//administration/data-source-management" + +[Data sources]: "/docs/grafana/ -> /docs/grafana//datasources" +[Data sources]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/connect-externally-hosted/data-sources" + +[ai]: "/docs/grafana/ -> /docs/grafana//dashboards/manage-dashboards#set-up-generative-ai-features-for-dashboards" +[ai]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/manage-dashboards#set-up-generative-ai-features-for-dashboards" + +[panel links]: "/docs/grafana/ -> /docs/grafana//dashboards/build-dashboards/manage-dashboard-links#panel-links" +[panel links]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/dashboards/build-dashboards/manage-dashboard-links#panel-links" + +[tooltips]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-tooltips" +[tooltips]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-tooltips" + +[legend]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-legend" +[legend]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-legend" + +[share]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/share-query" +[share]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/share-query" + +[create]: "/docs/grafana/ -> /docs/grafana//alerting/alerting-rules/create-grafana-managed-rule#create-alerts-from-panels" +[create]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/create-grafana-managed-rule#create-alerts-from-panels" + +[Configure panel options]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-panel-options" +[Configure panel options]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options" + +[Configure standard options]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-standard-options" +[Configure standard options]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-standard-options" + +[Configure data links]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-data-links" +[Configure data links]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-data-links" + +[Configure value mappings]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-value-mappings" +[Configure value mappings]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-value-mappings" + +[Configure thresholds]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-thresholds" +[Configure thresholds]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-thresholds" + +[Configure field overrides]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-overrides" +[Configure field overrides]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides" +{{% /docs/reference %}} diff --git a/docs/sources/panels-visualizations/query-transform-data/_index.md b/docs/sources/panels-visualizations/query-transform-data/_index.md index 356fca6533c..be31482eaab 100644 --- a/docs/sources/panels-visualizations/query-transform-data/_index.md +++ b/docs/sources/panels-visualizations/query-transform-data/_index.md @@ -190,6 +190,8 @@ Panel data source query options include: | This week so far | `now/w` | | Last 2 years | `now-2y/y` | +{{< docs/play title="Time range override" url="https://play.grafana.org/d/000000041/" >}} + - **Time shift:** Overrides the time range for individual panels by shifting its start and end relative to the time picker. For example, you can shift the time range for the panel to be two hours earlier than the dashboard time picker. diff --git a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md index c773210ef42..263d56c3c11 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md @@ -23,7 +23,9 @@ Bar gauges simplify your data by reducing every field to a single value. You cho This panel can show one or more bar gauges depending on how many series, rows, or columns your query returns. -{{< figure src="/static/img/docs/v66/bar_gauge_cover.png" max-width="1025px" caption="Stat panel" >}} +{{< figure src="/static/img/docs/v66/bar_gauge_cover.png" max-width="1025px" alt="Bar gauge panel" >}} + +{{< docs/play title="Bar Gauge" url="https://play.grafana.org/d/vmie2cmWz/" >}} ## Value options diff --git a/docs/sources/panels-visualizations/visualizations/heatmap/index.md b/docs/sources/panels-visualizations/visualizations/heatmap/index.md index 73f7f8f5058..70efed729ca 100644 --- a/docs/sources/panels-visualizations/visualizations/heatmap/index.md +++ b/docs/sources/panels-visualizations/visualizations/heatmap/index.md @@ -19,14 +19,52 @@ weight: 100 # Heatmap -Heatmaps allow you to view histograms over time. For more information about histograms, refer to [Introduction to histograms and heatmaps][]. +Heatmaps allow you to view [histograms](https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/histogram/) over time. While histograms display the data distribution that falls in a specific value range, heatmaps allow you to identify patterns in the histogram data distribution over time. For more information about heatmaps, refer to [Introduction to histograms and heatmaps](https://grafana.com/docs/grafana/latest/fundamentals/intro-histograms/). -![A heatmap visualization](/static/img/docs/v43/heatmap_panel_cover.jpg) +For example, if you want to understand the temperature changes for the past few years, you can use a heatmap visualization to identify trends in your data: -## Calculate from data +{{< figure src="/static/img/docs/heatmap-panel/temperature_heatmap.png" max-width="1025px" alt="A heatmap visualization showing the random walk distribution over time" >}} -This setting determines if the data is already a calculated heatmap (from the data source/transformer), or one that should be -calculated in the panel. +You can use a heatmap visualization if you need to: + +- Visualize a large density of your data distribution. +- Condense large amounts of data through various color schemes that are easier to interpret. +- Identify any outliers in your data distribution. +- Provide statistical analysis to see how values or trends change over time. + +## Configure a heatmap visualization + +Once you’ve created a [dashboard](https://grafana.com/docs/grafana//dashboards/build-dashboards/create-dashboard/), the following video shows you how to configure a heatmap visualization: + +{{< youtube id="SGWBzQ54koE" >}} + +## Supported data formats + +Heatmaps support time series data. + +### Example + +The table below is a simplified output of random walk distribution over time: + +| Time | Walking (km) | +| ------------------- | ------------ | +| 2023-06-25 21:13:09 | 10 | +| 2023-08-25 21:13:10 | 8 | +| 2023-08-30 21:13:10 | 10 | +| 2023-10-08 21:13:11 | 12 | +| 2023-12-25 21:13:11 | 14 | +| 2024-01-05 21:13:12 | 13 | +| 2024-02-22 21:13:13 | 10 | + +The data is converted as follows: + +{{< figure src="/static/img/docs/heatmap-panel/heatmap.png" max-width="1025px" alt="A heatmap visualization showing the random walk distribution over time" >}} + +## Heatmap options + +### Calculate from data + +This setting determines if the data is already a calculated heatmap (from the data source/transformer), or one that should be calculated in the panel. ### X Bucket diff --git a/docs/sources/panels-visualizations/visualizations/histogram/index.md b/docs/sources/panels-visualizations/visualizations/histogram/index.md index a406d7f1193..1c2583154d8 100644 --- a/docs/sources/panels-visualizations/visualizations/histogram/index.md +++ b/docs/sources/panels-visualizations/visualizations/histogram/index.md @@ -25,7 +25,7 @@ Histograms calculate the distribution of values and present them as a bar chart. For example, if you want to understand the distribution of people's heights, you can use a histogram visualization to identify patterns or insights in the data distribution: -{{< figure src="/static/img/docs/histogram-panel/histogram-example-v8-0.png" max-width="625px" alt="A histogram visualization showing the distribution of people's heights" >}} +{{< figure src="/static/img/docs/histogram-panel/histogram-example-v8-0.png" max-width="1025px" alt="A histogram visualization showing the distribution of people's heights" >}} You can use a histogram visualization if you need to: @@ -61,7 +61,7 @@ The following tables are examples of the type of data you need for a histogram v The data is converted as follows: -{{< figure src="/static/img/docs/histogram-panel/histogram-example-time-series.png" max-width="1025px" alt="A histogram visualization showing the walk distribution from random people over time." >}} +{{< figure src="/static/img/docs/histogram-panel/histogram-example-time-series.png" max-width="1025px" alt="A histogram visualization showing the random walk distribution." >}} #### Basic numerical table diff --git a/docs/sources/panels-visualizations/visualizations/logs/index.md b/docs/sources/panels-visualizations/visualizations/logs/index.md index 17739cafdef..418a9a51b94 100644 --- a/docs/sources/panels-visualizations/visualizations/logs/index.md +++ b/docs/sources/panels-visualizations/visualizations/logs/index.md @@ -24,7 +24,9 @@ weight: 100 The logs visualization shows log lines from data sources that support logs, such as Elastic, Influx, and Loki. Typically you would use this visualization next to a graph visualization to display the log output of a related process. - +{{< figure src="/static/img/docs/v64/logs-panel.png" max-width="1025px" alt="Logs panel" >}} + +{{< docs/play title="Logs Panel" url="https://play.grafana.org/d/6NmftOxZz/" >}} The logs visualization shows the result of queries that were entered in the Query tab. The results of multiple queries are merged and sorted by time. You can scroll inside the panel if the data source returns more lines than can be displayed at any one time. @@ -56,6 +58,7 @@ Use these settings to refine your visualization: - **Wrap lines -** Toggle line wrapping. - **Prettify JSON -** Set this to `true` to pretty print all JSON logs. This setting does not affect logs in any format other than JSON. - **Enable log details -** Toggle option to see the log details view for each log row. The default setting is true. +- **Deduplication -** Hides log messages that are duplicates of others shown according to criteria such as exact match, or those that only differ by numbers such as IPs or latencies. - **Order -** Display results in descending or ascending time order. The default is **Descending**, showing the newest logs first. Set to **Ascending** to show the oldest log lines first. {{% docs/reference %}} diff --git a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md index 5384c8143f6..ff8ede7aeb5 100644 --- a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md +++ b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md @@ -35,6 +35,8 @@ The state timeline visualization is useful when you need to monitor and analyze {{< youtube id="a9wZHM0mdxo" >}} +{{< docs/play title="Grafana State Timeline & Status History" url="https://play.grafana.org/d/qD-rVv6Mz/6-state-timeline-and-status-history?orgId=1s" >}} + ## Supported data formats The state timeline visualization works best if you have data capturing the various states of entities over time, formatted as a table. The data must include: diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/panels-visualizations/visualizations/status-history/index.md index a985512a681..e73cf0b3281 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/panels-visualizations/visualizations/status-history/index.md @@ -41,6 +41,8 @@ Once you've [created a dashboard](https://grafana.com/docs/grafana/}} +{{< docs/play title="Grafana State Timeline & Status History" url="https://play.grafana.org/d/qD-rVv6Mz/6-state-timeline-and-status-history?orgId=1s" >}} + ## Supported data formats The status history visualization works best if you have data capturing the various status of entities over time, formatted as a table. The data must include: diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 0d755b205e1..dadf12020cc 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -49,6 +49,7 @@ Some features are enabled by default. You can disable these feature by setting t | `dashgpt` | Enable AI powered features in dashboards | Yes | | `alertingInsights` | Show the new alerting insights landing page | Yes | | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | +| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes | | `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | | `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes | @@ -59,7 +60,6 @@ Some features are enabled by default. You can disable these feature by setting t | `lokiQueryHints` | Enables query hints for Loki | Yes | | `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | | | `betterPageScrolling` | Removes CustomScrollbar from the UI, relying on native browser scrollbars | Yes | -| `usePrometheusFrontendPackage` | Use the @grafana/prometheus frontend package in core Prometheus. | | | `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes | ## Preview feature toggles @@ -150,7 +150,6 @@ Experimental features might be changed or removed without prior notice. | `pluginsAPIMetrics` | Sends metrics of public grafana packages usage by plugins | | `idForwarding` | Generate signed id token for identity that can be forwarded to plugins and external services | | `enableNativeHTTPHistogram` | Enables native HTTP Histograms | -| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | | `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint | | `cachingOptimizeSerializationMemoryUsage` | If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses. | | `prometheusPromQAIL` | Prometheus and AI/ML to assist users in creating a query | @@ -175,6 +174,7 @@ Experimental features might be changed or removed without prior notice. | `kubernetesAggregator` | Enable grafana aggregator | | `expressionParser` | Enable new expression parser | | `accessActionSets` | Introduces action sets for resource permissions | +| `disableNumericMetricsSortingInExpressions` | In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. | ## Development feature toggles diff --git a/docs/sources/tutorials/alerting-get-started/index.md b/docs/sources/tutorials/alerting-get-started/index.md index 83827ef935e..490797e8584 100644 --- a/docs/sources/tutorials/alerting-get-started/index.md +++ b/docs/sources/tutorials/alerting-get-started/index.md @@ -152,9 +152,9 @@ In this section, we define queries, expressions (used to manipulate the data), a This [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query calculates the sum of the per-second average rates of increase of the `tns_request_duration_seconds_count` metric over the last 1 minute, grouped by the HTTP method used in the requests. This can be useful for analyzing the request duration trends for different HTTP methods. -1. Keep expressions “B” and “C” as they are. These expressions (**Reduce** and **Threshold**, respectively) come by default when creating a new rule. +1. Remove expression "B" (click the bin icon). The **Reduce** expression comes by default, and in this case, it is not needed since the queried data is already reduced. - The Reduce expression “B”, selects the last value of our query “A”, while the Threshold expression “C” will check if the last value from expression “B” is above a specific value. In addition, the Threshold expression is the alert condition by default. Enter `0.2` as threshold value. You can read more about queries and conditions [here](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/#expression-queries). +1. In expression "C", **Threshold**, change the **Input** to "A". By selecting the input, you're telling Grafana which data source and which specific metric within that data source the expression should evaluate. The Threshold expression is now the alert condition. Enter `0.2` as threshold value. You can read more about queries and conditions [here](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/#expression-queries). 1. Click Preview to run the queries. diff --git a/e2e/various-suite/query-editor.spec.ts b/e2e/various-suite/query-editor.spec.ts index 3441f2b4c0d..f9149f7b280 100644 --- a/e2e/various-suite/query-editor.spec.ts +++ b/e2e/various-suite/query-editor.spec.ts @@ -6,14 +6,16 @@ describe('Query editor', () => { e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); }); - it('Undo should work in query editor for prometheus -- test CI.', () => { + // x-ing to bypass this flaky test. + // Will rewrite in plugin-e2e with this issue + xit('Undo should work in query editor for prometheus -- test CI.', () => { e2e.pages.Explore.visit(); e2e.components.DataSourcePicker.container().should('be.visible').click(); cy.contains('gdev-prometheus').scrollIntoView().should('be.visible').click(); const queryText = `rate(http_requests_total{job="grafana"}[5m])`; - e2e.components.RadioButton.container().filter(':contains("Code")').click(); + e2e.components.RadioButton.container().filter(':contains("Code")').should('be.visible').click(); waitForMonacoToLoad(); diff --git a/go.mod b/go.mod index 17ac82f1891..772be49da9e 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014 replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.49.0 require ( - cloud.google.com/go/storage v1.36.0 // @grafana/backend-platform + cloud.google.com/go/storage v1.37.0 // @grafana/backend-platform cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/backend-platform @@ -51,7 +51,7 @@ require ( github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources - github.com/grafana/grafana-plugin-sdk-go v0.222.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana-plugin-sdk-go v0.224.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend @@ -101,8 +101,8 @@ require ( golang.org/x/time v0.5.0 // @grafana/backend-platform golang.org/x/tools v0.17.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics - google.golang.org/api v0.155.0 // @grafana/backend-platform - google.golang.org/grpc v1.62.1 // @grafana/plugins-platform-backend + google.golang.org/api v0.162.0 // @grafana/backend-platform + google.golang.org/grpc v1.63.2 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.33.0 // @grafana/plugins-platform-backend gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-squad-backend @@ -152,13 +152,13 @@ require ( github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // @grafana/backend-platform + github.com/golang/protobuf v1.5.4 // @grafana/backend-platform github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect github.com/googleapis/gax-go/v2 v2.12.0 // @grafana/backend-platform github.com/gorilla/mux v1.8.1 // @grafana/backend-platform github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad @@ -199,16 +199,16 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend - go.uber.org/goleak v1.3.0 // indirect + go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // @grafana/backend-platform golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect; @grafana/backend-platform + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/backend-platform ) require ( - cloud.google.com/go/kms v1.15.5 // @grafana/backend-platform + cloud.google.com/go/kms v1.15.7 // @grafana/backend-platform github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // @grafana/backend-platform github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // @grafana/backend-platform github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/backend-platform @@ -243,7 +243,7 @@ require ( github.com/dave/dst v0.27.2 // @grafana/grafana-as-code github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics - github.com/grafana/dataplane/sdata v0.0.7 // @grafana/observability-metrics + github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 // @grafana/grafana-bi-squad github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-squad-backend @@ -359,8 +359,8 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/term v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect @@ -382,8 +382,8 @@ require ( ) require ( - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go/iam v1.1.6 // indirect filippo.io/age v1.1.1 // @grafana/identity-access-team github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect diff --git a/go.sum b/go.sum index 92fd1692e31..6171999cd2c 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,9 @@ cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -610,8 +611,9 @@ cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+K cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -651,8 +653,9 @@ cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= cloud.google.com/go/kms v1.15.3/go.mod h1:AJdXqHxS2GlPyduM99s9iGqi2nwbviBbhV/hdmt4iOQ= cloud.google.com/go/kms v1.15.4/go.mod h1:L3Sdj6QTHK8dfwK5D1JLsAyELsNMnd3tAIwGS4ltKpc= -cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= +cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= +cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -1036,8 +1039,8 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4= +cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -2009,8 +2012,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -2171,8 +2175,8 @@ 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/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg= -github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9ChK0gH164= -github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= +github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= +github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb h1:AWE6+kvtE18HP+lRWNUCyvymyrFSXs6TcS2vXIXGIuw= github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb/go.mod h1:kkWM4WUV230bNG3urVRWPBnSJHs64y/0RmWjftnnn0c= github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQUsys3BHG8jnmniJ2Q74tXAG1NaDo= @@ -2186,8 +2190,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.222.0 h1:bWK35de9Kl9+lRGyh+41zW2X8MkQ4959aqUsi08/8ig= -github.com/grafana/grafana-plugin-sdk-go v0.222.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= +github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= +github.com/grafana/grafana-plugin-sdk-go v0.224.0/go.mod h1:dR9hYmI18hOMArvMrQ1O7oiNOUqCGbxDAe2t6z1xLH8= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= @@ -3938,8 +3942,8 @@ google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWL google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -4127,8 +4131,8 @@ google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+ google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -4147,8 +4151,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4171,8 +4175,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4230,8 +4234,8 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/go.work.sum b/go.work.sum index 70201be8575..715899a2b94 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2,173 +2,363 @@ buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127 buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4 h1:z3Xc9n8yZ5k/Xr4ZTuff76TAYP20dWy7ZBV4cGIpbkM= cloud.google.com/go/accessapproval v1.7.4 h1:ZvLvJ952zK8pFHINjpMBY5k7LTAp/6pBf50RDMRgBUI= +cloud.google.com/go/accessapproval v1.7.5 h1:uzmAMSgYcnlHa9X9YSQZ4Q1wlfl4NNkZyQgho1Z6p04= +cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0= cloud.google.com/go/accesscontextmanager v1.8.4 h1:Yo4g2XrBETBCqyWIibN3NHNPQKUfQqti0lI+70rubeE= +cloud.google.com/go/accesscontextmanager v1.8.5 h1:2GLNaNu9KRJhJBFTIVRoPwk6xE5mUDgD47abBq4Zp/I= +cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q= cloud.google.com/go/aiplatform v1.57.0 h1:WcZ6wDf/1qBWatmGM9Z+2BTiNjQQX54k2BekHUj93DQ= cloud.google.com/go/aiplatform v1.58.0 h1:xyCAfpI4yUMOQ4VtHN/bdmxPQ8xoEkTwFM1nbVmuQhs= -cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.60.0 h1:0cSrii1ZeLr16MbBoocyy5KVnrSdiQ3KN/vtrTe7RqE= +cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM= cloud.google.com/go/analytics v0.21.6 h1:fnV7B8lqyEYxCU0LKk+vUL7mTlqRAq4uFlIthIdr/iA= cloud.google.com/go/analytics v0.22.0 h1:w8KIgW8NRUHFVKjpkwCpLaHsr685tJ+ckPStOaSCZz0= -cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.23.0 h1:Q+y94XH84jM8SK8O7qiY/PJRexb6n7dRbQ6PiUa4YGM= +cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0= cloud.google.com/go/apigateway v1.6.4 h1:VVIxCtVerchHienSlaGzV6XJGtEM9828Erzyr3miUGs= +cloud.google.com/go/apigateway v1.6.5 h1:sPXnpk+6TneKIrjCjcpX5YGsAKy3PTdpIchoj8/74OE= +cloud.google.com/go/apigateway v1.6.5/go.mod h1:6wCwvYRckRQogyDDltpANi3zsCDl6kWi0b4Je+w2UiI= cloud.google.com/go/apigeeconnect v1.6.4 h1:jSoGITWKgAj/ssVogNE9SdsTqcXnryPzsulENSRlusI= +cloud.google.com/go/apigeeconnect v1.6.5 h1:CrfIKv9Go3fh/QfQgisU3MeP90Ww7l/sVGmr3TpECo8= +cloud.google.com/go/apigeeconnect v1.6.5/go.mod h1:MEKm3AiT7s11PqTfKE3KZluZA9O91FNysvd3E6SJ6Ow= cloud.google.com/go/apigeeregistry v0.8.2 h1:DSaD1iiqvELag+lV4VnnqUUFd8GXELu01tKVdWZrviE= +cloud.google.com/go/apigeeregistry v0.8.3 h1:C+QU2K+DzDjk4g074ouwHQGkoff1h5OMQp6sblCVreQ= +cloud.google.com/go/apigeeregistry v0.8.3/go.mod h1:aInOWnqF4yMQx8kTjDqHNXjZGh/mxeNlAf52YqtASUs= cloud.google.com/go/apikeys v0.6.0 h1:B9CdHFZTFjVti89tmyXXrO+7vSNo2jvZuHG8zD5trdQ= cloud.google.com/go/appengine v1.8.4 h1:Qub3fqR7iA1daJWdzjp/Q0Jz0fUG0JbMc7Ui4E9IX/E= +cloud.google.com/go/appengine v1.8.5 h1:l2SviT44zWQiOv8bPoMBzW0vOcMO22iO0s+nVtVhdts= +cloud.google.com/go/appengine v1.8.5/go.mod h1:uHBgNoGLTS5di7BvU25NFDuKa82v0qQLjyMJLuPQrVo= cloud.google.com/go/area120 v0.8.4 h1:YnSO8m02pOIo6AEOgiOoUDVbw4pf+bg2KLHi4rky320= +cloud.google.com/go/area120 v0.8.5 h1:vTs08KPLN/iMzTbxpu5ciL06KcsrVPMjz4IwcQyZ4uY= +cloud.google.com/go/area120 v0.8.5/go.mod h1:BcoFCbDLZjsfe4EkCnEq1LKvHSK0Ew/zk5UFu6GMyA0= cloud.google.com/go/artifactregistry v1.14.6 h1:/hQaadYytMdA5zBh+RciIrXZQBWK4vN7EUsrQHG+/t8= +cloud.google.com/go/artifactregistry v1.14.7 h1:W9sVlyb1VRcUf83w7aM3yMsnp4HS4PoyGqYQNG0O5lI= +cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM= cloud.google.com/go/asset v1.15.3 h1:uI8Bdm81s0esVWbWrTHcjFDFKNOa9aB7rI1vud1hO84= cloud.google.com/go/asset v1.17.0 h1:dLWfTnbwyrq/Kt8Tr2JiAbre1MEvS2Bl5cAMiYAy5Pg= -cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.2 h1:xgFnBP3luSbUcC9RWJvb3Zkt+y/wW6PKwPHr3ssnIP8= +cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4= cloud.google.com/go/assuredworkloads v1.11.4 h1:FsLSkmYYeNuzDm8L4YPfLWV+lQaUrJmH5OuD37t1k20= +cloud.google.com/go/assuredworkloads v1.11.5 h1:gCrN3IyvqY3cP0wh2h43d99CgH3G+WYs9CeuFVKChR8= +cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk= cloud.google.com/go/automl v1.13.4 h1:i9tOKXX+1gE7+rHpWKjiuPfGBVIYoWvLNIGpWgPtF58= +cloud.google.com/go/automl v1.13.5 h1:ijiJy9sYWh75WrqImXsfWc1e3HR3iO+ef9fvW03Ig/4= +cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y= cloud.google.com/go/baremetalsolution v1.2.3 h1:oQiFYYCe0vwp7J8ZmF6siVKEumWtiPFJMJcGuyDVRUk= +cloud.google.com/go/baremetalsolution v1.2.4 h1:LFydisRmS7hQk9P/YhekwuZGqb45TW4QavcrMToWo5A= +cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY= cloud.google.com/go/batch v1.7.0 h1:AxuSPoL2fWn/rUyvWeNCNd0V2WCr+iHRCU9QO1PUmpY= -cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.8.0 h1:2HK4JerwVaIcCh/lJiHwh6+uswPthiMMWhiSWLELayk= +cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc= cloud.google.com/go/beyondcorp v1.0.3 h1:VXf9SnrnSmj2BF2cHkoTHvOUp8gjsz1KJFOMW7czdsY= +cloud.google.com/go/beyondcorp v1.0.4 h1:qs0J0O9Ol2h1yA0AU+r7l3hOCPzs2MjE1d6d/kaHIKo= +cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc= cloud.google.com/go/bigquery v1.57.1 h1:FiULdbbzUxWD0Y4ZGPSVCDLvqRSyCIO6zKV7E2nf5uA= cloud.google.com/go/bigquery v1.58.0 h1:drSd9RcPVLJP2iFMimvOB9SCSIrcl+9HD4II03Oy7A0= -cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= +cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4= +cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc= cloud.google.com/go/billing v1.18.0 h1:GvKy4xLy1zF1XPbwP5NJb2HjRxhnhxjjXxvyZ1S/IAo= -cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.2 h1:oWUEQvuC4JvtnqLZ35zgzdbuHt4Itbftvzbe6aEyFdE= +cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE= cloud.google.com/go/binaryauthorization v1.8.0 h1:PHS89lcFayWIEe0/s2jTBiEOtqghCxzc7y7bRNlifBs= -cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.1 h1:1jcyh2uIUwSZkJ/JmL8kd5SUkL/Krbv8zmYLEbAz6kY= +cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ= cloud.google.com/go/certificatemanager v1.7.4 h1:5YMQ3Q+dqGpwUZ9X5sipsOQ1fLPsxod9HNq0+nrqc6I= +cloud.google.com/go/certificatemanager v1.7.5 h1:UMBr/twXvH3jcT5J5/YjRxf2tvwTYIfrpemTebe0txc= +cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM= cloud.google.com/go/channel v1.17.3 h1:Rd4+fBrjiN6tZ4TR8R/38elkyEkz6oogGDr7jDyjmMY= cloud.google.com/go/channel v1.17.4 h1:yYHOORIM+wkBy3EdwArg/WL7Lg+SoGzlKH9o3Bw2/jE= -cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.5 h1:/omiBnyFjm4S1ETHoOmJbL7LH7Ljcei4rYG6Sj3hc80= +cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc= cloud.google.com/go/cloudbuild v1.15.0 h1:9IHfEMWdCklJ1cwouoiQrnxmP0q3pH7JUt8Hqx4Qbck= +cloud.google.com/go/cloudbuild v1.15.1 h1:ZB6oOmJo+MTov9n629fiCrO9YZPOg25FZvQ7gIHu5ng= +cloud.google.com/go/cloudbuild v1.15.1/go.mod h1:gIofXZSu+XD2Uy+qkOrGKEx45zd7s28u/k8f99qKals= cloud.google.com/go/clouddms v1.7.3 h1:xe/wJKz55VO1+L891a1EG9lVUgfHr9Ju/I3xh1nwF84= +cloud.google.com/go/clouddms v1.7.4 h1:Sr0Zo5EAcPQiCBgHWICg3VGkcdS/LLP1d9SR7qQBM/s= +cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFotwdE79DieY= cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650= +cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= +cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= cloud.google.com/go/contactcenterinsights v1.12.1 h1:EiGBeejtDDtr3JXt9W7xlhXyZ+REB5k2tBgVPVtmNb0= -cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= +cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI= cloud.google.com/go/container v1.29.0 h1:jIltU529R2zBFvP8rhiG1mgeTcnT27KhU0H/1d6SQRg= -cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.31.0 h1:MAaNH7VRNPWEhvqOypq2j+7ONJKrKzon4v9nS3nLZe0= +cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA= cloud.google.com/go/containeranalysis v0.11.3 h1:5rhYLX+3a01drpREqBZVXR9YmWH45RnML++8NsCtuD8= +cloud.google.com/go/containeranalysis v0.11.4 h1:doJ0M1ljS4hS0D2UbHywlHGwB7sQLNrt9vFk9Zyi7vY= +cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE= cloud.google.com/go/datacatalog v1.19.0 h1:rbYNmHwvAOOwnW2FPXYkaK3Mf1MmGqRzK0mMiIEyLdo= cloud.google.com/go/datacatalog v1.19.2 h1:BV5sB7fPc8ccv/obwtHwQtCdLMAgI4KyaQWfkh8/mWg= -cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= +cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw= +cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4= cloud.google.com/go/dataflow v0.9.4 h1:7VmCNWcPJBS/srN2QnStTB6nu4Eb5TMcpkmtaPVhRt4= +cloud.google.com/go/dataflow v0.9.5 h1:RYHtcPhmE664+F0Je46p+NvFbG8z//KCXp+uEqB4jZU= +cloud.google.com/go/dataflow v0.9.5/go.mod h1:udl6oi8pfUHnL0z6UN9Lf9chGqzDMVqcYTcZ1aPnCZQ= cloud.google.com/go/dataform v0.9.1 h1:jV+EsDamGX6cE127+QAcCR/lergVeeZdEQ6DdrxW3sQ= +cloud.google.com/go/dataform v0.9.2 h1:5e4eqGrd0iDTCg4Q+VlAao5j2naKAA7xRurNtwmUknU= +cloud.google.com/go/dataform v0.9.2/go.mod h1:S8cQUwPNWXo7m/g3DhWHsLBoufRNn9EgFrMgne2j7cI= cloud.google.com/go/datafusion v1.7.4 h1:Q90alBEYlMi66zL5gMSGQHfbZLB55mOAg03DhwTTfsk= +cloud.google.com/go/datafusion v1.7.5 h1:HQ/BUOP8OIGJxuztpYvNvlb+/U+/Bfs9SO8tQbh61fk= +cloud.google.com/go/datafusion v1.7.5/go.mod h1:bYH53Oa5UiqahfbNK9YuYKteeD4RbQSNMx7JF7peGHc= cloud.google.com/go/datalabeling v0.8.4 h1:zrq4uMmunf2KFDl/7dS6iCDBBAxBnKVDyw6+ajz3yu0= +cloud.google.com/go/datalabeling v0.8.5 h1:GpIFRdm0qIZNsxqURFJwHt0ZBJZ0nF/mUVEigR7PH/8= +cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s= cloud.google.com/go/dataplex v1.13.0 h1:ACVOuxwe7gP0SqEso9SLyXbcZNk5l8hjcTX+XLntI5s= cloud.google.com/go/dataplex v1.14.0 h1:/WhVTR4v/L6ACKjlz/9CqkxkrVh2z7C44CLMUf0f60A= -cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.2 h1:fxIfdU8fxzR3clhOoNI7XFppvAmndxDu1AMH+qX9WKQ= +cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U= cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= cloud.google.com/go/dataproc/v2 v2.3.0 h1:tTVP9tTxmc8fixxOd/8s6Q6Pz/+yzn7r7XdZHretQH0= +cloud.google.com/go/dataproc/v2 v2.4.0 h1:/u81Fd+BvCLp+xjctI1DiWVJn6cn9/s3Akc8xPH02yk= +cloud.google.com/go/dataproc/v2 v2.4.0/go.mod h1:3B1Ht2aRB8VZIteGxQS/iNSJGzt9+CA0WGnDVMEm7Z4= cloud.google.com/go/dataqna v0.8.4 h1:NJnu1kAPamZDs/if3bJ3+Wb6tjADHKL83NUWsaIp2zg= +cloud.google.com/go/dataqna v0.8.5 h1:9ybXs3nr9BzxSGC04SsvtuXaHY0qmJSLIpIAbZo9GqQ= +cloud.google.com/go/dataqna v0.8.5/go.mod h1:vgihg1mz6n7pb5q2YJF7KlXve6tCglInd6XO0JGOlWM= cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= cloud.google.com/go/datastream v1.10.3 h1:Z2sKPIB7bT2kMW5Uhxy44ZgdJzxzE5uKjavoW+EuHEE= +cloud.google.com/go/datastream v1.10.4 h1:o1QDKMo/hk0FN7vhoUQURREuA0rgKmnYapB+1M+7Qz4= +cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo= cloud.google.com/go/deploy v1.16.0 h1:5OVjzm8MPC5kP+Ywbs0mdE0O7AXvAUXksSyHAyMFyMg= cloud.google.com/go/deploy v1.17.0 h1:P3SgJ+4rAktC2XqaI10G0ip/vzWluNBrC5VG0abMbLw= -cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= +cloud.google.com/go/deploy v1.17.1 h1:m27Ojwj03gvpJqCbodLYiVmE9x4/LrHGGMjzc0LBfM4= +cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50= cloud.google.com/go/dialogflow v1.47.0 h1:tLCWad8HZhlyUNfDzDP5m+oH6h/1Uvw/ei7B9AnsWMk= cloud.google.com/go/dialogflow v1.48.1 h1:1Uq2jDJzjJ3M4xYB608FCCFHfW3JmrTmHIxRSd7JGmY= -cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= +cloud.google.com/go/dialogflow v1.49.0 h1:KqG0oxGE71qo0lRVyAoeBozefCvsMfcDzDjoLYSY0F4= +cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0= cloud.google.com/go/dlp v1.11.1 h1:OFlXedmPP/5//X1hBEeq3D9kUVm9fb6ywYANlpv/EsQ= +cloud.google.com/go/dlp v1.11.2 h1:lTipOuJaSjlYnnotPMbEhKURLC6GzCMDDzVbJAEbmYM= +cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w= cloud.google.com/go/documentai v1.23.6 h1:0/S3AhS23+0qaFe3tkgMmS3STxgDgmE1jg4TvaDOZ9g= cloud.google.com/go/documentai v1.23.7 h1:hlYieOXUwiJ7HpBR/vEPfr8nfSxveLVzbqbUkSK0c/4= -cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.25.0 h1:lI62GMEEPO6vXJI9hj+G9WjOvnR0hEjvjokrnex4cxA= +cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY= cloud.google.com/go/domains v0.9.4 h1:ua4GvsDztZ5F3xqjeLKVRDeOvJshf5QFgWGg1CKti3A= +cloud.google.com/go/domains v0.9.5 h1:Mml/R6s3vQQvFPpi/9oX3O5dRirgjyJ8cksK8N19Y7g= +cloud.google.com/go/domains v0.9.5/go.mod h1:dBzlxgepazdFhvG7u23XMhmMKBjrkoUNaw0A8AQB55Y= cloud.google.com/go/edgecontainer v1.1.4 h1:Szy3Q/N6bqgQGyxqjI+6xJZbmvPvnFHp3UZr95DKcQ0= +cloud.google.com/go/edgecontainer v1.1.5 h1:tBY32km78ScpK2aOP84JoW/+wtpx5WluyPUSEE3270U= +cloud.google.com/go/edgecontainer v1.1.5/go.mod h1:rgcjrba3DEDEQAidT4yuzaKWTbkTI5zAMu3yy6ZWS0M= cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0= cloud.google.com/go/essentialcontacts v1.6.5 h1:S2if6wkjR4JCEAfDtIiYtD+sTz/oXjh2NUG4cgT1y/Q= +cloud.google.com/go/essentialcontacts v1.6.6 h1:13eHn5qBnsawxI7mIrv4jRIEmQ1xg0Ztqw5ZGqtUNfA= +cloud.google.com/go/essentialcontacts v1.6.6/go.mod h1:XbqHJGaiH0v2UvtuucfOzFXN+rpL/aU5BCZLn4DYl1Q= cloud.google.com/go/eventarc v1.13.3 h1:+pFmO4eu4dOVipSaFBLkmqrRYG94Xl/TQZFOeohkuqU= +cloud.google.com/go/eventarc v1.13.4 h1:ORkd6/UV5FIdA8KZQDLNZYKS7BBOrj0p01DXPmT4tE4= +cloud.google.com/go/eventarc v1.13.4/go.mod h1:zV5sFVoAa9orc/52Q+OuYUG9xL2IIZTbbuTHC6JSY8s= cloud.google.com/go/filestore v1.8.0 h1:/+wUEGwk3x3Kxomi2cP5dsR8+SIXxo7M0THDjreFSYo= +cloud.google.com/go/filestore v1.8.1 h1:X5G4y/vrUo1B8Nsz93qSWTMAcM8LXbGUldq33OdcdCw= +cloud.google.com/go/filestore v1.8.1/go.mod h1:MbN9KcaM47DRTIuLfQhJEsjaocVebNtNQhSLhKCF5GM= cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= cloud.google.com/go/functions v1.15.4 h1:ZjdiV3MyumRM6++1Ixu6N0VV9LAGlCX4AhW6Yjr1t+U= +cloud.google.com/go/functions v1.16.0 h1:IWVylmK5F6hJ3R5zaRW7jI5PrWhCvtBVU4axQLmXSo4= +cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k= cloud.google.com/go/gaming v1.10.1 h1:5qZmZEWzMf8GEFgm9NeC3bjFRpt7x4S6U7oLbxaf7N8= cloud.google.com/go/gkebackup v1.3.4 h1:KhnOrr9A1tXYIYeXKqCKbCI8TL2ZNGiD3dm+d7BDUBg= +cloud.google.com/go/gkebackup v1.3.5 h1:iuE8KNtTsPOc79qeWoNS8zOWoXPD9SAdOmwgxtlCmh8= +cloud.google.com/go/gkebackup v1.3.5/go.mod h1:KJ77KkNN7Wm1LdMopOelV6OodM01pMuK2/5Zt1t4Tvc= cloud.google.com/go/gkeconnect v0.8.4 h1:1JLpZl31YhQDQeJ98tK6QiwTpgHFYRJwpntggpQQWis= +cloud.google.com/go/gkeconnect v0.8.5 h1:17d+ZSSXKqG/RwZCq3oFMIWLPI8Zw3b8+a9/BEVlwH0= +cloud.google.com/go/gkeconnect v0.8.5/go.mod h1:LC/rS7+CuJ5fgIbXv8tCD/mdfnlAadTaUufgOkmijuk= cloud.google.com/go/gkehub v0.14.4 h1:J5tYUtb3r0cl2mM7+YHvV32eL+uZQ7lONyUZnPikCEo= +cloud.google.com/go/gkehub v0.14.5 h1:RboLNFzf9wEMSo7DrKVBlf+YhK/A/jrLN454L5Tz99Q= +cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA= cloud.google.com/go/gkemulticloud v1.0.3 h1:NmJsNX9uQ2CT78957xnjXZb26TDIMvv+d5W2vVUt0Pg= cloud.google.com/go/gkemulticloud v1.1.0 h1:C2Suwn3uPz+Yy0bxVjTlsMrUCaDovkgvfdyIa+EnUOU= -cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.1 h1:rsSZAGLhyjyE/bE2ToT5fqo1qSW7S+Ubsc9jFOcbhSI= +cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= +cloud.google.com/go/grafeas v0.3.4 h1:D4x32R/cHX3MTofKwirz015uEdVk4uAxvZkZCZkOrF4= +cloud.google.com/go/grafeas v0.3.4/go.mod h1:A5m316hcG+AulafjAbPKXBO/+I5itU4LOdKO2R/uDIc= cloud.google.com/go/gsuiteaddons v1.6.4 h1:uuw2Xd37yHftViSI8J2hUcCS8S7SH3ZWH09sUDLW30Q= +cloud.google.com/go/gsuiteaddons v1.6.5 h1:CZEbaBwmbYdhFw21Fwbo+C35HMe36fTE0FBSR4KSfWg= +cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= cloud.google.com/go/iap v1.9.3 h1:M4vDbQ4TLXdaljXVZSwW7XtxpwXUUarY2lIs66m0aCM= +cloud.google.com/go/iap v1.9.4 h1:94zirc2r4t6KzhAMW0R6Dme005eTP6yf7g6vN4IhRrA= +cloud.google.com/go/iap v1.9.4/go.mod h1:vO4mSq0xNf/Pu6E5paORLASBwEmphXEjgCFg7aeNu1w= cloud.google.com/go/ids v1.4.4 h1:VuFqv2ctf/A7AyKlNxVvlHTzjrEvumWaZflUzBPz/M4= +cloud.google.com/go/ids v1.4.5 h1:xd4U7pgl3GHV+MABnv1BF4/Vy/zBF7CYC8XngkOLzag= +cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1UuBo= cloud.google.com/go/iot v1.7.4 h1:m1WljtkZnvLTIRYW1YTOv5A6H1yKgLHR6nU7O8yf27w= +cloud.google.com/go/iot v1.7.5 h1:munTeBlbqI33iuTYgXy7S8lW2TCgi5l1hA4roSIY+EE= +cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs= cloud.google.com/go/language v1.12.2 h1:zg9uq2yS9PGIOdc0Kz/l+zMtOlxKWonZjjo5w5YPG2A= +cloud.google.com/go/language v1.12.3 h1:iaJZg6K4j/2PvZZVcjeO/btcWWIllVRBhuTFjGO4LXs= +cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8= cloud.google.com/go/lifesciences v0.9.4 h1:rZEI/UxcxVKEzyoRS/kdJ1VoolNItRWjNN0Uk9tfexg= +cloud.google.com/go/lifesciences v0.9.5 h1:gXvN70m2p+4zgJFzaz6gMKaxTuF9WJ0USYoMLWAOm8g= +cloud.google.com/go/lifesciences v0.9.5/go.mod h1:OdBm0n7C0Osh5yZB7j9BXyrMnTRGBJIZonUMxo5CzPw= cloud.google.com/go/logging v1.8.1 h1:26skQWPeYhvIasWKm48+Eq7oUqdcdbwsCVwz5Ys0FvU= cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= cloud.google.com/go/managedidentities v1.6.4 h1:SF/u1IJduMqQQdJA4MDyivlIQ4SrV5qAawkr/ZEREkY= +cloud.google.com/go/managedidentities v1.6.5 h1:+bpih1piZVLxla/XBqeSUzJBp8gv9plGHIMAI7DLpDM= +cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI= cloud.google.com/go/maps v1.6.2 h1:WxxLo//b60nNFESefLgaBQevu8QGUmRV3+noOjCfIHs= cloud.google.com/go/maps v1.6.3 h1:Qqs6Dza+PRp5CZO5AfgPnLwU1k3pp0IMFRDtLpT+aCA= -cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= +cloud.google.com/go/maps v1.6.4 h1:EVCZAiDvog9So46460BGbCasPhi613exoaQbpilMVlk= +cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI= cloud.google.com/go/mediatranslation v0.8.4 h1:VRCQfZB4s6jN0CSy7+cO3m4ewNwgVnaePanVCQh/9Z4= +cloud.google.com/go/mediatranslation v0.8.5 h1:c76KdIXljQHSCb/Cy47S8H4s05A4zbK3pAFGzwcczZo= +cloud.google.com/go/mediatranslation v0.8.5/go.mod h1:y7kTHYIPCIfgyLbKncgqouXJtLsU+26hZhHEEy80fSs= cloud.google.com/go/memcache v1.10.4 h1:cdex/ayDd294XBj2cGeMe6Y+H1JvhN8y78B9UW7pxuQ= +cloud.google.com/go/memcache v1.10.5 h1:yeDv5qxRedFosvpMSEswrqUsJM5OdWvssPHFliNFTc4= +cloud.google.com/go/memcache v1.10.5/go.mod h1:/FcblbNd0FdMsx4natdj+2GWzTq+cjZvMa1I+9QsuMA= cloud.google.com/go/metastore v1.13.3 h1:94l/Yxg9oBZjin2bzI79oK05feYefieDq0o5fjLSkC8= +cloud.google.com/go/metastore v1.13.4 h1:dR7vqWXlK6IYR8Wbu9mdFfwlVjodIBhd1JRrpZftTEg= +cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE= cloud.google.com/go/monitoring v1.16.3 h1:mf2SN9qSoBtIgiMA4R/y4VADPWZA7VCNJA079qLaZQ8= cloud.google.com/go/monitoring v1.17.0 h1:blrdvF0MkPPivSO041ihul7rFMhXdVp8Uq7F59DKXTU= -cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= +cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= cloud.google.com/go/networkconnectivity v1.14.3 h1:e9lUkCe2BexsqsUc2bjV8+gFBpQa54J+/F3qKVtW+wA= +cloud.google.com/go/networkconnectivity v1.14.4 h1:GBfXFhLyPspnaBE3nI/BRjdhW8vcbpT9QjE/4kDCDdc= +cloud.google.com/go/networkconnectivity v1.14.4/go.mod h1:PU12q++/IMnDJAB+3r+tJtuCXCfwfN+C6Niyj6ji1Po= cloud.google.com/go/networkmanagement v1.9.3 h1:HsQk4FNKJUX04k3OI6gUsoveiHMGvDRqlaFM2xGyvqU= +cloud.google.com/go/networkmanagement v1.9.4 h1:aLV5GcosBNmd6M8+a0ekB0XlLRexv4fvnJJrYnqeBcg= +cloud.google.com/go/networkmanagement v1.9.4/go.mod h1:daWJAl0KTFytFL7ar33I6R/oNBH8eEOX/rBNHrC/8TA= cloud.google.com/go/networksecurity v0.9.4 h1:947tNIPnj1bMGTIEBo3fc4QrrFKS5hh0bFVsHmFm4Vo= +cloud.google.com/go/networksecurity v0.9.5 h1:+caSxBTj0E8OYVh/5wElFdjEMO1S/rZtE1152Cepchc= +cloud.google.com/go/networksecurity v0.9.5/go.mod h1:KNkjH/RsylSGyyZ8wXpue8xpCEK+bTtvof8SBfIhMG8= cloud.google.com/go/notebooks v1.11.2 h1:eTOTfNL1yM6L/PCtquJwjWg7ZZGR0URFaFgbs8kllbM= +cloud.google.com/go/notebooks v1.11.3 h1:FH48boYmrWVQ6k0Mx/WrnNafXncT5iSYxA8CNyWTgy0= +cloud.google.com/go/notebooks v1.11.3/go.mod h1:0wQyI2dQC3AZyQqWnRsp+yA+kY4gC7ZIVP4Qg3AQcgo= cloud.google.com/go/optimization v1.6.2 h1:iFsoexcp13cGT3k/Hv8PA5aK+FP7FnbhwDO9llnruas= +cloud.google.com/go/optimization v1.6.3 h1:63NZaWyN+5rZEKHPX4ACpw3BjgyeuY8+rCehiCMaGPY= +cloud.google.com/go/optimization v1.6.3/go.mod h1:8ve3svp3W6NFcAEFr4SfJxrldzhUl4VMUJmhrqVKtYA= cloud.google.com/go/orchestration v1.8.4 h1:kgwZ2f6qMMYIVBtUGGoU8yjYWwMTHDanLwM/CQCFaoQ= +cloud.google.com/go/orchestration v1.8.5 h1:YHgWMlrPttIVGItgGfuvO2KM7x+y9ivN/Yk92pMm1a4= +cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8= cloud.google.com/go/orgpolicy v1.11.4 h1:RWuXQDr9GDYhjmrredQJC7aY7cbyqP9ZuLbq5GJGves= cloud.google.com/go/orgpolicy v1.12.0 h1:sab7cDiyfdthpAL0JkSpyw1C3mNqkXToVOhalm79PJQ= -cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.1 h1:2JbXigqBJVp8Dx5dONUttFqewu4fP0p3pgOdIZAhpYU= +cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I= cloud.google.com/go/osconfig v1.12.4 h1:OrRCIYEAbrbXdhm13/JINn9pQchvTTIzgmOCA7uJw8I= +cloud.google.com/go/osconfig v1.12.5 h1:Mo5jGAxOMKH/PmDY7fgY19yFcVbvwREb5D5zMPQjFfo= +cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8= cloud.google.com/go/oslogin v1.12.2 h1:NP/KgsD9+0r9hmHC5wKye0vJXVwdciv219DtYKYjgqE= cloud.google.com/go/oslogin v1.13.0 h1:gbA/G4p+youIR4O/Rk6DU181QlBlpwPS16kvJwqEz8o= -cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= +cloud.google.com/go/oslogin v1.13.1 h1:1K4nOT5VEZNt7XkhaTXupBYos5HjzvJMfhvyD2wWdFs= +cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws= cloud.google.com/go/phishingprotection v0.8.4 h1:sPLUQkHq6b4AL0czSJZ0jd6vL55GSTHz2B3Md+TCZI0= +cloud.google.com/go/phishingprotection v0.8.5 h1:DH3WFLzEoJdW/6xgsmoDqOwT1xddFi7gKu0QGZQhpGU= +cloud.google.com/go/phishingprotection v0.8.5/go.mod h1:g1smd68F7mF1hgQPuYn3z8HDbNre8L6Z0b7XMYFmX7I= cloud.google.com/go/policytroubleshooter v1.10.2 h1:sq+ScLP83d7GJy9+wpwYJVnY+q6xNTXwOdRIuYjvHT4= +cloud.google.com/go/policytroubleshooter v1.10.3 h1:c0WOzC6hz964QWNBkyKfna8A2jOIx1zzZa43Gx/P09o= +cloud.google.com/go/policytroubleshooter v1.10.3/go.mod h1:+ZqG3agHT7WPb4EBIRqUv4OyIwRTZvsVDHZ8GlZaoxk= cloud.google.com/go/privatecatalog v0.9.4 h1:Vo10IpWKbNvc/z/QZPVXgCiwfjpWoZ/wbgful4Uh/4E= +cloud.google.com/go/privatecatalog v0.9.5 h1:UZ0assTnATXSggoxUIh61RjTQ4P9zCMk/kEMbn0nMYA= +cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk= cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= cloud.google.com/go/pubsub v1.34.0 h1:ZtPbfwfi5rLaPeSvDC29fFoE20/tQvGrUS6kVJZJvkU= -cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= +cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y= +cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE= cloud.google.com/go/pubsublite v1.8.1 h1:pX+idpWMIH30/K7c0epN6V703xpIcMXWRjKJsz0tYGY= cloud.google.com/go/recaptchaenterprise v1.3.1 h1:u6EznTGzIdsyOsvm+Xkw0aSuKFXQlyjGE9a4exk6iNQ= cloud.google.com/go/recaptchaenterprise/v2 v2.9.0 h1:Zrd4LvT9PaW91X/Z13H0i5RKEv9suCLuk8zp+bfOpN4= -cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.2 h1:U3Wfq12X9cVMuTpsWDSURnXF0Z9hSPTHj+xsnXDRLsw= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU= cloud.google.com/go/recommendationengine v0.8.4 h1:JRiwe4hvu3auuh2hujiTc2qNgPPfVp+Q8KOpsXlEzKQ= +cloud.google.com/go/recommendationengine v0.8.5 h1:ineqLswaCSBY0csYv5/wuXJMBlxATK6Xc5jJkpiTEdM= +cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ= cloud.google.com/go/recommender v1.11.3 h1:VndmgyS/J3+izR8V8BHa7HV/uun8//ivQ3k5eVKKyyM= cloud.google.com/go/recommender v1.12.0 h1:tC+ljmCCbuZ/ybt43odTFlay91n/HLIhflvaOeb0Dh4= -cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.1 h1:LVLYS3r3u0MSCxQSDUtLSkporEGi9OAE6hGvayrZNPs= +cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0= cloud.google.com/go/redis v1.14.1 h1:J9cEHxG9YLmA9o4jTSvWt/RuVEn6MTrPlYSCRHujxDQ= +cloud.google.com/go/redis v1.14.2 h1:QF0maEdVv0Fj/2roU8sX3NpiDBzP9ICYTO+5F32gQNo= +cloud.google.com/go/redis v1.14.2/go.mod h1:g0Lu7RRRz46ENdFKQ2EcQZBAJ2PtJHJLuiiRuEXwyQw= cloud.google.com/go/resourcemanager v1.9.4 h1:JwZ7Ggle54XQ/FVYSBrMLOQIKoIT/uer8mmNvNLK51k= +cloud.google.com/go/resourcemanager v1.9.5 h1:AZWr1vWVDKGwfLsVhcN+vcwOz3xqqYxtmMa0aABCMms= +cloud.google.com/go/resourcemanager v1.9.5/go.mod h1:hep6KjelHA+ToEjOfO3garMKi/CLYwTqeAw7YiEI9x8= cloud.google.com/go/resourcesettings v1.6.4 h1:yTIL2CsZswmMfFyx2Ic77oLVzfBFoWBYgpkgiSPnC4Y= +cloud.google.com/go/resourcesettings v1.6.5 h1:BTr5MVykJwClASci/7Og4Qfx70aQ4n3epsNLj94ZYgw= +cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I= cloud.google.com/go/retail v1.14.4 h1:geqdX1FNqqL2p0ADXjPpw8lq986iv5GrVcieTYafuJQ= +cloud.google.com/go/retail v1.16.0 h1:Fn1GuAua1c6crCGqfJ1qMxG1Xh10Tg/x5EUODEHMqkw= +cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE= cloud.google.com/go/run v1.3.3 h1:qdfZteAm+vgzN1iXzILo3nJFQbzziudkJrvd9wCf3FQ= +cloud.google.com/go/run v1.3.4 h1:m9WDA7DzTpczhZggwYlZcBWgCRb+kgSIisWn1sbw2rQ= +cloud.google.com/go/run v1.3.4/go.mod h1:FGieuZvQ3tj1e9GnzXqrMABSuir38AJg5xhiYq+SF3o= cloud.google.com/go/scheduler v1.10.5 h1:eMEettHlFhG5pXsoHouIM5nRT+k+zU4+GUvRtnxhuVI= +cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE= +cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= +cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY= +cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4= cloud.google.com/go/security v1.15.4 h1:sdnh4Islb1ljaNhpIXlIPgb3eYj70QWgPVDKOUYvzJc= +cloud.google.com/go/security v1.15.5 h1:wTKJQ10j8EYgvE8Y+KhovxDRVDk2iv/OsxZ6GrLP3kE= +cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc= cloud.google.com/go/securitycenter v1.24.3 h1:crdn2Z2rFIy8WffmmhdlX3CwZJusqCiShtnrGFRwpeE= -cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.4 h1:/5jjkZ+uGe8hZ7pvd7pO30VW/a+pT2MrrdgOqjyucKQ= +cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU= cloud.google.com/go/servicecontrol v1.11.1 h1:d0uV7Qegtfaa7Z2ClDzr9HJmnbJW7jn0WhZ7wOX6hLE= cloud.google.com/go/servicedirectory v1.11.3 h1:5niCMfkw+jifmFtbBrtRedbXkJm3fubSR/KHbxSJZVM= +cloud.google.com/go/servicedirectory v1.11.4 h1:da7HFI1229kyzIyuVEzHXip0cw0d+E0s8mjQby0WN+k= +cloud.google.com/go/servicedirectory v1.11.4/go.mod h1:Bz2T9t+/Ehg6x+Y7Ycq5xiShYLD96NfEsWNHyitj1qM= cloud.google.com/go/servicemanagement v1.8.0 h1:fopAQI/IAzlxnVeiKn/8WiV6zKndjFkvi+gzu+NjywY= cloud.google.com/go/serviceusage v1.6.0 h1:rXyq+0+RSIm3HFypctp7WoXxIA563rn206CfMWdqXX4= cloud.google.com/go/shell v1.7.4 h1:nurhlJcSVFZneoRZgkBEHumTYf/kFJptCK2eBUq/88M= +cloud.google.com/go/shell v1.7.5 h1:3Fq2hzO0ZSyaqBboJrFkwwf/qMufDtqwwA6ep8EZxEI= +cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE= cloud.google.com/go/spanner v1.53.1 h1:xNmE0SXMSxNBuk7lRZ5G/S+A49X91zkSTt7Jn5Ptlvw= cloud.google.com/go/spanner v1.55.0 h1:YF/A/k73EMYCjp8wcJTpkE+TcrWutHRlsCtlRSfWS64= -cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= +cloud.google.com/go/spanner v1.57.0 h1:fJq+ZfQUDHE+cy1li0bJA8+sy2oiSGhuGqN5nqVaZdU= +cloud.google.com/go/spanner v1.57.0/go.mod h1:aXQ5QDdhPRIqVhYmnkAdwPYvj/DRN0FguclhEWw+jOo= cloud.google.com/go/speech v1.21.0 h1:qkxNao58oF8ghAHE1Eghen7XepawYEN5zuZXYWaUTA4= +cloud.google.com/go/speech v1.21.1 h1:nuFc+Kj5B8de75nN4FdPyUbI2SiBoHZG6BLurXL56Q0= +cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA= cloud.google.com/go/storagetransfer v1.10.3 h1:YM1dnj5gLjfL6aDldO2s4GeU8JoAvH1xyIwXre63KmI= +cloud.google.com/go/storagetransfer v1.10.4 h1:dy4fL3wO0VABvzM05ycMUPFHxTPbJz9Em8ikAJVqSbI= +cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs= cloud.google.com/go/talent v1.6.5 h1:LnRJhhYkODDBoTwf6BeYkiJHFw9k+1mAFNyArwZUZAs= +cloud.google.com/go/talent v1.6.6 h1:JssV0CE3FNujuSWn7SkosOzg7qrMxVnt6txOfGcMSa4= +cloud.google.com/go/talent v1.6.6/go.mod h1:y/WQDKrhVz12WagoarpAIyKKMeKGKHWPoReZ0g8tseQ= cloud.google.com/go/texttospeech v1.7.4 h1:ahrzTgr7uAbvebuhkBAAVU6kRwVD0HWsmDsvMhtad5Q= +cloud.google.com/go/texttospeech v1.7.5 h1:dxY2Q5mHCbrGa3oPR2O3PCicdnvKa1JmwGQK36EFLOw= +cloud.google.com/go/texttospeech v1.7.5/go.mod h1:tzpCuNWPwrNJnEa4Pu5taALuZL4QRRLcb+K9pbhXT6M= cloud.google.com/go/tpu v1.6.4 h1:XIEH5c0WeYGaVy9H+UueiTaf3NI6XNdB4/v6TFQJxtE= +cloud.google.com/go/tpu v1.6.5 h1:C8YyYda8WtNdBoCgFwwBzZd+S6+EScHOxM/z1h0NNp8= +cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs= cloud.google.com/go/trace v1.10.4 h1:2qOAuAzNezwW3QN+t41BtkDJOG42HywL73q8x/f6fnM= +cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= +cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= cloud.google.com/go/translate v1.9.3 h1:t5WXTqlrk8VVJu/i3WrYQACjzYJiff5szARHiyqqPzI= cloud.google.com/go/translate v1.10.0 h1:tncNaKmlZnayMMRX/mMM2d5AJftecznnxVBD4w070NI= -cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.1 h1:upovZ0wRMdzZvXnu+RPam41B0mRJ+coRXFP2cYFJ7ew= +cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk= cloud.google.com/go/video v1.20.3 h1:Xrpbm2S9UFQ1pZEeJt9Vqm5t2T/z9y/M3rNXhFoo8Is= +cloud.google.com/go/video v1.20.4 h1:TXwotxkShP1OqgKsbd+b8N5hrIHavSyLGvYnLGCZ7xc= +cloud.google.com/go/video v1.20.4/go.mod h1:LyUVjyW+Bwj7dh3UJnUGZfyqjEto9DnrvTe1f/+QrW0= cloud.google.com/go/videointelligence v1.11.4 h1:YS4j7lY0zxYyneTFXjBJUj2r4CFe/UoIi/PJG0Zt/Rg= +cloud.google.com/go/videointelligence v1.11.5 h1:mYaWH8uhUCXLJCN3gdXswKzRa2+lK0zN6/KsIubm6pE= +cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I= cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= cloud.google.com/go/vision/v2 v2.7.5 h1:T/ujUghvEaTb+YnFY/jiYwVAkMbIC8EieK0CJo6B4vg= +cloud.google.com/go/vision/v2 v2.8.0 h1:W52z1b6LdGI66MVhE70g/NFty9zCYYcjdKuycqmlhtg= +cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU= cloud.google.com/go/vmmigration v1.7.4 h1:qPNdab4aGgtaRX+51jCOtJxlJp6P26qua4o1xxUDjpc= +cloud.google.com/go/vmmigration v1.7.5 h1:5v9RT2vWyuw3pK2ox0HQpkoftO7Q7/8591dTxxQc79g= +cloud.google.com/go/vmmigration v1.7.5/go.mod h1:pkvO6huVnVWzkFioxSghZxIGcsstDvYiVCxQ9ZH3eYI= cloud.google.com/go/vmwareengine v1.0.3 h1:WY526PqM6QNmFHSqe2sRfK6gRpzWjmL98UFkql2+JDM= +cloud.google.com/go/vmwareengine v1.1.1 h1:EGdDi9QbqThfZq3ILcDK5g+m9jTevc34AY5tACx5v7k= +cloud.google.com/go/vmwareengine v1.1.1/go.mod h1:nMpdsIVkUrSaX8UvmnBhzVzG7PPvNYc5BszcvIVudYs= cloud.google.com/go/vpcaccess v1.7.4 h1:zbs3V+9ux45KYq8lxxn/wgXole6SlBHHKKyZhNJoS+8= +cloud.google.com/go/vpcaccess v1.7.5 h1:XyL6hTLtEM/eE4F1GEge8xUN9ZCkiVWn44K/YA7z1rQ= +cloud.google.com/go/vpcaccess v1.7.5/go.mod h1:slc5ZRvvjP78c2dnL7m4l4R9GwL3wDLcpIWz6P/ziig= cloud.google.com/go/webrisk v1.9.4 h1:iceR3k0BCRZgf2D/NiKviVMFfuNC9LmeNLtxUFRB/wI= +cloud.google.com/go/webrisk v1.9.5 h1:251MvGuC8wisNN7+jqu9DDDZAi38KiMXxOpA/EWy4dE= +cloud.google.com/go/webrisk v1.9.5/go.mod h1:aako0Fzep1Q714cPEM5E+mtYX8/jsfegAuS8aivxy3U= cloud.google.com/go/websecurityscanner v1.6.4 h1:5Gp7h5j7jywxLUp6NTpjNPkgZb3ngl0tUSw6ICWvtJQ= +cloud.google.com/go/websecurityscanner v1.6.5 h1:YqWZrZYabG88TZt7364XWRJGhxmxhony2ZUyZEYMF2k= +cloud.google.com/go/websecurityscanner v1.6.5/go.mod h1:QR+DWaxAz2pWooylsBF854/Ijvuoa3FCyS1zBa1rAVQ= cloud.google.com/go/workflows v1.12.3 h1:qocsqETmLAl34mSa01hKZjcqAvt699gaoFbooGGMvaM= +cloud.google.com/go/workflows v1.12.4 h1:uHNmUiatTbPQ4H1pabwfzpfEYD4BBnqDHqMm2IesOh4= +cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9 h1:yxE46rQA0QaqPGqN2UnwXvgCrRqtjR1CsGSWVTRjvv4= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= @@ -218,11 +408,13 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 h1:iXUgAaqDcIUGbRoy2TdeofRG/j1zpGRSEmNK05T+bi8= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4= github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4= github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= @@ -231,10 +423,10 @@ github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6 github.com/apache/arrow/go/v11 v11.0.0 h1:hqauxvFQxww+0mEU/2XHG6LT7eZternCZq+A5Yly2uM= github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg= -github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/arrow/go/v13 v13.0.0 h1:kELrvDQuKZo8csdWYqBQfyi431x6Zs/YJTEgUuSVcWk= github.com/apache/arrow/go/v13 v13.0.0/go.mod h1:W69eByFNO0ZR30q1/7Sr9d83zcVZmF2MiP3fFYAWJOc= -github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= +github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= +github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= 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= @@ -325,6 +517,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1 github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= +github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= @@ -406,6 +599,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= +github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9ChK0gH164= +github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw/SaEclDUloYx0+FXDKJWKhNbE4= github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY= github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 h1:/of8Z8taCPftShATouOrBVy6GaTTjgQd/VfNiZp/VXQ= @@ -414,8 +609,6 @@ github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJ github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240321101410-40158de684b2 h1:wqqaqZw+J8sLXzOjXMMde219jc6dfJPI8599UpaILZw= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240321101410-40158de684b2/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= @@ -431,6 +624,7 @@ github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgj github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91 h1:KyZDvZ/GGn+r+Y3DKZ7UOQ/TP4xV6HNkrwiVMB1GnNY= @@ -687,6 +881,7 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= @@ -730,6 +925,8 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRK github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= +go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= +go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.opentelemetry.io/collector v0.74.0 h1:0s2DKWczGj/pLTsXGb1P+Je7dyuGx9Is4/Dri1+cS7g= go.opentelemetry.io/collector v0.74.0/go.mod h1:7NjZAvkhQ6E+NLN4EAH2hw3Nssi+F14t7mV7lMNXCto= go.opentelemetry.io/collector/component v0.74.0 h1:W32ILPgbA5LO+m9Se61hbbtiLM6FYusNM36K5/CCOi0= @@ -750,12 +947,12 @@ go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0/go.mod h1:9X9/RYFxJI go.opentelemetry.io/collector/semconv v0.90.1 h1:2fkQZbefQBbIcNb9Rk1mRcWlFZgQOk7CpST1e1BK8eg= go.opentelemetry.io/contrib v0.18.0 h1:uqBh0brileIvG6luvBjdxzoFL8lxDGuhxJWsvK3BveI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c= go.opentelemetry.io/contrib/samplers/jaegerremote v0.16.0/go.mod h1:StxwPndBVNZD2sZez0RQ0SP/129XGCd4aEmVGaw1/QM= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/bridge/opencensus v0.37.0 h1:ieH3gw7b1eg90ARsFAlAsX5LKVZgnCYfaDwRrK6xLHU= go.opentelemetry.io/otel/bridge/opencensus v0.37.0/go.mod h1:ddiK+1PE68l/Xk04BGTh9Y6WIcxcLrmcVxVlS0w5WZ0= go.opentelemetry.io/otel/bridge/opentracing v1.10.0 h1:WzAVGovpC1s7KD5g4taU6BWYZP3QGSDVTlbRu9fIHw8= @@ -765,9 +962,11 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= go.opentelemetry.io/otel/exporters/prometheus v0.37.0/go.mod h1:hB8qWjsStK36t50/R0V2ULFb4u95X/Q6zupXLgvjTh8= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= @@ -777,7 +976,6 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= @@ -787,25 +985,22 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= -google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= @@ -835,7 +1030,6 @@ k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= diff --git a/package.json b/package.json index 148e5422ad9..5a182619e3a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "watch": "yarn start -d watch,start core:start --watchTheme", "ci:test-frontend": "yarn run test:ci", "i18n:clean": "rimraf public/locales/en-US/grafana.json", - "i18n:extract": "yarn run i18next -c public/locales/i18next-parser.config.js 'public/**/*.{tsx,ts}' 'packages/grafana-ui/**/*.{tsx,ts}' && yarn i18n:pseudo", + "i18n:extract": "yarn run i18next -c public/locales/i18next-parser.config.cjs 'public/**/*.{tsx,ts}' 'packages/grafana-ui/**/*.{tsx,ts}' && yarn i18n:pseudo", "i18n:pseudo": "node ./public/locales/pseudo.js", "i18n:stats": "node ./scripts/cli/reportI18nStats.mjs", "betterer": "betterer", @@ -89,10 +89,10 @@ "@rtk-query/codegen-openapi": "^1.2.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", "@swc/core": "1.4.2", - "@swc/helpers": "0.5.9", + "@swc/helpers": "0.5.10", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/angular": "1.8.9", "@types/angular-route": "1.7.6", @@ -118,7 +118,6 @@ "@types/lodash": "4.17.0", "@types/logfmt": "^1.2.3", "@types/lucene": "^2", - "@types/marked": "5.0.2", "@types/mousetrap": "1.6.15", "@types/node": "20.12.7", "@types/node-forge": "^1", @@ -126,7 +125,7 @@ "@types/papaparse": "5.3.14", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.3", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-beautiful-dnd": "13.1.8", "@types/react-dom": "18.2.25", "@types/react-grid-layout": "1.3.5", @@ -174,6 +173,7 @@ "eslint-plugin-jsdoc": "48.2.3", "eslint-plugin-jsx-a11y": "6.8.0", "eslint-plugin-lodash": "7.4.0", + "eslint-plugin-no-barrel-files": "^1.1.0", "eslint-plugin-react": "7.34.1", "eslint-plugin-react-hooks": "4.6.0", "eslint-scope": "^8.0.0", @@ -255,7 +255,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^4.6.0", + "@grafana/scenes": "^4.10.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", @@ -344,7 +344,7 @@ "ml-regression-simple-linear": "^3.0.0", "moment": "2.30.1", "moment-timezone": "0.5.45", - "monaco-editor": "0.34.0", + "monaco-editor": "0.34.1", "monaco-promql": "1.7.4", "mousetrap": "1.6.5", "mousetrap-global-bind": "1.1.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index a57b99e299d..b720fb5e97b 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -66,10 +66,9 @@ "@types/dompurify": "^3.0.0", "@types/history": "4.7.11", "@types/lodash": "4.17.0", - "@types/marked": "5.0.2", "@types/node": "20.12.7", "@types/papaparse": "5.3.14", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-dom": "18.2.25", "@types/tinycolor2": "1.4.6", "esbuild": "0.18.12", diff --git a/packages/grafana-data/src/themes/zIndex.ts b/packages/grafana-data/src/themes/zIndex.ts index 1423bbaf664..093c0ef93d4 100644 --- a/packages/grafana-data/src/themes/zIndex.ts +++ b/packages/grafana-data/src/themes/zIndex.ts @@ -9,6 +9,7 @@ export const zIndex = { tooltip: 1040, modalBackdrop: 1050, modal: 1060, + tooltipWithinModal: 1060, portal: 1061, }; diff --git a/packages/grafana-data/src/transformations/fieldReducer.test.ts b/packages/grafana-data/src/transformations/fieldReducer.test.ts index 7ccda4c1573..f155a62e511 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.test.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.test.ts @@ -252,17 +252,19 @@ describe('Stats Calculators', () => { expect(reduce(someNulls, ReducerID.count)).toEqual(4); }); - for (let i = 1; i < 100; i++) { - it(`can reduce the ${i}th percentile`, () => { + it('can reduce to percentiles', () => { + // This `Array.from` will build an array of elements from 1 to 99 + const percentiles = [...Array.from({ length: 99 }, (_, i) => i + 1)]; + percentiles.forEach((percentile) => { const preciseStats = reduceField({ field: createField( 'x', Array.from({ length: 101 }, (_, index) => index) ), - reducers: [(ReducerID as Record)[`p${i}`]], + reducers: [(ReducerID as Record)[`p${percentile}`]], }); - expect(preciseStats[`p${i}`]).toEqual(i); + expect(preciseStats[`p${percentile}`]).toEqual(percentile); }); - } + }); }); diff --git a/packages/grafana-data/src/transformations/fieldReducer.ts b/packages/grafana-data/src/transformations/fieldReducer.ts index a08c7db3c50..b3d547184cb 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.ts @@ -402,27 +402,34 @@ export const fieldReducers = new Registry(() => [ }), preservesUnits: false, }, + ...buildPercentileReducers(), ]); -for (let i = 1; i < 100; i++) { - const percentile = i / 100; - const id = `p${i}` as ReducerID; +// This `Array.from` will build an array of elements from 1 to 99 +const buildPercentileReducers = (percentiles = [...Array.from({ length: 99 }, (_, i) => i + 1)]) => { + const percentileReducers: FieldReducerInfo[] = []; const nth = (n: number) => n > 3 && n < 21 ? 'th' : n % 10 === 1 ? 'st' : n % 10 === 2 ? 'nd' : n % 10 === 3 ? 'rd' : 'th'; - const name = `${i}${nth(i)} percentile`; - const description = `${i}${nth(i)} percentile value`; - fieldReducers.register({ - id: id, - name: name, - description: description, - standard: false, - reduce: (field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs => { - return { [id]: calculatePercentile(field, percentile, ignoreNulls, nullAsZero) }; - }, - preservesUnits: true, + percentiles.forEach((p) => { + const percentile = p / 100; + const id = `p${p}` as ReducerID; + const name = `${p}${nth(p)} %`; + const description = `${p}${nth(p)} percentile value`; + + percentileReducers.push({ + id: id, + name: name, + description: description, + standard: false, + reduce: (field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs => { + return { [id]: calculatePercentile(field, percentile, ignoreNulls, nullAsZero) }; + }, + preservesUnits: true, + }); }); -} + return percentileReducers; +}; // Used for test cases export const defaultCalcs: FieldCalcs = { diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts index b61174d2e13..5d2ba4b10f9 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts @@ -26,6 +26,25 @@ const seriesAWithSingleField = toDataFrame({ ], }); +const multiSeriesWithSingleField = [ + toDataFrame({ + name: 'A', + length: 3, + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, + { name: 'value', type: FieldType.number, values: [1, 0, 1] }, + ], + }), + toDataFrame({ + name: 'B', + length: 3, + fields: [ + { name: 'time', type: FieldType.time, values: [5000, 6000, 7000] }, + { name: 'value', type: FieldType.number, values: [0, 1, 1] }, + ], + }), +]; + describe('FilterByValue transformer', () => { beforeAll(() => { mockTransformationsRegistry([filterByValueTransformer]); @@ -72,6 +91,68 @@ describe('FilterByValue transformer', () => { }); }); + it('should not cross frame boundaries', async () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.filterByValue, + options: { + type: FilterByValueType.exclude, + match: FilterByValueMatch.any, + filters: [ + { + fieldName: 'A value', + config: { + id: ValueMatcherID.equal, + options: { value: 0 }, + }, + }, + { + fieldName: 'B value', + config: { + id: ValueMatcherID.equal, + options: { value: 0 }, + }, + }, + ], + }, + }; + + await expect(transformDataFrame([cfg], multiSeriesWithSingleField)).toEmitValuesWith((received) => { + const processed = received[0]; + + expect(processed.length).toEqual(2); + + expect(processed[0].fields).toEqual([ + { + name: 'time', + type: FieldType.time, + values: [1000, 3000], + state: {}, + }, + { + name: 'value', + type: FieldType.number, + values: [1, 1], + state: {}, + }, + ]); + + expect(processed[1].fields).toEqual([ + { + name: 'time', + type: FieldType.time, + values: [6000, 7000], + state: {}, + }, + { + name: 'value', + type: FieldType.number, + values: [1, 1], + state: {}, + }, + ]); + }); + }); + it('should include values', async () => { const lowerOrEqual: MatcherConfig> = { id: ValueMatcherID.lowerOrEqual, diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.ts index 766c98fcc2d..39e80a2d824 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.ts @@ -92,14 +92,16 @@ export const filterByValueTransformer: DataTransformerInfo { - if (!Array.isArray(data) || data.length === 0) { + if (data.length === 0) { return data; } - const rows = new Set(); + const processed: DataFrame[] = []; + + const fieldIndexByName = groupFieldIndexByName(data); for (const frame of data) { - const fieldIndexByName = groupFieldIndexByName(frame, data); + const rows = new Set(); let matchers; if (transformationsVariableSupport()) { @@ -135,13 +137,9 @@ export const filterByValueTransformer: DataTransformerInfo => { - return frame.fields.reduce((all: Record, field, fieldIndex) => { - const fieldName = getFieldDisplayName(field, frame, data); - all[fieldName] = fieldIndex; - return all; - }, {}); +const groupFieldIndexByName = (data: DataFrame[]) => { + const lookup: Record = {}; + + for (const frame of data) { + frame.fields.forEach((field, fieldIndex) => { + const fieldName = getFieldDisplayName(field, frame, data); + lookup[fieldName] = fieldIndex; + }); + } + + return lookup; }; diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index f131a71c49e..1ed466bec02 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -587,7 +587,7 @@ export interface QueryFixAction { */ options?: KeyValue; /** - * An optional single row data frame containing the row that triggered the the QueryFixAction. + * An optional single row data frame containing the row that triggered the QueryFixAction. */ frame?: DataFrame; } diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index a751590217f..a670448deb8 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -174,9 +174,9 @@ export interface FeatureToggles { authAPIAccessTokenAuth?: boolean; scopeFilters?: boolean; ssoSettingsSAML?: boolean; - usePrometheusFrontendPackage?: boolean; oauthRequireSubClaim?: boolean; newDashboardWithFiltersAndGroupBy?: boolean; cloudWatchNewLabelParsing?: boolean; accessActionSets?: boolean; + disableNumericMetricsSortingInExpressions?: boolean; } diff --git a/packages/grafana-data/src/types/logs.ts b/packages/grafana-data/src/types/logs.ts index d9edfca6bc2..0124ffe98ac 100644 --- a/packages/grafana-data/src/types/logs.ts +++ b/packages/grafana-data/src/types/logs.ts @@ -55,7 +55,7 @@ export interface LogRowModel { // the same as rows final index when rendered. rowIndex: number; - // The value of the the dataframe's id field, if it exists + // The value of the dataframe's id field, if it exists rowId?: string; // Full DataFrame from which we parsed this log. diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index 5c934d3b299..c006b72fe45 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -43,6 +43,8 @@ export enum PluginErrorCode { missingSignature = 'signatureMissing', invalidSignature = 'signatureInvalid', modifiedSignature = 'signatureModified', + failedBackendStart = 'failedBackendStart', + angular = 'angular', } /** Describes error returned from Grafana plugins API call */ diff --git a/packages/grafana-data/src/types/scopes.ts b/packages/grafana-data/src/types/scopes.ts index 09dd972f98c..2f9b785ca27 100644 --- a/packages/grafana-data/src/types/scopes.ts +++ b/packages/grafana-data/src/types/scopes.ts @@ -1,20 +1,35 @@ -export interface ScopeDashboard { - uid: string; - title: string; - url: string; +export interface ScopeDashboardBindingSpec { + dashboard: string; + scope: string; } -export interface ScopeFilter { +export type ScopeFilterOperator = 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match'; + +export const scopeFilterOperatorMap: Record = { + '=': 'equals', + '!=': 'not-equals', + '=~': 'regex-match', + '!~': 'regex-not-match', +}; + +export interface ScopeSpecFilter { key: string; value: string; - operator: string; + operator: ScopeFilterOperator; } -export interface Scope { - uid: string; +export interface ScopeSpec { title: string; type: string; description: string; category: string; - filters: ScopeFilter[]; + filters: ScopeSpecFilter[]; +} + +// TODO: Use Resource from apiserver when we export the types +export interface Scope { + metadata: { + name: string; + }; + spec: ScopeSpec; } diff --git a/packages/grafana-data/src/types/select.ts b/packages/grafana-data/src/types/select.ts index 56ea31a3e2c..0e46c04392b 100644 --- a/packages/grafana-data/src/types/select.ts +++ b/packages/grafana-data/src/types/select.ts @@ -7,7 +7,7 @@ export interface SelectableValue { value?: T; imgUrl?: string; icon?: string; - // Secondary text under the the title of the option. + // Secondary text under the title of the option. description?: string; // Adds a simple native title attribute to each option. title?: string; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index f67c6874dcf..ccfef3f8388 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -155,6 +155,7 @@ export const Components = { Panels: { Panel: { title: (title: string) => `data-testid Panel header ${title}`, + content: 'data-testid panel content', headerItems: (item: string) => `data-testid Panel header item ${item}`, menuItems: (item: string) => `data-testid Panel menu item ${item}`, menu: (title: string) => `data-testid Panel menu ${title}`, diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 8d6860288e3..93ba6b7cdd1 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -62,12 +62,12 @@ "@grafana/tsconfig": "^1.3.0-rc1", "@rollup/plugin-node-resolve": "15.2.3", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.0", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-virtualized-auto-sizer": "1.0.4", "@types/tinycolor2": "1.4.6", "babel-jest": "29.7.0", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index 8159cf77d98..b63c9866b9b 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -43,7 +43,7 @@ "@svgr/plugin-prettier": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "@types/node": "20.12.7", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-dom": "18.2.25", "esbuild": "0.18.12", "prettier": "3.2.5", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index cc8d2a5ed92..82bf0ac3aec 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -31,10 +31,10 @@ "devDependencies": { "@grafana/tsconfig": "^1.3.0-rc1", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.4", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/systemjs": "6.13.5", "@types/testing-library__jest-dom": "5.14.9", "jest": "^29.6.4", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index d018aa48700..004fcd9764d 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -81,10 +81,10 @@ "@rollup/plugin-image": "3.0.3", "@rollup/plugin-node-resolve": "15.2.3", "@swc/core": "1.4.2", - "@swc/helpers": "0.5.9", + "@swc/helpers": "0.5.10", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/d3": "7.4.3", "@types/debounce-promise": "3.1.9", @@ -92,11 +92,10 @@ "@types/jest": "29.5.12", "@types/jquery": "3.5.29", "@types/lodash": "4.17.0", - "@types/marked": "5.0.2", "@types/node": "20.12.7", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.3", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-beautiful-dnd": "13.1.8", "@types/react-dom": "18.2.25", "@types/react-highlight-words": "0.16.7", diff --git a/packages/grafana-prometheus/src/add_label_to_query.test.ts b/packages/grafana-prometheus/src/add_label_to_query.test.ts index 08c32e3c398..0936da9f726 100644 --- a/packages/grafana-prometheus/src/add_label_to_query.test.ts +++ b/packages/grafana-prometheus/src/add_label_to_query.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/add_label_to_query.test.ts import { addLabelToQuery } from './add_label_to_query'; describe('addLabelToQuery()', () => { diff --git a/packages/grafana-prometheus/src/add_label_to_query.ts b/packages/grafana-prometheus/src/add_label_to_query.ts index 696e0cbb8dc..690a5c3aaff 100644 --- a/packages/grafana-prometheus/src/add_label_to_query.ts +++ b/packages/grafana-prometheus/src/add_label_to_query.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/add_label_to_query.ts import { parser, VectorSelector } from '@prometheus-io/lezer-promql'; import { PromQueryModeller } from './querybuilder/PromQueryModeller'; diff --git a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx index 0cbaf5978cd..2c22905e99e 100644 --- a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx +++ b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx import React from 'react'; import { AnnotationQuery } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/components/PromCheatSheet.tsx b/packages/grafana-prometheus/src/components/PromCheatSheet.tsx index 347d6547301..7eaee0b0782 100644 --- a/packages/grafana-prometheus/src/components/PromCheatSheet.tsx +++ b/packages/grafana-prometheus/src/components/PromCheatSheet.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx import React from 'react'; import { QueryEditorHelpProps } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/components/PromExemplarField.tsx b/packages/grafana-prometheus/src/components/PromExemplarField.tsx index e09a6260c98..8b300687dad 100644 --- a/packages/grafana-prometheus/src/components/PromExemplarField.tsx +++ b/packages/grafana-prometheus/src/components/PromExemplarField.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx import { css, cx } from '@emotion/css'; import React, { useEffect, useState } from 'react'; import { usePrevious } from 'react-use'; diff --git a/packages/grafana-prometheus/src/components/PromExploreExtraField.test.tsx b/packages/grafana-prometheus/src/components/PromExploreExtraField.test.tsx index 4c22e2f16f2..75cc778f7f0 100644 --- a/packages/grafana-prometheus/src/components/PromExploreExtraField.test.tsx +++ b/packages/grafana-prometheus/src/components/PromExploreExtraField.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx import { render, screen } from '@testing-library/react'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx index c9ad0a79e36..0d3fa86a1b4 100644 --- a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx +++ b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx import { css, cx } from '@emotion/css'; import { isEqual } from 'lodash'; import React, { memo, useCallback } from 'react'; diff --git a/packages/grafana-prometheus/src/components/PromQueryEditorByApp.test.tsx b/packages/grafana-prometheus/src/components/PromQueryEditorByApp.test.tsx index 832cc37355f..6909409f810 100644 --- a/packages/grafana-prometheus/src/components/PromQueryEditorByApp.test.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryEditorByApp.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx import { render, screen } from '@testing-library/react'; import { noop } from 'lodash'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/components/PromQueryEditorByApp.tsx b/packages/grafana-prometheus/src/components/PromQueryEditorByApp.tsx index 843bb30284a..82b095322bf 100644 --- a/packages/grafana-prometheus/src/components/PromQueryEditorByApp.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryEditorByApp.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.tsx import React, { memo } from 'react'; import { CoreApp } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/components/PromQueryEditorForAlerting.tsx b/packages/grafana-prometheus/src/components/PromQueryEditorForAlerting.tsx index 5adac3afeb5..5f5f2da7bfc 100644 --- a/packages/grafana-prometheus/src/components/PromQueryEditorForAlerting.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryEditorForAlerting.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromQueryEditorForAlerting.tsx import React from 'react'; import { PromQueryField } from './PromQueryField'; diff --git a/packages/grafana-prometheus/src/components/PromQueryField.test.tsx b/packages/grafana-prometheus/src/components/PromQueryField.test.tsx index 700f73574d5..46c56efc381 100644 --- a/packages/grafana-prometheus/src/components/PromQueryField.test.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryField.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx import { getByTestId, render, screen, waitFor } from '@testing-library/react'; // @ts-ignore import userEvent from '@testing-library/user-event'; diff --git a/packages/grafana-prometheus/src/components/PromQueryField.tsx b/packages/grafana-prometheus/src/components/PromQueryField.tsx index 3ceab631609..623092b648c 100644 --- a/packages/grafana-prometheus/src/components/PromQueryField.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryField.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx import { cx } from '@emotion/css'; import React, { ReactNode } from 'react'; diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx index e3ba496356b..393a15b3d00 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx index 79ae235574e..e58a57c9323 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx import { css, cx } from '@emotion/css'; import React, { ChangeEvent } from 'react'; import { FixedSizeList } from 'react-window'; diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx index 0d3a877069d..70fc36a5173 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -195,7 +196,7 @@ describe('PromVariableQueryEditor', () => { }); }); - test('Calls onChange for label_names, label_values, metrics, query result and and classic query.', async () => { + test('Calls onChange for label_names, label_values, metrics, query result and classic query.', async () => { const onChange = jest.fn(); props.query = { diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx index fce9bf1e1df..cd81e5fc6c8 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx import React, { FormEvent, useCallback, useEffect, useState } from 'react'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; 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 2e5dbb1f52e..c0b3acbd2fc 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx import { css } from '@emotion/css'; import { parser } from '@prometheus-io/lezer-promql'; import { debounce } from 'lodash'; @@ -221,6 +222,13 @@ const MonacoQueryField = (props: Props) => { 'isEditorFocused' + id ); + // Fixes Monaco capturing the search key binding and displaying a useless search box within the Editor. + // See https://github.com/grafana/grafana/issues/85850 + monaco.editor.addKeybindingRule({ + keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyF, + command: null, + }); + /* Something in this configuration of monaco doesn't bubble up [mod]+K, which the command palette uses. Pass the event out of monaco manually */ diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldLazy.tsx b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldLazy.tsx index 797d48b6c2b..ea2fb3576e9 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldLazy.tsx +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldLazy.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryFieldLazy.tsx import React, { Suspense } from 'react'; import MonacoQueryField from './MonacoQueryField'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts index 1383b565828..a0d21761f2c 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryFieldProps.ts import { HistoryItem } from '@grafana/data'; import { PrometheusDatasource } from '../../datasource'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldWrapper.tsx b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldWrapper.tsx index 7f69ce54758..2fafe41a1cc 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldWrapper.tsx +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldWrapper.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryFieldWrapper.tsx import React, { useRef } from 'react'; import { MonacoQueryFieldLazy } from './MonacoQueryFieldLazy'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/getOverrideServices.ts b/packages/grafana-prometheus/src/components/monaco-query-field/getOverrideServices.ts index b3bc45a3f57..c7a3192ddb6 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/getOverrideServices.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/getOverrideServices.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/getOverrideServices.ts import { monacoTypes } from '@grafana/ui'; // this thing here is a workaround in a way. diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts index a092beac588..d93338c5ea5 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts @@ -1,3 +1,4 @@ +// 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/completions.ts import UFuzzy from '@leeoniya/ufuzzy'; import { config } from '@grafana/runtime'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts index 6c1037df321..abc97de27ce 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts @@ -1,3 +1,4 @@ +// 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/index.ts import type { Monaco, monacoTypes } from '@grafana/ui'; import { CompletionType, getCompletions } from './completions'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts index 8c7be6b592b..605d9658174 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts @@ -1,3 +1,4 @@ +// 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/situation.test.ts import { getSituation, Situation } from './situation'; // we use the `^` character as the cursor-marker in the string. diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts index e6f569fe806..f633922d298 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts @@ -1,3 +1,4 @@ +// 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/situation.ts import type { SyntaxNode, Tree } from '@lezer/common'; import { AggregateExpr, diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/util.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/util.ts index 35babca0d09..84ee27c8e94 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/util.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/util.ts @@ -1,3 +1,5 @@ +// 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/util.ts + // this helper class is used to make typescript warn you when you forget // a case-block in a switch statement. // example code that triggers the typescript-error: 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 c51c764e8fa..e467257e23d 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,3 +1,4 @@ +// 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'; 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 8b9cb8e6766..34ce38c060b 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 @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/validation.ts import { SyntaxNode } from '@lezer/common'; import { LRParser } from '@lezer/lr'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/promql.ts b/packages/grafana-prometheus/src/components/monaco-query-field/promql.ts index af7ab116c95..c7697fbfb3c 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/promql.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/promql.ts @@ -84,6 +84,12 @@ const functions = [ 'exp', 'floor', 'histogram_quantile', + 'histogram_avg', + 'histogram_count', + 'histogram_sum', + 'histogram_fraction', + 'histogram_stddev', + 'histogram_stdvar', 'holt_winters', 'hour', 'idelta', diff --git a/packages/grafana-prometheus/src/components/types.ts b/packages/grafana-prometheus/src/components/types.ts index 419b86ea5bd..4f453b0a947 100644 --- a/packages/grafana-prometheus/src/components/types.ts +++ b/packages/grafana-prometheus/src/components/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/types.ts import { QueryEditorProps } from '@grafana/data'; import { PrometheusDatasource } from '../datasource'; diff --git a/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx b/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx index 11bbe3d2361..4cd5a13b85a 100644 --- a/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx +++ b/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx import { cx } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/configuration/ConfigEditor.test.tsx b/packages/grafana-prometheus/src/configuration/ConfigEditor.test.tsx index 9ce9e66c563..12878c69435 100644 --- a/packages/grafana-prometheus/src/configuration/ConfigEditor.test.tsx +++ b/packages/grafana-prometheus/src/configuration/ConfigEditor.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.test.tsx import React from 'react'; import { FieldValidationMessage } from '@grafana/ui'; diff --git a/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx b/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx index 86a6f687253..28355af85be 100644 --- a/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx +++ b/packages/grafana-prometheus/src/configuration/ConfigEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/configuration/DataSourceHttpSettingsOverhaul.tsx b/packages/grafana-prometheus/src/configuration/DataSourceHttpSettingsOverhaul.tsx index 86af4461740..035e513c6a3 100644 --- a/packages/grafana-prometheus/src/configuration/DataSourceHttpSettingsOverhaul.tsx +++ b/packages/grafana-prometheus/src/configuration/DataSourceHttpSettingsOverhaul.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/DataSourceHttpSettingsOverhaul.tsx import React from 'react'; import { DataSourceSettings } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx b/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx index 75614934cf5..acb9c35cbe6 100644 --- a/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx +++ b/packages/grafana-prometheus/src/configuration/ExemplarSetting.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx import React, { useState } from 'react'; import { DataSourceInstanceSettings } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx b/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx index 6ffece606a0..24ad9a5d1ad 100644 --- a/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx +++ b/packages/grafana-prometheus/src/configuration/ExemplarsSettings.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/configuration/PromFlavorVersions.ts b/packages/grafana-prometheus/src/configuration/PromFlavorVersions.ts index bce2dd88a87..71381bd73c0 100644 --- a/packages/grafana-prometheus/src/configuration/PromFlavorVersions.ts +++ b/packages/grafana-prometheus/src/configuration/PromFlavorVersions.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/PromFlavorVersions.ts export const PromFlavorVersions: { [index: string]: Array<{ value?: string; label: string }> } = { Prometheus: [ { value: undefined, label: 'Please select' }, diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx index af9b99bd4da..2deae5f94dd 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import React, { SyntheticEvent } from 'react'; diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.tsx index d9a0b7abc64..5e41aaac5e9 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx import React, { SyntheticEvent, useState } from 'react'; import { diff --git a/packages/grafana-prometheus/src/configuration/mocks.ts b/packages/grafana-prometheus/src/configuration/mocks.ts index dc52090298b..3c1d7e29762 100644 --- a/packages/grafana-prometheus/src/configuration/mocks.ts +++ b/packages/grafana-prometheus/src/configuration/mocks.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/mocks.ts import { DataSourceSettings } from '@grafana/data'; import { getMockDataSource } from '../gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks'; diff --git a/packages/grafana-prometheus/src/dashboards/grafana_stats.json b/packages/grafana-prometheus/src/dashboards/grafana_stats.json index a121de7fe5a..0131aa9bc40 100644 --- a/packages/grafana-prometheus/src/dashboards/grafana_stats.json +++ b/packages/grafana-prometheus/src/dashboards/grafana_stats.json @@ -1,4 +1,5 @@ { + "_comment": "Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/dashboards/grafana_stats.json", "__inputs": [ { "name": "DS_PROMETHEUS", diff --git a/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json b/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json index 3d4cea64f05..034663fe6f4 100644 --- a/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json +++ b/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json @@ -1,4 +1,5 @@ { + "_comment": "Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/dashboards/prometheus_2_stats.json", "__inputs": [ { "name": "DS_GDEV-PROMETHEUS", diff --git a/packages/grafana-prometheus/src/dashboards/prometheus_stats.json b/packages/grafana-prometheus/src/dashboards/prometheus_stats.json index 9169006b895..8a2764c5cb7 100644 --- a/packages/grafana-prometheus/src/dashboards/prometheus_stats.json +++ b/packages/grafana-prometheus/src/dashboards/prometheus_stats.json @@ -1,4 +1,5 @@ { + "_comment": "Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json", "__inputs": [ { "name": "DS_GDEV-PROMETHEUS", diff --git a/packages/grafana-prometheus/src/dataquery.ts b/packages/grafana-prometheus/src/dataquery.ts index 545a32345b7..c56a5f91307 100644 --- a/packages/grafana-prometheus/src/dataquery.ts +++ b/packages/grafana-prometheus/src/dataquery.ts @@ -1,4 +1,5 @@ -import { Scope } from '@grafana/data'; +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/dataquery.ts +import { ScopeSpec, ScopeSpecFilter } from '@grafana/data'; import * as common from '@grafana/schema'; export enum QueryEditorMode { @@ -42,5 +43,6 @@ export interface Prometheus extends common.DataQuery { * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ range?: boolean; - scope?: Scope; + scope?: ScopeSpec; + adhocFilters?: ScopeSpecFilter[]; } diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index 72f9c4572d8..4a89851f678 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -1,7 +1,9 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/datasource.test.ts import { cloneDeep } from 'lodash'; import { lastValueFrom, of } from 'rxjs'; import { + AdHocVariableFilter, AnnotationEvent, AnnotationQueryRequest, CoreApp, @@ -11,10 +13,11 @@ import { dateTime, LoadingState, rangeUtil, + ScopeSpecFilter, TimeRange, VariableHide, } from '@grafana/data'; -import { TemplateSrv } from '@grafana/runtime'; +import { config, TemplateSrv } from '@grafana/runtime'; import { alignRange, @@ -539,6 +542,10 @@ describe('PrometheusDatasource', () => { }); describe('interpolateVariablesInQueries', () => { + afterEach(() => { + config.featureToggles.promQLScope = undefined; + }); + it('should call replace function 2 times', () => { const query: PromQuery = { expr: 'test{job="testjob"}', @@ -565,6 +572,21 @@ describe('PrometheusDatasource', () => { ds.interpolateVariablesInQueries(queries, {}); expect(ds.enhanceExprWithAdHocFilters).toHaveBeenCalled(); }); + + it('should not apply adhoc filters when promQLScope is enabled', () => { + config.featureToggles.promQLScope = true; + ds.enhanceExprWithAdHocFilters = jest.fn(); + ds.generateScopeFilters = jest.fn(); + const queries = [ + { + refId: 'A', + expr: 'rate({bar="baz", job="foo"} [5m]', + }, + ]; + ds.interpolateVariablesInQueries(queries, {}); + expect(ds.enhanceExprWithAdHocFilters).not.toHaveBeenCalled(); + expect(ds.generateScopeFilters).toHaveBeenCalled(); + }); }); describe('applyTemplateVariables', () => { @@ -572,6 +594,10 @@ describe('PrometheusDatasource', () => { replaceMock.mockImplementation((a: string, ...rest: unknown[]) => a); }); + afterEach(() => { + config.featureToggles.promQLScope = false; + }); + it('should call replace function for legendFormat', () => { const query = { expr: 'test{job="bar"}', @@ -634,6 +660,45 @@ describe('PrometheusDatasource', () => { expect(result).toMatchObject({ expr: 'test{job="bar", k1="v1", k2!="v2"}' }); }); + it('should generate scope filters and **not** apply ad-hoc filters to expr', () => { + config.featureToggles.promQLScope = true; + replaceMock.mockImplementation((a: string) => a); + const filters = [ + { + key: 'k1', + operator: '=', + value: 'v1', + }, + { + key: 'k2', + operator: '!=', + value: 'v2', + }, + ]; + + const query = { + expr: 'test{job="bar"}', + refId: 'A', + }; + + const expectedScopeFilters: ScopeSpecFilter[] = [ + { + key: 'k1', + operator: 'equals', + value: 'v1', + }, + { + key: 'k2', + operator: 'not-equals', + value: 'v2', + }, + ]; + + const result = ds.applyTemplateVariables(query, {}, filters); + expect(result.expr).toBe('test{job="bar"}'); + expect(result.adhocFilters).toEqual(expectedScopeFilters); + }); + it('should add ad-hoc filters only to expr', () => { replaceMock.mockImplementation((a: string) => a?.replace('$A', '99') ?? a); const filters = [ @@ -1167,6 +1232,46 @@ describe('modifyQuery', () => { expect(result.expr).toEqual('go_goroutines{cluster="us-cluster", pod!="pod-123"}'); }); }); + + describe('scope filters', () => { + const instanceSettings = { + access: 'proxy', + id: 1, + jsonData: {}, + name: 'scoped-prom', + readOnly: false, + type: 'prometheus', + uid: 'scoped-prom', + } as unknown as DataSourceInstanceSettings; + const ds = new PrometheusDatasource(instanceSettings, templateSrvStub); + + it('should convert each adhoc operator to scope operator properly', () => { + const adhocFilter: AdHocVariableFilter[] = [ + { key: 'eq', value: 'eqv', operator: '=' }, + { + key: 'neq', + value: 'neqv', + operator: '!=', + }, + { key: 'reg', value: 'regv', operator: '=~' }, + { key: 'nreg', value: 'nregv', operator: '!~' }, + ]; + const expectedScopeFilter: ScopeSpecFilter[] = [ + { key: 'eq', value: 'eqv', operator: 'equals' }, + { + key: 'neq', + value: 'neqv', + operator: 'not-equals', + }, + { key: 'reg', value: 'regv', operator: 'regex-match' }, + { key: 'nreg', value: 'nregv', operator: 'regex-not-match' }, + ]; + const result = ds.generateScopeFilters(adhocFilter); + result.forEach((r, i) => { + expect(r).toEqual(expectedScopeFilter[i]); + }); + }); + }); }); }); diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 6c9a0c81032..0f787a82c93 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/datasource.ts import { defaults } from 'lodash'; import { lastValueFrom, Observable, throwError } from 'rxjs'; import { map, tap } from 'rxjs/operators'; @@ -25,6 +26,8 @@ import { rangeUtil, renderLegendFormat, ScopedVars, + scopeFilterOperatorMap, + ScopeSpecFilter, TimeRange, } from '@grafana/data'; import { @@ -369,7 +372,7 @@ export class PrometheusDatasource }; if (config.featureToggles.promQLScope) { - processedTarget.scope = request.scope; + processedTarget.scope = request.scope?.spec; } if (target.instant && target.range) { @@ -477,8 +480,13 @@ export class PrometheusDatasource let expr = target.expr; - // Apply adhoc filters - expr = this.enhanceExprWithAdHocFilters(options.filters, expr); + if (config.featureToggles.promQLScope) { + // Apply scope filters + query.adhocFilters = this.generateScopeFilters(options.filters); + } else { + // Apply adhoc filters + expr = this.enhanceExprWithAdHocFilters(options.filters, expr); + } // Only replace vars in expression after having (possibly) updated interval vars query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr); @@ -493,6 +501,18 @@ export class PrometheusDatasource return query; } + /** + * This converts the adhocVariableFilter array and converts it to scopeFilter array + * @param filters + */ + generateScopeFilters(filters?: AdHocVariableFilter[]): ScopeSpecFilter[] { + if (!filters) { + return []; + } + + return filters.map((f) => ({ ...f, operator: scopeFilterOperatorMap[f.operator] })); + } + getRateIntervalScopedVariable(interval: number, scrapeInterval: number) { // Fall back to the default scrape interval of 15s if scrapeInterval is 0 for some reason. if (scrapeInterval === 0) { @@ -679,7 +699,7 @@ export class PrometheusDatasource // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { if (!options || options.filters.length === 0) { - await this.languageProvider.fetchLabels(options.timeRange); + await this.languageProvider.fetchLabels(options.timeRange, options.queries); return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k })); } @@ -709,12 +729,13 @@ export class PrometheusDatasource const expr = promQueryModeller.renderLabels(labelFilters); if (this.hasLabelsMatchAPISupport()) { - return (await this.languageProvider.fetchSeriesValuesWithMatch(options.key, expr, options.timeRange)).map( - (v) => ({ - value: v, - text: v, - }) - ); + const requestId = `[${this.uid}][${options.key}]`; + return ( + await this.languageProvider.fetchSeriesValuesWithMatch(options.key, expr, requestId, options.timeRange) + ).map((v) => ({ + value: v, + text: v, + })); } const params = this.getTimeRangeParams(options.timeRange ?? getDefaultTimeRange()); @@ -731,14 +752,17 @@ export class PrometheusDatasource if (queries && queries.length) { expandedQueries = queries.map((query) => { const interpolatedQuery = this.templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr); - const withAdhocFilters = this.enhanceExprWithAdHocFilters(filters, interpolatedQuery); const expandedQuery = { ...query, + ...(config.featureToggles.promQLScope ? { adhocFilters: this.generateScopeFilters(filters) } : {}), datasource: this.getRef(), - expr: withAdhocFilters, + expr: config.featureToggles.promQLScope + ? interpolatedQuery + : this.enhanceExprWithAdHocFilters(filters, interpolatedQuery), interval: this.templateSrv.replace(query.interval, scopedVars), }; + return expandedQuery; }); } @@ -900,12 +924,10 @@ export class PrometheusDatasource // interpolate expression const expr = this.templateSrv.replace(target.expr, variables, this.interpolateQueryExpr); - // Add ad hoc filters - const exprWithAdHocFilters = this.enhanceExprWithAdHocFilters(filters, expr); - return { ...target, - expr: exprWithAdHocFilters, + ...(config.featureToggles.promQLScope ? { adhocFilters: this.generateScopeFilters(filters) } : {}), + expr: config.featureToggles.promQLScope ? expr : this.enhanceExprWithAdHocFilters(filters, expr), interval: this.templateSrv.replace(target.interval, variables), legendFormat: this.templateSrv.replace(target.legendFormat, variables), }; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx index d0558fe43ee..c71e11ec749 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx +++ b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx import React, { useEffect, useState } from 'react'; import store from '../../store'; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx index e4bde7ccaea..e3df7a01194 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx +++ b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx @@ -1 +1,2 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/index.tsx export { LocalStorageValueProvider } from './LocalStorageValueProvider'; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts b/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts index 28587a7d428..a23d14c93cc 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts +++ b/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/store.ts type StoreValue = string | number | boolean | null; export class Store { diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/CancelablePromise.ts b/packages/grafana-prometheus/src/gcopypaste/app/core/utils/CancelablePromise.ts index 221e5ef2bb6..0256922cbbb 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/CancelablePromise.ts +++ b/packages/grafana-prometheus/src/gcopypaste/app/core/utils/CancelablePromise.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/utils/CancelablePromise.ts // https://github.com/facebook/react/issues/5465 export interface CancelablePromise { diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts b/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts index 4084e418f18..28b4af48c0b 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts +++ b/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/utils/query.ts import { DataQuery } from '@grafana/data'; export const getNextRefIdChar = (queries: DataQuery[]): string => { diff --git a/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts b/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts index db78a03b289..15ebe4229a9 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts +++ b/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/datasources/__mocks__/dataSourcesMocks.ts import { merge } from 'lodash'; import { DataSourceJsonData, DataSourceSettings } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts b/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts index 39b01600d46..a2d00ad34f4 100644 --- a/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts +++ b/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/live/data/amendTimeSeries.ts import { closestIdx } from '@grafana/data'; export type Table = [times: number[], ...values: any[][]]; diff --git a/packages/grafana-prometheus/src/gcopypaste/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-prometheus/src/gcopypaste/packages/grafana-ui/src/components/Select/SelectBase.tsx index 9c6e81eec9f..f252ee11767 100644 --- a/packages/grafana-prometheus/src/gcopypaste/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-prometheus/src/gcopypaste/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/packages/grafana-ui/src/components/Select/SelectBase.tsx import { cx } from '@emotion/css'; import { max } from 'lodash'; import React, { RefCallback } from 'react'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts index b6f8a091aaa..ea1578a7482 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/index.ts import { Observable } from 'rxjs'; import { toEmitValues } from './toEmitValues'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts index 71edff27e8d..bb0d9aa3c63 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.test.ts import { interval, Observable, of, throwError } from 'rxjs'; import { map, mergeMap, take } from 'rxjs/operators'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts index d2987471a38..5d87c1f342d 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.ts import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; import { isEqual } from 'lodash'; import { Observable, Subscription } from 'rxjs'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts index 7ca3eed1c57..352c8237665 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.test.ts import { interval, Observable, of, throwError } from 'rxjs'; import { map, mergeMap, take } from 'rxjs/operators'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts index d9a53e965a0..2c647baea5b 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts @@ -1,3 +1,4 @@ +// Core Grafana history import { matcherHint, printReceived } from 'jest-matcher-utils'; import { Observable, Subscription } from 'rxjs'; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts index c67b32897f5..953b5ff86da 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.ts import { Observable } from 'rxjs'; export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts index e7758bb9177..3915cc13a4f 100644 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts +++ b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/utils.ts import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; import { asapScheduler, Subscription, timer, isObservable } from 'rxjs'; diff --git a/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts b/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts index c1ff432dacc..f8d333d2c2e 100644 --- a/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts +++ b/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/packages/grafana-ui/src/components/Select/SelectBase.tsx import { Matcher, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { select } from 'react-select-event'; diff --git a/packages/grafana-prometheus/src/index.ts b/packages/grafana-prometheus/src/index.ts index 76302ec9570..acf324f7e92 100644 --- a/packages/grafana-prometheus/src/index.ts +++ b/packages/grafana-prometheus/src/index.ts @@ -59,14 +59,14 @@ export { PromQail } from './querybuilder/components/promQail/PromQail'; // SRC/ // Main export -export { PrometheusDatasource } from './datasource'; +export { PrometheusDatasource, InstantQueryRefIdIndex } from './datasource'; // The parts export { addLabelToQuery } from './add_label_to_query'; export { type QueryEditorMode, type PromQueryFormat, type Prometheus } from './dataquery'; export { PrometheusMetricFindQuery } from './metric_find_query'; export { promqlGrammar } from './promql'; export { getQueryHints, getInitHints } from './query_hints'; -export { transformV2, transformDFToTable } from './result_transformer'; +export { transformV2, transformDFToTable, parseSampleValue, sortSeriesByLabel } from './result_transformer'; export { type PromQuery, type PrometheusCacheLevel, diff --git a/packages/grafana-prometheus/src/language_provider.mock.ts b/packages/grafana-prometheus/src/language_provider.mock.ts index 731a1977ca9..e10c2844b19 100644 --- a/packages/grafana-prometheus/src/language_provider.mock.ts +++ b/packages/grafana-prometheus/src/language_provider.mock.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.mock.ts export class EmptyLanguageProviderMock { metrics = []; constructor() {} diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index 574fec92ef0..dd7dec7ce71 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -1,10 +1,11 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.test.ts import { AbstractLabelOperator, dateTime, TimeRange } from '@grafana/data'; import { Label } from './components/monaco-query-field/monaco-completion-provider/situation'; import { PrometheusDatasource } from './datasource'; import LanguageProvider from './language_provider'; import { getClientCacheDurationInMinutes, getPrometheusTime, getRangeSnapInterval } from './language_utils'; -import { PrometheusCacheLevel } from './types'; +import { PrometheusCacheLevel, PromQuery } from './types'; const now = new Date(1681300293392).getTime(); const timeRangeDurationSeconds = 1; @@ -306,6 +307,207 @@ describe('Language completion provider', () => { }); }); + describe('fetchLabels', () => { + const tr = getMockTimeRange(); + const getParams = (requestSpy: ReturnType) => { + // Following is equal to `URLSearchParams().toString()` + return requestSpy.mock.calls[0][2]?.toString() ?? 'undefined'; + }; + + describe('with POST', () => { + let languageProvider: LanguageProvider; + beforeEach(() => { + languageProvider = new LanguageProvider({ + ...defaultDatasource, + httpMethod: 'POST', + } as PrometheusDatasource); + }); + + it('should send query metrics to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from complex query to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'histogram_quantile(0.95, sum(rate(go_gc_pauses_seconds_bucket[$__rate_interval])) by (le))', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from multiple queries to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total', + }, + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from a query contains multiple metrics to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from a query contains multiple metrics and queries to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'A', + expr: 'histogram_quantile(0.95, sum(rate(process_max_fds[$__rate_interval])) by (le)) + go_gc_heap_frees_by_size_bytes_bucket', + }, + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch( + encodeURI( + 'match[]=process_max_fds&match[]=go_gc_heap_frees_by_size_bytes_bucket&match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket' + ) + ); + }); + }); + + describe('with GET', () => { + let languageProvider: LanguageProvider; + beforeEach(() => { + languageProvider = new LanguageProvider({ + ...defaultDatasource, + httpMethod: 'GET', + } as PrometheusDatasource); + }); + + it('should send query metrics to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from complex query to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'histogram_quantile(0.95, sum(rate(go_gc_pauses_seconds_bucket[$__rate_interval])) by (le))', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from multiple queries to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total', + }, + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket') + ); + }); + + it('should send metrics from a query contains multiple metrics to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket') + ); + }); + + it('should send metrics from a query contains multiple metrics and queries to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'A', + expr: 'histogram_quantile(0.95, sum(rate(process_max_fds[$__rate_interval])) by (le)) + go_gc_heap_frees_by_size_bytes_bucket', + }, + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI( + 'match[]=process_max_fds&match[]=go_gc_heap_frees_by_size_bytes_bucket&match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket' + ) + ); + }); + }); + }); + describe('fetchLabelValues', () => { it('should interpolate variable in series', () => { const languageProvider = new LanguageProvider({ diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index 9ad75e37023..a6105c988a3 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.ts import { once } from 'lodash'; import Prism from 'prismjs'; @@ -21,6 +22,7 @@ import { toPromLikeQuery, } from './language_utils'; import PromqlSyntax from './promql'; +import { buildVisualQueryFromString } from './querybuilder/parsing'; import { PrometheusCacheLevel, PromMetricsMetadata, PromQuery } from './types'; const DEFAULT_KEYS = ['job', 'instance']; @@ -106,7 +108,9 @@ export default class PromQlLanguageProvider extends LanguageProvider { const res = await this.datasource.metadataRequest(url, params, options); return res.data.data; } catch (error) { - console.error(error); + if (!isCancelledError(error)) { + console.error(error); + } } return defaultValue; @@ -204,21 +208,36 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Fetches all label keys */ - async fetchLabels(timeRange?: TimeRange): Promise { + fetchLabels = async (timeRange?: TimeRange, queries?: PromQuery[]): Promise => { if (timeRange) { this.timeRange = timeRange; } - const url = '/api/v1/labels'; - const params = this.datasource.getAdjustedInterval(this.timeRange); + let url = '/api/v1/labels'; + const timeParams = this.datasource.getAdjustedInterval(this.timeRange); this.labelFetchTs = Date.now().valueOf(); - const res = await this.request(url, [], params, this.getDefaultCacheHeaders()); + const searchParams = new URLSearchParams({ ...timeParams }); + queries?.forEach((q) => { + const visualQuery = buildVisualQueryFromString(q.expr); + searchParams.append('match[]', visualQuery.query.metric); + if (visualQuery.query.binaryQueries) { + visualQuery.query.binaryQueries.forEach((bq) => { + searchParams.append('match[]', bq.query.metric); + }); + } + }); + + if (this.datasource.httpMethod === 'GET') { + url += `?${searchParams.toString()}`; + } + + const res = await this.request(url, [], searchParams, this.getDefaultCacheHeaders()); if (Array.isArray(res)) { this.labelKeys = res.slice().sort(); } return []; - } + }; /** * Gets series values @@ -240,10 +259,12 @@ export default class PromQlLanguageProvider extends LanguageProvider { * @param name * @param match * @param timeRange + * @param requestId */ fetchSeriesValuesWithMatch = async ( name: string, - match?: string, + match: string, + requestId?: string, timeRange: TimeRange = this.timeRange ): Promise => { const interpolatedName = name ? this.datasource.interpolateString(name) : null; @@ -253,13 +274,16 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...range, ...(interpolatedMatch && { 'match[]': interpolatedMatch }), }; + let requestOptions: Partial | undefined = { + ...this.getDefaultCacheHeaders(), + ...(requestId && { requestId }), + }; - const value = await this.request( - `/api/v1/label/${interpolatedName}/values`, - [], - urlParams, - this.getDefaultCacheHeaders() - ); + if (!Object.keys(requestOptions).length) { + requestOptions = undefined; + } + + const value = await this.request(`/api/v1/label/${interpolatedName}/values`, [], urlParams, requestOptions); return value ?? []; }; @@ -376,3 +400,9 @@ function getNameLabelValue(promQuery: string, tokens: Array { diff --git a/packages/grafana-prometheus/src/module.ts b/packages/grafana-prometheus/src/module.ts index 8fdb90c8bd9..0bbabf50383 100644 --- a/packages/grafana-prometheus/src/module.ts +++ b/packages/grafana-prometheus/src/module.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/module.ts // DONT NEED THIS BUT MAYBE EXPORT THIS TO CORE PROM import { DataSourcePlugin } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/promql.test.ts b/packages/grafana-prometheus/src/promql.test.ts index ff78a805ef2..8ce99e175fc 100644 --- a/packages/grafana-prometheus/src/promql.test.ts +++ b/packages/grafana-prometheus/src/promql.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/promql.test.ts import Prism from 'prismjs'; import promql from './promql'; diff --git a/packages/grafana-prometheus/src/promql.ts b/packages/grafana-prometheus/src/promql.ts index c37f52796ba..58893206b09 100644 --- a/packages/grafana-prometheus/src/promql.ts +++ b/packages/grafana-prometheus/src/promql.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/promql.ts import { Grammar } from 'prismjs'; import { CompletionItem } from '@grafana/ui'; @@ -534,6 +535,44 @@ export const FUNCTIONS = [ detail: 'present_over_time(range-vector)', documentation: 'The value 1 for any series in the specified interval.', }, + { + insertText: 'histogram_avg', + label: 'histogram_avg', + detail: 'histogram_avg(v instant-vector)', + documentation: + 'Returns the arithmetic average of observed values stored in a native histogram. Samples that are not native histograms are ignored and do not show up in the returned vector.', + }, + { + insertText: 'histogram_count', + label: 'histogram_count', + detail: 'histogram_count(v instant-vector)', + documentation: 'Returns the count of observations stored in a native histogram.', + }, + { + insertText: 'histogram_sum', + label: 'histogram_sum', + detail: 'histogram_sum(v instant-vector)', + documentation: 'Returns the sum of observations stored in a native histogram.', + }, + { + insertText: 'histogram_fraction', + label: 'histogram_fraction', + detail: 'histogram_fraction(lower scalar, upper scalar, v instant-vector)', + documentation: 'Returns the estimated fraction of observations between the provided lower and upper values.', + }, + { + insertText: 'histogram_stddev', + label: 'histogram_stddev', + detail: 'histogram_stddev(v instant-vector)', + documentation: + 'Returns the estimated standard deviation of observations in a native histogram, based on the geometric mean of the buckets where the observations lie.', + }, + { + insertText: 'histogram_stdvar', + label: 'histogram_stdvar', + detail: 'histogram_stdvar(v instant-vector)', + documentation: 'Returns the estimated standard variance of observations in a native histogram.', + }, ]; export const PROM_KEYWORDS = FUNCTIONS.map((keyword) => keyword.label); diff --git a/packages/grafana-prometheus/src/query_hints.test.ts b/packages/grafana-prometheus/src/query_hints.test.ts index 7089ea3e2e1..aa425a22f28 100644 --- a/packages/grafana-prometheus/src/query_hints.test.ts +++ b/packages/grafana-prometheus/src/query_hints.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/query_hints.test.ts import { PrometheusDatasource } from './datasource'; import { getQueryHints, SUM_HINT_THRESHOLD_COUNT } from './query_hints'; diff --git a/packages/grafana-prometheus/src/query_hints.ts b/packages/grafana-prometheus/src/query_hints.ts index 12c8bdfbbaf..10ec829f364 100644 --- a/packages/grafana-prometheus/src/query_hints.ts +++ b/packages/grafana-prometheus/src/query_hints.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/query_hints.ts import { size } from 'lodash'; import { QueryFix, QueryHint } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.test.ts b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.test.ts index f81ab15cbc9..432adeb59d4 100644 --- a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts import { PromQueryModeller } from './PromQueryModeller'; import { PromOperationId } from './types'; diff --git a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts index 894ba907ed1..cef7b53e6d6 100644 --- a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts +++ b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts import { FUNCTIONS } from '../promql'; import { getAggregationOperations } from './aggregations'; diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx index 083396cda39..a332a7f2da4 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/QueryPattern.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.test.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.test.tsx index a8a2b3c9e02..8cc19b9cbe9 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/QueryPatternsModal.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx index f98a778e778..404b0b7a502 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/QueryPatternsModal.tsx import { css } from '@emotion/css'; import { capitalize } from 'lodash'; import React, { useMemo, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/aggregations.ts b/packages/grafana-prometheus/src/querybuilder/aggregations.ts index 18a2f72b687..d4129c3665e 100644 --- a/packages/grafana-prometheus/src/querybuilder/aggregations.ts +++ b/packages/grafana-prometheus/src/querybuilder/aggregations.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/aggregations.ts import { createAggregationOperation, createAggregationOperationWithParam, diff --git a/packages/grafana-prometheus/src/querybuilder/binaryScalarOperations.ts b/packages/grafana-prometheus/src/querybuilder/binaryScalarOperations.ts index 523f85e99a7..b1fc45e849a 100644 --- a/packages/grafana-prometheus/src/querybuilder/binaryScalarOperations.ts +++ b/packages/grafana-prometheus/src/querybuilder/binaryScalarOperations.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts import { defaultAddOperationHandler } from './operationUtils'; import { QueryBuilderOperation, QueryBuilderOperationDef, QueryBuilderOperationParamDef } from './shared/types'; import { PromOperationId, PromVisualQueryOperationCategory } from './types'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx index c1a28d8f96f..c8075933f3b 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilterItem.tsx import debounce from 'debounce-promise'; import React, { useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx index fcd1b06b72a..b1e21da0096 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilters.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React, { ComponentProps } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx index 09f5fd1d822..9364dd83f28 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/LabelFilters.tsx import { css, cx } from '@emotion/css'; import { isEqual } from 'lodash'; import React, { useEffect, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx index 6cf4e9a9b74..0c1c4a8e35c 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx import React, { useState } from 'react'; import { DataSourceApi, SelectableValue, toOption } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx index a0561768837..cb115f389c0 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx index 3e0ab156c2e..35a10f7064c 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx import { css } from '@emotion/css'; import debounce from 'debounce-promise'; import React, { RefCallback, useCallback, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx index b27af15c168..6d25c74377d 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricsLabelsSection.tsx import React, { useCallback } from 'react'; import { SelectableValue } from '@grafana/data'; @@ -147,12 +148,12 @@ export function MetricsLabelsSection({ if (!forLabel.label) { return Promise.resolve([]); } - return datasource.languageProvider.fetchSeriesValuesWithMatch(forLabel.label, promQLExpression).then((response) => { - return response.map((v) => ({ - value: v, - label: v, - })); - }); + + const requestId = `[${datasource.uid}][${query.metric}][${forLabel.label}][${forLabel.op}]`; + + return datasource.languageProvider + .fetchSeriesValuesWithMatch(forLabel.label, promQLExpression, requestId) + .then((response) => response.map((v) => ({ value: v, label: v }))); }; /** diff --git a/packages/grafana-prometheus/src/querybuilder/components/NestedQuery.tsx b/packages/grafana-prometheus/src/querybuilder/components/NestedQuery.tsx index ecba5898454..efdffc73023 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/NestedQuery.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/NestedQuery.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQuery.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/NestedQueryList.tsx b/packages/grafana-prometheus/src/querybuilder/components/NestedQueryList.tsx index fb9e60229fe..7d36bec9919 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/NestedQueryList.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/NestedQueryList.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/NestedQueryList.tsx import React from 'react'; import { Stack } from '@grafana/ui'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx index 54f44c46f13..fdbb2779a4f 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.test.tsx import { getByText, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx index e05b1c2caa2..90e4d46f8c7 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx index 5172172fd32..e55b4f2d3f4 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.tsx index 46d3dc05625..a960fc3a58e 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.tsx import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import React, { useEffect, useReducer } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderExplained.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderExplained.tsx index 38389fbc6e4..c3de6c3808e 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderExplained.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderExplained.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderExplained.tsx import React from 'react'; import { Stack } from '@grafana/ui'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx index 331092c1962..9b9531a28ed 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx index 79f93db2fe7..7a554d9be45 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx import React, { SyntheticEvent } from 'react'; import { CoreApp, SelectableValue } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.test.tsx index 5554f33997d..c907e604576 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.test.tsx import { render, screen } from '@testing-library/react'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx index 75bc63055e6..84e5805a4f3 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx index 9e9079188f3..2dbc49535a7 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { cloneDeep, defaultsDeep } from 'lodash'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx index b46ef45c6f0..30c5502bacb 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx import { isEqual, map } from 'lodash'; import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx index 440278b4c50..5be07dee6be 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryLegendEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx import React, { useRef } from 'react'; import { SelectableValue } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/QueryPreview.tsx b/packages/grafana-prometheus/src/querybuilder/components/QueryPreview.tsx index 0f731ec0b3d..2bd0e252672 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/QueryPreview.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/QueryPreview.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/QueryPreview.tsx import React from 'react'; import { EditorFieldGroup, EditorRow } from '@grafana/experimental'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx index c5a7d104849..38caa6e6f49 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/AdditionalSettings.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/FeedbackLink.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/FeedbackLink.tsx index 4ba1d881147..954efbd61e1 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/FeedbackLink.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/FeedbackLink.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/FeedbackLink.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx index f12d7c67080..ca0c28916c3 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.tsx index 6e02715a804..4d6aab8dc0d 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.tsx import { cx } from '@emotion/css'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import debounce from 'debounce-promise'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx index a474098bbc0..ef396c58e21 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/ResultsTable.tsx import { css } from '@emotion/css'; import React, { ReactElement } from 'react'; import Highlighter from 'react-highlight-words'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/helpers.ts b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/helpers.ts index e8a40d02b0c..19d016eafd0 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/helpers.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/helpers.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/state/helpers.ts import { AnyAction } from '@reduxjs/toolkit'; import { reportInteraction } from '@grafana/runtime'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/state.ts b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/state.ts index f22bd73ea4b..fa29ba2450e 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/state.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/state.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/state/state.ts import { SelectableValue } from '@grafana/data'; import { PromVisualQuery } from '../../../types'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts index e8eb2fe86dd..e105530602c 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/styles.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/styles.ts import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/types.ts b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/types.ts index d4be9a48638..233f3765e8a 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/types.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/types.ts export type MetricsData = MetricData[]; export type MetricData = { diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/uFuzzy.ts b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/uFuzzy.ts index 7c7e5eb44ea..7b070b9ab58 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/uFuzzy.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/uFuzzy.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/uFuzzy.ts import uFuzzy from '@leeoniya/ufuzzy'; import { debounce as debounceLodash } from 'lodash'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.test.tsx index b4056fb8bca..0874258cc87 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx index 8777f8c48ab..638d3f53b93 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx import { css, cx } from '@emotion/css'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import React, { useEffect, useReducer, useRef, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.test.tsx index 2b64277c22c..3bfabc4bf60 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.test.tsx import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.tsx index 5445134b64b..577d330691e 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/QueryAssistantButton.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.tsx import React from 'react'; import { selectors } from '@grafana/e2e-selectors'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionContainer.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionContainer.tsx index 1a52194b9cb..469811e72e2 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionContainer.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionContainer.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx import { cx } from '@emotion/css'; import React, { useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionItem.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionItem.tsx index 9076faae6bc..09a5b1c3fce 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionItem.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/QuerySuggestionItem.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionItem.tsx import { cx } from '@emotion/css'; import React, { FormEvent, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/prompts.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/prompts.ts index b92bd2d2f14..3a62b1c7b72 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/prompts.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/prompts.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/prompts.ts export const ExplainSystemPrompt = `You are an expert in Prometheus, the event monitoring and alerting application. You are given relevant PromQL documentation, a type and description for a Prometheus metric, and a PromQL query on that metric. Using the provided information for reference, please explain what the output of a given query is in 1 sentences. Do not walk through what the functions do separately, make your answer concise. diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.test.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.test.ts index 323848bebdc..90da7dbadd5 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.test.ts import { llms } from '@grafana/experimental'; import { guessMetricType, isLLMPluginEnabled } from './helpers'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.ts index 8bf207da75e..ea94d6da2a7 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/helpers.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.ts import { AnyAction } from 'redux'; import { llms } from '@grafana/experimental'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/state.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/state.ts index 2c00053f1dc..a33d0a227ee 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/state.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/state.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/state.ts import { PromVisualQuery } from '../../../types'; import { Interaction, SuggestionType } from '../types'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/templates.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/templates.ts index 2a56e9f4fbb..330de01da2f 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/state/templates.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/state/templates.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/templates.ts import { QuerySuggestion } from '../types'; interface TemplateData { diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/types.ts b/packages/grafana-prometheus/src/querybuilder/components/promQail/types.ts index e8c5005cfac..09f4a3988f5 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/types.ts +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/types.ts export type QuerySuggestion = { query: string; explanation: string; diff --git a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.test.ts b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.test.ts index 4c7ca4e6393..a3c95b17a52 100644 --- a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/hooks/useFlag.test.ts import { act, renderHook } from '@testing-library/react'; import { promQueryEditorExplainKey, useFlag } from './useFlag'; diff --git a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts index 183a956d45a..7dfc732b025 100644 --- a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts +++ b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/hooks/useFlag.ts import { useCallback, useState } from 'react'; import store from '../../gcopypaste/app/core/store'; diff --git a/packages/grafana-prometheus/src/querybuilder/operationUtils.test.ts b/packages/grafana-prometheus/src/querybuilder/operationUtils.test.ts index da1aabe924e..d1e690bc807 100644 --- a/packages/grafana-prometheus/src/querybuilder/operationUtils.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/operationUtils.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/operationUtils.test.ts import { createAggregationOperation, createAggregationOperationWithParam, diff --git a/packages/grafana-prometheus/src/querybuilder/operationUtils.ts b/packages/grafana-prometheus/src/querybuilder/operationUtils.ts index 9be994d73da..a0bda0d598f 100644 --- a/packages/grafana-prometheus/src/querybuilder/operationUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/operationUtils.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/operationUtils.ts import { capitalize } from 'lodash'; import pluralize from 'pluralize'; diff --git a/packages/grafana-prometheus/src/querybuilder/operations.ts b/packages/grafana-prometheus/src/querybuilder/operations.ts index 3b445efa37e..afecda0ab31 100644 --- a/packages/grafana-prometheus/src/querybuilder/operations.ts +++ b/packages/grafana-prometheus/src/querybuilder/operations.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/operations.ts import { binaryScalarOperations } from './binaryScalarOperations'; import { LabelParamEditor } from './components/LabelParamEditor'; import { @@ -28,6 +29,23 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] { renderer: functionRendererLeft, addOperationHandler: defaultAddOperationHandler, }, + createFunction({ id: PromOperationId.HistogramAvg }), + createFunction({ id: PromOperationId.HistogramCount }), + createFunction({ id: PromOperationId.HistogramSum }), + { + id: PromOperationId.HistogramFraction, + name: 'Histogram fraction', + params: [ + { name: 'Lower scalar', type: 'number' }, + { name: 'Upper scalar', type: 'number' }, + ], + defaultParams: [0.0, 0.2], + category: PromVisualQueryOperationCategory.Functions, + renderer: functionRendererLeft, + addOperationHandler: defaultAddOperationHandler, + }, + createFunction({ id: PromOperationId.HistogramStddev }), + createFunction({ id: PromOperationId.HistogramStdvar }), { id: PromOperationId.LabelReplace, name: 'Label replace', diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts index 08fa72b7b8a..1522aff295f 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts import { buildVisualQueryFromString } from './parsing'; import { PromOperationId, PromVisualQuery } from './types'; @@ -290,6 +291,28 @@ describe('buildVisualQueryFromString', () => { }); }); + // enable in #85942 when updated lezer parser is merged + xit('parses a native histogram function correctly', () => { + expect( + buildVisualQueryFromString('histogram_count(rate(counters_logins{app="backend"}[$__rate_interval]))') + ).toEqual({ + errors: [], + query: { + metric: 'counters_logins', + labels: [{ label: 'app', op: '=', value: 'backend' }], + operations: [ + { + id: 'rate', + params: ['$__rate_interval'], + }, + { + id: 'histogram_quantile', + }, + ], + }, + }); + }); + it('parses function with multiple arguments', () => { expect( buildVisualQueryFromString( diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.ts b/packages/grafana-prometheus/src/querybuilder/parsing.ts index de1486efc2a..af31365c564 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts import { SyntaxNode } from '@lezer/common'; import { AggregateExpr, diff --git a/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts b/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts index 81c8d255ca6..3bef72e1fd0 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/parsingUtils.test.ts import { parser } from '@prometheus-io/lezer-promql'; import { getLeftMostChild, getString, replaceVariables } from './parsingUtils'; diff --git a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts index 10be8692ebc..bc19084122f 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/parsingUtils.ts import { SyntaxNode, TreeCursor } from '@lezer/common'; import { QueryBuilderOperation, QueryBuilderOperationParamValue } from './shared/types'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/LokiAndPromQueryModellerBase.ts b/packages/grafana-prometheus/src/querybuilder/shared/LokiAndPromQueryModellerBase.ts index 81afc5a2762..872973b4887 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/LokiAndPromQueryModellerBase.ts +++ b/packages/grafana-prometheus/src/querybuilder/shared/LokiAndPromQueryModellerBase.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/LokiAndPromQueryModellerBase.ts import { Registry } from '@grafana/data'; import { PromVisualQueryOperationCategory } from '../types'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx index cd4dd77ff72..405b40b7cd2 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx import { css, cx } from '@emotion/css'; import React, { useEffect, useId, useState } from 'react'; import { Draggable } from 'react-beautiful-dnd'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationExplainedBox.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationExplainedBox.tsx index f38597c3c10..d5e5f753997 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationExplainedBox.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationExplainedBox.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationHeader.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationHeader.tsx index a47c747c9d5..757befe40ec 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationHeader.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationHeader.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationHeader.tsx import { css } from '@emotion/css'; import React, { useState } from 'react'; import { DraggableProvided } from 'react-beautiful-dnd'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx index 56f12509390..c5149c16ef9 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationInfoButton.tsx import { css } from '@emotion/css'; import { autoUpdate, diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx index 02d95b87236..912bc4e4952 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationList.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.tsx index 6e104e2675f..875eb166730 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationList.tsx import { css } from '@emotion/css'; import React, { useState } from 'react'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationListExplained.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationListExplained.tsx index bfc7fba3808..bde7203584c 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationListExplained.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationListExplained.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained.tsx import { Grammar } from 'prismjs'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx index e9bbbff644b..d4f8694c4a6 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationParamEditor.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx import { css } from '@emotion/css'; import React, { ComponentType } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationsEditorRow.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationsEditorRow.tsx index 00ebc654f06..696d034b5a9 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationsEditorRow.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationsEditorRow.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow.tsx import { css } from '@emotion/css'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx index 91389eb495b..25ed6b8e271 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryBuilderHints.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryBuilderHints.tsx import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx index b47d89b6301..f5541dde1c8 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryEditorModeToggle.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle.tsx import React from 'react'; import { RadioButtonGroup } from '@grafana/ui'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryHeaderSwitch.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryHeaderSwitch.tsx index b7088bb3f8b..f92f2accd44 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryHeaderSwitch.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryHeaderSwitch.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch.tsx import { css } from '@emotion/css'; import { uniqueId } from 'lodash'; import React, { HTMLProps, useRef } from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx index b0c6951b092..7d8df70545b 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup.tsx import { css } from '@emotion/css'; import React from 'react'; import { useToggle } from 'react-use'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/RawQuery.tsx b/packages/grafana-prometheus/src/querybuilder/shared/RawQuery.tsx index b2cc3d8a372..da7ec8c0f79 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/RawQuery.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/RawQuery.tsx @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/RawQuery.tsx import { css, cx } from '@emotion/css'; import Prism, { Grammar } from 'prismjs'; import React from 'react'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/types.ts b/packages/grafana-prometheus/src/querybuilder/shared/types.ts index 73d46de0a74..e8764866c43 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/types.ts +++ b/packages/grafana-prometheus/src/querybuilder/shared/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts /** * Shared types that can be reused by Loki and other data sources */ diff --git a/packages/grafana-prometheus/src/querybuilder/state.test.ts b/packages/grafana-prometheus/src/querybuilder/state.test.ts index a96f876ada1..04293ec686c 100644 --- a/packages/grafana-prometheus/src/querybuilder/state.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/state.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/state.test.ts import { CoreApp } from '@grafana/data'; import { PromQuery } from '../types'; diff --git a/packages/grafana-prometheus/src/querybuilder/state.ts b/packages/grafana-prometheus/src/querybuilder/state.ts index 06ef89c33fc..fddda52c406 100644 --- a/packages/grafana-prometheus/src/querybuilder/state.ts +++ b/packages/grafana-prometheus/src/querybuilder/state.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/state.ts import { CoreApp } from '@grafana/data'; import store from '../gcopypaste/app/core/store'; diff --git a/packages/grafana-prometheus/src/querybuilder/testUtils.ts b/packages/grafana-prometheus/src/querybuilder/testUtils.ts index 818d05756b4..04ef4c911e7 100644 --- a/packages/grafana-prometheus/src/querybuilder/testUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/testUtils.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/testUtils.ts import { getAllByRole, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/packages/grafana-prometheus/src/querybuilder/types.ts b/packages/grafana-prometheus/src/querybuilder/types.ts index 24750f21a96..c846adff8a5 100644 --- a/packages/grafana-prometheus/src/querybuilder/types.ts +++ b/packages/grafana-prometheus/src/querybuilder/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/types.ts import { VisualQueryBinary } from './shared/LokiAndPromQueryModellerBase'; import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types'; @@ -63,6 +64,12 @@ export enum PromOperationId { Floor = 'floor', Group = 'group', HistogramQuantile = 'histogram_quantile', + HistogramAvg = 'histogram_avg', + HistogramCount = 'histogram_count', + HistogramSum = 'histogram_sum', + HistogramFraction = 'histogram_fraction', + HistogramStddev = 'histogram_stddev', + HistogramStdvar = 'histogram_stdvar', HoltWinters = 'holt_winters', Hour = 'hour', Idelta = 'idelta', diff --git a/packages/grafana-prometheus/src/querycache/QueryCache.test.ts b/packages/grafana-prometheus/src/querycache/QueryCache.test.ts index d0f385a7516..78ff585ec20 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCache.test.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCache.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querycache/QueryCache.test.ts import moment from 'moment'; import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@grafana/data'; diff --git a/packages/grafana-prometheus/src/querycache/QueryCache.ts b/packages/grafana-prometheus/src/querycache/QueryCache.ts index 0cf5f84c2f5..05ad2c3f1d3 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCache.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCache.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts import { DataFrame, DataQueryRequest, diff --git a/packages/grafana-prometheus/src/querycache/QueryCacheTestData.ts b/packages/grafana-prometheus/src/querycache/QueryCacheTestData.ts index ec4d5154bf2..a066f3107e4 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCacheTestData.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCacheTestData.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querycache/QueryCacheTestData.ts import { clone } from 'lodash'; /** diff --git a/packages/grafana-prometheus/src/result_transformer.test.ts b/packages/grafana-prometheus/src/result_transformer.test.ts index bc2f251767f..b530babd4f5 100644 --- a/packages/grafana-prometheus/src/result_transformer.test.ts +++ b/packages/grafana-prometheus/src/result_transformer.test.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/result_transformer.test.ts import { cacheFieldDisplayNames, createDataFrame, diff --git a/packages/grafana-prometheus/src/result_transformer.ts b/packages/grafana-prometheus/src/result_transformer.ts index ac93f7955a8..a3f11c39087 100644 --- a/packages/grafana-prometheus/src/result_transformer.ts +++ b/packages/grafana-prometheus/src/result_transformer.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/result_transformer.ts import { flatten, forOwn, groupBy, partition } from 'lodash'; import { diff --git a/packages/grafana-prometheus/src/tracking.ts b/packages/grafana-prometheus/src/tracking.ts index 76706984df3..0fbce200fd6 100644 --- a/packages/grafana-prometheus/src/tracking.ts +++ b/packages/grafana-prometheus/src/tracking.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/tracking.ts import { CoreApp, DataQueryRequest, DataQueryResponse } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; @@ -9,10 +10,10 @@ export function trackQuery( startTime: Date ): void { const { app, targets: queries } = request; - // We do want to track panel-editor and explore - // We do not want to track queries from the dashboard or viewing a panel - // also included in the tracking is cloud-alerting, unified-alerting, and unknown - if (app === CoreApp.Dashboard || app === CoreApp.PanelViewer) { + // We only track queries run in Explore. + // We do not want to track queries from the dashboard, viewing a panel, + // cloud-alerting, unified-alerting, scenes and unknown + if (app !== CoreApp.Explore) { return; } diff --git a/packages/grafana-prometheus/src/types.ts b/packages/grafana-prometheus/src/types.ts index f6837388e05..beb9fb07a63 100644 --- a/packages/grafana-prometheus/src/types.ts +++ b/packages/grafana-prometheus/src/types.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/types.ts import { DataSourceJsonData } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; diff --git a/packages/grafana-prometheus/src/variables.ts b/packages/grafana-prometheus/src/variables.ts index fcf545e0d1c..cf3ac7c5f63 100644 --- a/packages/grafana-prometheus/src/variables.ts +++ b/packages/grafana-prometheus/src/variables.ts @@ -1,3 +1,4 @@ +// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/variables.ts import { from, Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index f52d0a5f5bf..9b341226b25 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -52,13 +52,13 @@ "@rollup/plugin-node-resolve": "15.2.3", "@rollup/plugin-terser": "0.4.4", "@testing-library/dom": "10.0.0", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/angular": "1.8.9", "@types/history": "4.7.11", "@types/jest": "29.5.12", "@types/lodash": "4.17.0", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-dom": "18.2.25", "@types/systemjs": "6.13.5", "esbuild": "0.18.12", diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.tsx index b53e83f124d..442bb2ad45d 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.tsx @@ -10,7 +10,7 @@ import { SelectableValue, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { ActionMeta, HorizontalGroup, PluginSignatureBadge, Select } from '@grafana/ui'; +import { ActionMeta, PluginSignatureBadge, Select, Stack } from '@grafana/ui'; import { getDataSourceSrv } from '../services/dataSourceSrv'; @@ -209,9 +209,9 @@ export class DataSourcePicker extends PureComponent { if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) { return ( - + {o.label} - + ); } return o.label || ''; diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 0b5f9574ee4..8fb27b3851c 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -83,8 +83,10 @@ export interface CanvasConnection { path: ConnectionPath; size?: ui.ScaleDimensionConfig; source: ConnectionCoordinates; + sourceOriginal?: ConnectionCoordinates; target: ConnectionCoordinates; targetName?: string; + targetOriginal?: ConnectionCoordinates; vertices?: Array; } diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 7484862733f..39b1bfebc0d 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -35,11 +35,11 @@ "devDependencies": { "@grafana/tsconfig": "^1.3.0-rc1", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.4", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/systemjs": "6.13.5", "@types/testing-library__jest-dom": "5.14.9", "jest": "^29.6.4", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 882d983f289..9e88abab438 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -75,7 +75,7 @@ "lodash": "4.17.21", "micro-memoize": "^4.1.2", "moment": "2.30.1", - "monaco-editor": "0.34.0", + "monaco-editor": "0.34.1", "ol": "7.4.0", "prismjs": "1.29.0", "rc-cascader": "3.24.1", @@ -129,7 +129,7 @@ "@storybook/theming": "7.4.5", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "15.0.1", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/common-tags": "^1.8.0", "@types/d3": "7.4.3", @@ -141,9 +141,8 @@ "@types/mock-raf": "1.0.6", "@types/node": "20.12.7", "@types/prismjs": "1.26.3", - "@types/react": "18.2.77", + "@types/react": "18.2.78", "@types/react-beautiful-dnd": "13.1.8", - "@types/react-calendar": "3.9.0", "@types/react-color": "3.0.12", "@types/react-dom": "18.2.25", "@types/react-highlight-words": "0.16.7", diff --git a/packages/grafana-ui/src/components/Alert/Alert.mdx b/packages/grafana-ui/src/components/Alert/Alert.mdx index 96c05104a45..16916f92069 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.mdx +++ b/packages/grafana-ui/src/components/Alert/Alert.mdx @@ -104,9 +104,7 @@ Inline Banners are task-oriented alerts that notify users of the status of an ac ```jsx - -
Child content that includes some alert details, like maybe what actually happened.
-
+ Child content that includes some alert details, like maybe what actually happened.
``` @@ -149,9 +147,7 @@ appEvents.publish({ ```jsx - -
Child content that includes some alert details, like maybe what actually happened.
-
+ Child content that includes some alert details, like maybe what actually happened.
``` diff --git a/packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx b/packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx index 1e13af573d6..baa8dd04d67 100644 --- a/packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx +++ b/packages/grafana-ui/src/components/Alert/InlineBanner.story.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import { StoryFn, Meta } from '@storybook/react'; import React from 'react'; -import { Alert, AlertVariant, VerticalGroup } from '@grafana/ui'; +import { Alert, AlertVariant, Stack } from '@grafana/ui'; import { StoryExample } from '../../utils/storybook/StoryExample'; @@ -29,11 +29,7 @@ const meta: Meta = { export const Basic: StoryFn = (args) => { return (
- - -
Child content that includes some alert details, like maybe what actually happened.
-
-
+ Child content that includes some alert details, like maybe what actually happened.
); }; @@ -44,13 +40,7 @@ Basic.args = { }; export const WithActions: StoryFn = (args) => { - return ( - - -
Child content that includes some alert details, like maybe what actually happened.
-
-
- ); + return Child content that includes some alert details, like maybe what actually happened.; }; WithActions.args = { @@ -62,7 +52,7 @@ WithActions.args = { export const Examples: StoryFn = () => { return ( - + = () => { - + {severities.map((severity) => ( Child content ))} - + - + ); }; diff --git a/packages/grafana-ui/src/components/Alert/Toast.story.tsx b/packages/grafana-ui/src/components/Alert/Toast.story.tsx index 1352fe33256..001ae0faa0e 100644 --- a/packages/grafana-ui/src/components/Alert/Toast.story.tsx +++ b/packages/grafana-ui/src/components/Alert/Toast.story.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import { StoryFn, Meta } from '@storybook/react'; import React from 'react'; -import { Alert, AlertVariant, VerticalGroup } from '@grafana/ui'; +import { Alert, AlertVariant, Stack } from '@grafana/ui'; import { StoryExample } from '../../utils/storybook/StoryExample'; @@ -33,9 +33,7 @@ export const Basic: StoryFn = (args) => { return (
- -
Child content that includes some alert details, like maybe what actually happened.
-
+ Child content that includes some alert details, like maybe what actually happened.
); @@ -43,9 +41,9 @@ export const Basic: StoryFn = (args) => { export function Examples() { return ( - + - + {severities.map((severity) => ( ))} - + @@ -124,7 +122,7 @@ export function Examples() { Vivamus sit amet dui semper, suscipit est nec, elementum arcu. Praesent ante turpis, convallis ac leo eget, - + ); } diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.mdx b/packages/grafana-ui/src/components/EmptyState/EmptyState.mdx index 846652136a9..1851d7c91c2 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.mdx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.mdx @@ -3,13 +3,60 @@ import { EmptyState } from './EmptyState'; # EmptyState -Use an empty state to communicate to the user that there is no data to display, or that a search query returned no results. +The EmptyState component consists of a message and optionally an image, button, and additional information. -## variant="not-found" +## When to use -### When to use +Use an empty state to communicate to the user that there is no data to display and provide instructions for what to do next. +Example use cases: -Use in place of a results table or list when a search query or filter returns no results. +- When a user has not created a particular resource yet +- When a filter or search query returns no results +- When a user completes all actions, such as clearing their inbox or notifications + +There are different variants to handle the most common empty state use cases. These variants provide a specific default message and image. + +## Usage + +### `variant="call-to-action"` + +Use when there is no data to display and you want to encourage the user to take an action. Usually either to complete some initial configuration, or to create an item. + +```jsx +import { EmptyState, LinkButton, TextLink } from '@grafana/ui'; + + + Create playlist + + } +> + You can use playlists to cycle dashboards on TVs without user control.{' '} + + Learn more. + +; +``` + +For scenarios where there is no single button that can be clicked to create the item, you can omit the `button` prop. Instead, provide additional information to help the user understand how to create the specific resource. + +```jsx +import { EmptyState, TextLink } from '@grafana/ui'; + + + Create a library panel from any existing dashboard panel through the panel context menu.{' '} + + Learn more. + +; +``` + +### `variant="not-found"` + +Use in place of content when a search query or filter returns no results. There are sensible defaults for the image, so in most cases all you need to provide is a message. @@ -19,14 +66,29 @@ import { EmptyState } from '@grafana/ui'; ; ``` -### Providing custom overrides +### `variant="completed"` -You can optionally override or hide the image, add additional information or a button (e.g. to clear the search query) +For when a user has completed all tasks on a page, such as reading all their notifications. ```jsx -import { Button, EmptyState } from '@grafana/ui'; +import { EmptyState } from '@grafana/ui'; + +; +``` + +## Customization + +For all variants you can: + +- provide a custom image or hide the image entirely +- provide a button (e.g. to provide a call to action or clear the search query) +- provide additional information via React children + +```jsx +import { Button, EmptyState, TextLink } from '@grafana/ui'; } image={} message="No playlists found" diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.story.tsx b/packages/grafana-ui/src/components/EmptyState/EmptyState.story.tsx index d4da9da7cff..9e81d2e234e 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.story.tsx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.story.tsx @@ -1,6 +1,8 @@ import { Meta, StoryFn } from '@storybook/react'; import React from 'react'; +import { Button } from '../Button'; + import { EmptyState } from './EmptyState'; import mdx from './EmptyState.mdx'; @@ -12,10 +14,14 @@ const meta: Meta = { page: mdx, }, controls: { - exclude: ['button', 'image', 'variant'], + exclude: ['image'], }, }, argTypes: { + button: { + control: 'select', + options: ['None', 'Create', 'Clear filters'], + }, children: { type: 'string', }, @@ -23,13 +29,24 @@ const meta: Meta = { }; export const Basic: StoryFn = (args) => { - return ; + let button; + if (args.button === 'Create') { + button = ( + + ); + } else if (args.button === 'Clear filters') { + button = ; + } + return ; }; Basic.args = { + button: 'Create', children: 'Use this space to add any additional information', - message: 'No results found', - variant: 'not-found', + message: "You haven't created any dashboards yet", + variant: 'call-to-action', }; export default meta; diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx index 4fa685e2d19..548a1a66477 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx @@ -1,10 +1,13 @@ import React, { ReactNode } from 'react'; +import SVG from 'react-inlinesvg'; import { Box } from '../Layout/Box/Box'; import { Stack } from '../Layout/Stack/Stack'; import { Text } from '../Text/Text'; +import { GrotCTA } from './GrotCTA/GrotCTA'; import { GrotNotFound } from './GrotNotFound/GrotNotFound'; +import GrotCompleted from './grot-completed.svg'; interface Props { /** @@ -21,26 +24,52 @@ interface Props { */ message: string; /** - * Empty state variant. Possible values are 'search'. + * Which variant to use. Affects the default image shown. */ - variant: 'not-found'; + variant: 'call-to-action' | 'not-found' | 'completed'; } export const EmptyState = ({ button, children, - image = , + image, message, hideImage = false, + variant, }: React.PropsWithChildren) => { + const imageToShow = image ?? getDefaultImageForVariant(variant); + return ( - {!hideImage && image} + {!hideImage && imageToShow} - {message} - {children && {children}} + + {message} + + {children && ( + + {children} + + )} {button} ); }; + +function getDefaultImageForVariant(variant: Props['variant']) { + switch (variant) { + case 'call-to-action': { + return ; + } + case 'not-found': { + return ; + } + case 'completed': { + return ; + } + default: { + throw new Error(`Unknown variant: ${variant}`); + } + } +} diff --git a/packages/grafana-ui/src/components/EmptyState/GrotCTA/GrotCTA.tsx b/packages/grafana-ui/src/components/EmptyState/GrotCTA/GrotCTA.tsx new file mode 100644 index 00000000000..ce7d363fb6f --- /dev/null +++ b/packages/grafana-ui/src/components/EmptyState/GrotCTA/GrotCTA.tsx @@ -0,0 +1,32 @@ +import { css } from '@emotion/css'; +import React, { SVGProps } from 'react'; +import SVG from 'react-inlinesvg'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../../themes'; + +import grotCTASvg from './grot-cta.svg'; + +export interface Props { + width?: SVGProps['width']; + height?: SVGProps['height']; +} + +export const GrotCTA = ({ width = 'auto', height }: Props) => { + const styles = useStyles2(getStyles); + + return ; +}; + +GrotCTA.displayName = 'GrotCTA'; + +const getStyles = (theme: GrafanaTheme2) => { + return { + svg: css({ + '#grot-cta-cactus-1, #grot-cta-cactus-2': { + fill: theme.isDark ? '#58558c' : '#c9c5f4', + }, + }), + }; +}; diff --git a/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg b/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg new file mode 100644 index 00000000000..7e44c92839c --- /dev/null +++ b/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/grafana-ui/src/components/EmptyState/grot-completed.svg b/packages/grafana-ui/src/components/EmptyState/grot-completed.svg new file mode 100644 index 00000000000..2205faa698d --- /dev/null +++ b/packages/grafana-ui/src/components/EmptyState/grot-completed.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx index 0084d82a550..07186be5e8c 100644 --- a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx +++ b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx @@ -4,6 +4,7 @@ import React, { CSSProperties } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes'; +import { handleReducedMotion } from '../../utils/handleReducedMotion'; export interface LoadingBarProps { width: number; @@ -52,10 +53,17 @@ const getStyles = (_theme: GrafanaTheme2, delay: number, duration: number) => { animationName: animation, // an initial delay to prevent the loader from showing if the response is faster than the delay animationDelay: `${delay}ms`, - animationDuration: `${duration}ms`, animationTimingFunction: 'linear', animationIterationCount: 'infinite', willChange: 'transform', + ...handleReducedMotion( + { + animationDuration: `${duration}ms`, + }, + { + animationDuration: `${4 * duration}ms`, + } + ), }), }; }; diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx index 6e050af4541..a048c384a25 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useToggle } from 'react-use'; import { LoadingState } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { PanelChrome, PanelChromeProps } from './PanelChrome'; @@ -152,16 +153,17 @@ it('collapses the controlled panel when user clicks on the chevron or the title' expect(screen.getByText("Panel's Content")).toBeInTheDocument(); - const button = screen.getByText('Default title'); + const button = screen.getByRole('button', { name: 'Default title' }); + const content = screen.getByTestId(selectors.components.Panels.Panel.content); // collapse button should have same aria-controls as the panel's content - expect(button.getAttribute('aria-controls')).toBe(button.parentElement?.parentElement?.nextElementSibling?.id); + expect(button.getAttribute('aria-controls')).toBe(content.id); fireEvent.click(button); expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument(); // aria-controls should be removed when panel is collapsed expect(button).not.toHaveAttribute('aria-controlls'); - expect(button.parentElement?.parentElement?.nextElementSibling?.id).toBe(undefined); + expect(screen.queryByTestId(selectors.components.Panels.Panel.content)?.id).toBe(undefined); }); it('collapses the uncontrolled panel when user clicks on the chevron or the title', () => { @@ -169,14 +171,15 @@ it('collapses the uncontrolled panel when user clicks on the chevron or the titl expect(screen.getByText("Panel's Content")).toBeInTheDocument(); - const button = screen.getByText('Default title'); + const button = screen.getByRole('button', { name: 'Default title' }); + const content = screen.getByTestId(selectors.components.Panels.Panel.content); + // collapse button should have same aria-controls as the panel's content - expect(button.getAttribute('aria-controls')).toBe(button.parentElement?.parentElement?.nextElementSibling?.id); + expect(button.getAttribute('aria-controls')).toBe(content.id); fireEvent.click(button); - expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument(); // aria-controls should be removed when panel is collapsed expect(button).not.toHaveAttribute('aria-controlls'); - expect(button.parentElement?.parentElement?.nextElementSibling?.id).toBe(undefined); + expect(screen.queryByTestId(selectors.components.Panels.Panel.content)?.id).toBe(undefined); }); diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 4f4d690a42d..e1ae34984b8 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -10,6 +10,7 @@ import { getFocusStyles } from '../../themes/mixins'; import { DelayRender } from '../../utils/DelayRender'; import { Icon } from '../Icon/Icon'; import { LoadingBar } from '../LoadingBar/LoadingBar'; +import { Text } from '../Text/Text'; import { Tooltip } from '../Tooltip'; import { HoverWidget } from './HoverWidget'; @@ -167,34 +168,40 @@ export function PanelChrome({ <> {/* Non collapsible title */} {!collapsible && title && ( -
- {title} -
+
+ + {title} + +
)} {/* Collapsible title */} {collapsible && ( -
- -
+
+ + + +
)}
@@ -229,7 +236,7 @@ export function PanelChrome({ return ( // tabIndex={0} is needed for keyboard accessibility in the plot area -
{typeof children === 'function' ? children(innerWidth, innerHeight) : children}
)} -
+ ); } @@ -424,13 +432,11 @@ const getStyles = (theme: GrafanaTheme2) => { title: css({ label: 'panel-title', display: 'flex', - marginBottom: 0, // override default h6 margin-bottom padding: theme.spacing(0, padding), - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - fontSize: theme.typography.h6.fontSize, - fontWeight: theme.typography.h6.fontWeight, + minWidth: 0, + '& > h2': { + minWidth: 0, + }, }), items: css({ display: 'flex', @@ -478,14 +484,9 @@ const getStyles = (theme: GrafanaTheme2) => { display: 'flex', gap: theme.spacing(0.5), background: 'transparent', - color: theme.colors.text.primary, border: 'none', padding: 0, - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - fontSize: theme.typography.h6.fontSize, - fontWeight: theme.typography.h6.fontWeight, + maxWidth: '100%', }), }; }; diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index 7420a8034e0..de754714c6e 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -63,10 +63,6 @@ export class RefreshPicker extends PureComponent { return 'primary'; } - if (this.props.isLoading) { - return 'destructive'; - } - if (this.props.primary) { return 'primary'; } diff --git a/packages/grafana-ui/src/components/Select/SelectMenu.tsx b/packages/grafana-ui/src/components/Select/SelectMenu.tsx index 5ed9d32c9b7..c6120d0a0d2 100644 --- a/packages/grafana-ui/src/components/Select/SelectMenu.tsx +++ b/packages/grafana-ui/src/components/Select/SelectMenu.tsx @@ -34,7 +34,8 @@ export const SelectMenu = ({ children, maxHeight, innerRef, innerProps }: React. SelectMenu.displayName = 'SelectMenu'; const VIRTUAL_LIST_ITEM_HEIGHT = 37; -const VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER = 7; +const VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER = 8; +const VIRTUAL_LIST_PADDING = 8; // A virtualized version of the SelectMenu, descriptions for SelectableValue options not supported since those are of a variable height. // @@ -57,7 +58,7 @@ export const VirtualizedSelectMenu = ({ children, maxHeight, options, getValue } } const longestOption = max(options.map((option) => option.label?.length)) ?? 0; - const widthEstimate = longestOption * VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER; + const widthEstimate = longestOption * VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER + VIRTUAL_LIST_PADDING * 2; const heightEstimate = Math.min(options.length * VIRTUAL_LIST_ITEM_HEIGHT, maxHeight); // Try to scroll to keep current value in the middle diff --git a/packages/grafana-ui/src/components/Table/FilterPopup.tsx b/packages/grafana-ui/src/components/Table/FilterPopup.tsx index fcb4803f9bc..a8f6b675a35 100644 --- a/packages/grafana-ui/src/components/Table/FilterPopup.tsx +++ b/packages/grafana-ui/src/components/Table/FilterPopup.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Field, GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { Button, ClickOutsideWrapper, HorizontalGroup, IconButton, Label, VerticalGroup } from '..'; +import { Button, ClickOutsideWrapper, IconButton, Label, Stack } from '..'; import { useStyles2, useTheme2 } from '../../themes'; import { FilterList } from './FilterList'; @@ -65,9 +65,9 @@ export const FilterPopup = ({ {/* This is just blocking click events from bubbeling and should not have a keyboard interaction. */} {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
- - - + + + !s); }} /> - +
- - - + + + - + {clearFilterVisible && ( - + - + )} - - + +
); diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx index 46861900ff0..df90a10bc44 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx @@ -1,11 +1,12 @@ import { css, cx } from '@emotion/css'; -import React, { useLayoutEffect, useRef, useReducer, CSSProperties } from 'react'; +import React, { useLayoutEffect, useRef, useReducer, CSSProperties, useContext } from 'react'; import { createPortal } from 'react-dom'; import uPlot from 'uplot'; import { GrafanaTheme2 } from '@grafana/data'; import { DashboardCursorSync } from '@grafana/schema'; +import { ModalsContext } from '../../../components/Modal/ModalsContext'; import { useStyles2 } from '../../../themes'; import { getPortalContainer } from '../../Portal/Portal'; import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder'; @@ -122,8 +123,10 @@ export const TooltipPlugin2 = ({ const sizeRef = useRef(); + const isWithinModal = useContext(ModalsContext).component !== null; + maxWidth = isPinned ? DEFAULT_TOOLTIP_WIDTH : maxWidth ?? DEFAULT_TOOLTIP_WIDTH; - const styles = useStyles2(getStyles, maxWidth); + const styles = useStyles2(getStyles, maxWidth, isWithinModal); const renderRef = useRef(render); renderRef.current = render; @@ -580,11 +583,11 @@ export const TooltipPlugin2 = ({ return null; }; -const getStyles = (theme: GrafanaTheme2, maxWidth?: number) => ({ +const getStyles = (theme: GrafanaTheme2, maxWidth?: number, isWithinModal?: boolean) => ({ tooltipWrapper: css({ top: 0, left: 0, - zIndex: theme.zIndex.tooltip, + zIndex: !isWithinModal ? theme.zIndex.tooltip : theme.zIndex.tooltipWithinModal, whiteSpace: 'pre', borderRadius: theme.shape.radius.default, position: 'fixed', diff --git a/packages/grafana-ui/src/options/builder/hideSeries.tsx b/packages/grafana-ui/src/options/builder/hideSeries.tsx index 90ae2deb6c5..6acb16ad363 100644 --- a/packages/grafana-ui/src/options/builder/hideSeries.tsx +++ b/packages/grafana-ui/src/options/builder/hideSeries.tsx @@ -5,7 +5,7 @@ import { FieldConfigEditorBuilder, StandardEditorProps } from '@grafana/data'; import { HideableFieldConfig, HideSeriesConfig } from '@grafana/schema'; import { FilterPill } from '../../components/FilterPill/FilterPill'; -import { HorizontalGroup } from '../../components/Layout/Layout'; +import { Stack } from '../../components/Layout/Stack/Stack'; const SeriesConfigEditor = ({ value, onChange }: StandardEditorProps) => { const onChangeToggle = useCallback( @@ -16,7 +16,7 @@ const SeriesConfigEditor = ({ value, onChange }: StandardEditorProps + {Object.keys(value).map((k) => { const key = k as keyof HideSeriesConfig; return ( @@ -29,7 +29,7 @@ const SeriesConfigEditor = ({ value, onChange }: StandardEditorProps ); })} - + ); }; diff --git a/packages/grafana-ui/src/options/builder/stacking.tsx b/packages/grafana-ui/src/options/builder/stacking.tsx index 5d561c824a2..b099157cbff 100644 --- a/packages/grafana-ui/src/options/builder/stacking.tsx +++ b/packages/grafana-ui/src/options/builder/stacking.tsx @@ -12,7 +12,7 @@ import { GraphFieldConfig, StackingConfig, StackingMode } from '@grafana/schema' import { RadioButtonGroup } from '../../components/Forms/RadioButtonGroup/RadioButtonGroup'; import { IconButton } from '../../components/IconButton/IconButton'; import { Input } from '../../components/Input/Input'; -import { HorizontalGroup } from '../../components/Layout/Layout'; +import { Stack } from '../../components/Layout/Stack/Stack'; import { graphFieldOptions } from '../../components/uPlot/config'; export const StackingEditor = ({ @@ -22,7 +22,7 @@ export const StackingEditor = ({ item, }: StandardEditorProps> }>) => { return ( - + )} - + ); }; diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 53b733bcf74..3908900f8e6 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "strconv" - "strings" "golang.org/x/sync/errgroup" @@ -19,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -46,9 +44,6 @@ func (hs *HTTPServer) AdminCreateUser(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusBadRequest, "bad request data", err) } - form.Email = strings.TrimSpace(form.Email) - form.Login = strings.TrimSpace(form.Login) - cmd := user.CreateUserCommand{ Login: form.Login, Email: form.Email, @@ -57,10 +52,6 @@ func (hs *HTTPServer) AdminCreateUser(c *contextmodel.ReqContext) response.Respo OrgID: form.OrgId, } - if len(cmd.Password) < 4 { - return response.Error(http.StatusBadRequest, "Password is missing or too short", nil) - } - usr, err := hs.userService.Create(c.Req.Context(), &cmd) if err != nil { if errors.Is(err, org.ErrOrgNotFound) { @@ -115,39 +106,19 @@ func (hs *HTTPServer) AdminUpdateUserPassword(c *contextmodel.ReqContext) respon return response.Error(http.StatusBadRequest, "id is invalid", err) } - if err := form.Password.Validate(hs.Cfg); err != nil { - return response.Err(err) + if response := hs.errOnExternalUser(c.Req.Context(), userID); response != nil { + return response } - userQuery := user.GetUserByIDQuery{ID: userID} + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, Password: &form.Password}); err != nil { + return response.Error(http.StatusInternalServerError, "Failed to update user password", err) + } - usr, err := hs.userService.GetByID(c.Req.Context(), &userQuery) + usr, err := hs.userService.GetByID(c.Req.Context(), &user.GetUserByIDQuery{ID: userID}) if err != nil { return response.Error(http.StatusInternalServerError, "Could not read user from database", err) } - getAuthQuery := login.GetAuthInfoQuery{UserId: usr.ID} - if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil { - oauthInfo := hs.SocialService.GetOAuthInfoProvider(authInfo.AuthModule) - if login.IsProviderEnabled(hs.Cfg, authInfo.AuthModule, oauthInfo) { - return response.Error(http.StatusBadRequest, "Cannot update external user password", err) - } - } - - passwordHashed, err := util.EncodePassword(string(form.Password), usr.Salt) - if err != nil { - return response.Error(http.StatusInternalServerError, "Could not encode password", err) - } - - cmd := user.ChangeUserPasswordCommand{ - UserID: userID, - NewPassword: user.Password(passwordHashed), - } - - if err := hs.userService.ChangePassword(c.Req.Context(), &cmd); err != nil { - return response.Error(http.StatusInternalServerError, "Failed to update user password", err) - } - if err := hs.loginAttemptService.Reset(c.Req.Context(), usr.Login); err != nil { c.Logger.Warn("could not reset login attempts", "err", err, "username", usr.Login) @@ -185,15 +156,17 @@ func (hs *HTTPServer) AdminUpdateUserPermissions(c *contextmodel.ReqContext) res return response.Error(http.StatusBadRequest, "id is invalid", err) } - getAuthQuery := login.GetAuthInfoQuery{UserId: userID} - if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil && authInfo != nil { + if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &login.GetAuthInfoQuery{UserId: userID}); err == nil && authInfo != nil { oauthInfo := hs.SocialService.GetOAuthInfoProvider(authInfo.AuthModule) if login.IsGrafanaAdminExternallySynced(hs.Cfg, oauthInfo, authInfo.AuthModule) { return response.Error(http.StatusForbidden, "Cannot change Grafana Admin role for externally synced user", nil) } } - err = hs.userService.UpdatePermissions(c.Req.Context(), userID, form.IsGrafanaAdmin) + err = hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{ + UserID: userID, + IsGrafanaAdmin: &form.IsGrafanaAdmin, + }) if err != nil { if errors.Is(err, user.ErrLastGrafanaAdmin) { return response.Error(http.StatusBadRequest, user.ErrLastGrafanaAdmin.Error(), nil) @@ -318,8 +291,8 @@ func (hs *HTTPServer) AdminDisableUser(c *contextmodel.ReqContext) response.Resp return response.Error(http.StatusInternalServerError, "Could not disable external user", nil) } - disableCmd := user.DisableUserCommand{UserID: userID, IsDisabled: true} - if err := hs.userService.Disable(c.Req.Context(), &disableCmd); err != nil { + isDisabled := true + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, IsDisabled: &isDisabled}); err != nil { if errors.Is(err, user.ErrUserNotFound) { return response.Error(http.StatusNotFound, user.ErrUserNotFound.Error(), nil) } @@ -361,8 +334,8 @@ func (hs *HTTPServer) AdminEnableUser(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusInternalServerError, "Could not enable external user", nil) } - disableCmd := user.DisableUserCommand{UserID: userID, IsDisabled: false} - if err := hs.userService.Disable(c.Req.Context(), &disableCmd); err != nil { + isDisabled := true + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, IsDisabled: &isDisabled}); err != nil { if errors.Is(err, user.ErrUserNotFound) { return response.Error(http.StatusNotFound, user.ErrUserNotFound.Error(), nil) } diff --git a/pkg/api/admin_users_test.go b/pkg/api/admin_users_test.go index d40417f72f3..0b6011d95e2 100644 --- a/pkg/api/admin_users_test.go +++ b/pkg/api/admin_users_test.go @@ -355,8 +355,9 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string hs := &HTTPServer{ Cfg: setting.NewCfg(), SQLStore: sqlStore, - authInfoService: &authinfotest.FakeService{}, + authInfoService: &authinfotest.FakeService{ExpectedError: user.ErrUserNotFound}, userService: userSvc, + SocialService: &mockSocialService{}, } sc := setupScenarioContext(t, url) diff --git a/pkg/api/api.go b/pkg/api/api.go index dee4f01541e..235af6462cb 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -572,18 +572,18 @@ func (hs *HTTPServer) registerRoutes() { r.Group("/api/admin/users", func(adminUserRoute routing.RouteRegister) { userIDScope := ac.Scope("global.users", "id", ac.Parameter(":id")) - adminUserRoute.Post("/", authorize(ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.AdminCreateUser)) - adminUserRoute.Put("/:id/password", authorize(ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPassword)) - adminUserRoute.Put("/:id/permissions", authorize(ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPermissions)) - adminUserRoute.Delete("/:id", authorize(ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(hs.AdminDeleteUser)) - adminUserRoute.Post("/:id/disable", authorize(ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser)) - adminUserRoute.Post("/:id/enable", authorize(ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(hs.AdminEnableUser)) - adminUserRoute.Get("/:id/quotas", authorize(ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(hs.GetUserQuotas)) - adminUserRoute.Put("/:id/quotas/:target", authorize(ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(hs.UpdateUserQuota)) + adminUserRoute.Post("/", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.AdminCreateUser)) + adminUserRoute.Put("/:id/password", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPassword)) + adminUserRoute.Put("/:id/permissions", reqGrafanaAdmin, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPermissions)) + adminUserRoute.Delete("/:id", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(hs.AdminDeleteUser)) + adminUserRoute.Post("/:id/disable", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser)) + adminUserRoute.Post("/:id/enable", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(hs.AdminEnableUser)) + adminUserRoute.Get("/:id/quotas", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(hs.GetUserQuotas)) + adminUserRoute.Put("/:id/quotas/:target", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(hs.UpdateUserQuota)) - adminUserRoute.Post("/:id/logout", authorize(ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser)) - adminUserRoute.Get("/:id/auth-tokens", authorize(ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens)) - adminUserRoute.Post("/:id/revoke-auth-token", authorize(ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken)) + adminUserRoute.Post("/:id/logout", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser)) + adminUserRoute.Get("/:id/auth-tokens", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens)) + adminUserRoute.Post("/:id/revoke-auth-token", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken)) }, reqSignedIn) // rendering diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index df798af8ca8..010ca3cb8e2 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -50,6 +51,7 @@ func (hs *HTTPServer) getDSQueryEndpoint() web.Handler { return } r.URL.Path = "/apis/query.grafana.app/v0alpha1/namespaces/" + namespaceMapper(user.OrgID) + "/query" + r.Header.Add(proxyutil.IDHeaderName, user.GetIDToken()) hs.clientConfigProvider.DirectlyServeHTTP(w, r) } } diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index a2f08b9607b..51054a22ada 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -270,10 +270,6 @@ func (hs *HTTPServer) CompleteInvite(c *contextmodel.ReqContext) response.Respon } } - if err := completeInvite.Password.Validate(hs.Cfg); err != nil { - return response.Err(err) - } - cmd := user.CreateUserCommand{ Email: completeInvite.Email, Name: completeInvite.Name, diff --git a/pkg/api/password.go b/pkg/api/password.go index f3b265029e4..fb6db45d939 100644 --- a/pkg/api/password.go +++ b/pkg/api/password.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -65,6 +64,11 @@ func (hs *HTTPServer) ResetPassword(c *contextmodel.ReqContext) response.Respons if err := web.Bind(c.Req, &form); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } + + if form.NewPassword != form.ConfirmPassword { + return response.Error(http.StatusBadRequest, "Passwords do not match", nil) + } + query := notifications.ValidateResetPasswordCodeQuery{Code: form.Code} // For now the only way to know the username to clear login attempts for is @@ -85,33 +89,12 @@ func (hs *HTTPServer) ResetPassword(c *contextmodel.ReqContext) response.Respons return response.Error(http.StatusInternalServerError, "Unknown error validating email code", err) } - getAuthQuery := login.GetAuthInfoQuery{UserId: userResult.ID} - if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil { - oauthInfo := hs.SocialService.GetOAuthInfoProvider(authInfo.AuthModule) - if login.IsProviderEnabled(hs.Cfg, authInfo.AuthModule, oauthInfo) { - return response.Error(http.StatusBadRequest, "Cannot update external user password", err) - } + if response := hs.errOnExternalUser(c.Req.Context(), userResult.ID); response != nil { + return response } - if form.NewPassword != form.ConfirmPassword { - return response.Error(http.StatusBadRequest, "Passwords do not match", nil) - } - - if err := form.NewPassword.Validate(hs.Cfg); err != nil { - c.Logger.Warn("the new password doesn't meet the password policy criteria", "err", err) - return response.Err(err) - } - - cmd := user.ChangeUserPasswordCommand{} - cmd.UserID = userResult.ID - encodedPassword, err := util.EncodePassword(string(form.NewPassword), userResult.Salt) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to encode password", err) - } - cmd.NewPassword = user.Password(encodedPassword) - - if err := hs.userService.ChangePassword(c.Req.Context(), &cmd); err != nil { - return response.Error(http.StatusInternalServerError, "Failed to change user password", err) + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userResult.ID, Password: &form.ConfirmPassword}); err != nil { + return response.ErrOrFallback(http.StatusInternalServerError, "Failed to change user password", err) } if err := hs.loginAttemptService.Reset(c.Req.Context(), username); err != nil { diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index d7bc8f1fea2..ffe15c59357 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -173,6 +173,11 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.Response { pluginID := web.Params(c.Req)[":pluginId"] + perr := hs.pluginErrorResolver.PluginError(c.Req.Context(), pluginID) + if perr != nil { + return response.Error(http.StatusInternalServerError, perr.PublicMessage(), perr) + } + plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) if !exists { return response.Error(http.StatusNotFound, "Plugin not found, no installed plugin with that id", nil) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 4731b2fb032..a948a261013 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -38,6 +38,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/updatechecker" @@ -764,3 +765,88 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) { }) } } + +func Test_PluginsSettings(t *testing.T) { + pID := "test-datasource" + p1 := createPlugin(plugins.JSONData{ + ID: pID, Type: "datasource", Name: pID, + Info: plugins.Info{ + Version: "1.0.0", + }}, plugins.ClassExternal, plugins.NewFakeFS()) + + pluginRegistry := &fakes.FakePluginRegistry{ + Store: map[string]*plugins.Plugin{ + p1.ID: p1, + }, + } + + pluginSettings := pluginsettings.FakePluginSettings{Plugins: map[string]*pluginsettings.DTO{ + pID: {ID: 0, OrgID: 1, PluginID: pID, PluginVersion: "1.0.0"}, + }} + + type testCase struct { + desc string + expectedCode int + errCode plugins.ErrorCode + expectedSettings dtos.PluginSetting + expectedError string + } + tcs := []testCase{ + { + desc: "should only be able to get plugin settings", + expectedCode: http.StatusOK, + expectedSettings: dtos.PluginSetting{ + Id: pID, + Name: pID, + Type: "datasource", + Info: plugins.Info{ + Version: "1.0.0", + }, + SecureJsonFields: map[string]bool{}, + }, + }, + { + desc: "should return a plugin error", + expectedCode: http.StatusInternalServerError, + errCode: plugins.ErrorCodeFailedBackendStart, + expectedError: "Plugin failed to start", + }, + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + server := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.Cfg = setting.NewCfg() + hs.PluginSettings = &pluginSettings + hs.pluginStore = pluginstore.New(pluginRegistry, &fakes.FakeLoader{}) + hs.pluginFileStore = filestore.ProvideService(pluginRegistry) + errTracker := pluginerrs.ProvideErrorTracker() + if tc.errCode != "" { + errTracker.Record(context.Background(), &plugins.Error{ + PluginID: pID, + ErrorCode: tc.errCode, + }) + } + hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker) + var err error + hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest()) + require.NoError(t, err) + }) + + res, err := server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest("/api/plugins/"+pID+"/settings"), userWithPermissions(1, []ac.Permission{}))) + require.NoError(t, err) + assert.Equal(t, tc.expectedCode, res.StatusCode) + if tc.expectedCode == http.StatusOK { + var result dtos.PluginSetting + require.NoError(t, json.NewDecoder(res.Body).Decode(&result)) + require.NoError(t, res.Body.Close()) + assert.Equal(t, tc.expectedSettings, result) + } else { + var respJson map[string]any + err := json.NewDecoder(res.Body).Decode(&respJson) + require.NoError(t, err) + require.Equal(t, tc.expectedError, respJson["message"]) + } + }) + } +} diff --git a/pkg/api/signup.go b/pkg/api/signup.go index 935817aaed7..3309a028e28 100644 --- a/pkg/api/signup.go +++ b/pkg/api/signup.go @@ -4,7 +4,6 @@ import ( "context" "errors" "net/http" - "strings" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -89,9 +88,6 @@ func (hs *HTTPServer) SignUpStep2(c *contextmodel.ReqContext) response.Response return response.Error(http.StatusUnauthorized, "User signup is disabled", nil) } - form.Email = strings.TrimSpace(form.Email) - form.Username = strings.TrimSpace(form.Username) - createUserCmd := user.CreateUserCommand{ Email: form.Email, Login: form.Username, diff --git a/pkg/api/user.go b/pkg/api/user.go index e529497546f..f774d8ae133 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -226,13 +226,8 @@ func (hs *HTTPServer) UpdateUserActiveOrg(c *contextmodel.ReqContext) response.R func (hs *HTTPServer) handleUpdateUser(ctx context.Context, cmd user.UpdateUserCommand) response.Response { // external user -> user data cannot be updated - isExternal, err := hs.isExternalUser(ctx, cmd.UserID) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to validate User", err) - } - - if isExternal { - return response.Error(http.StatusForbidden, "User info cannot be updated for external Users", nil) + if response := hs.errOnExternalUser(ctx, cmd.UserID); response != nil { + return response } if len(cmd.Login) == 0 { @@ -344,20 +339,6 @@ func (hs *HTTPServer) UpdateUserEmail(c *contextmodel.ReqContext) response.Respo return response.Redirect(hs.Cfg.AppSubURL + "/profile") } -func (hs *HTTPServer) isExternalUser(ctx context.Context, userID int64) (bool, error) { - getAuthQuery := login.GetAuthInfoQuery{UserId: userID} - var err error - if _, err = hs.authInfoService.GetAuthInfo(ctx, &getAuthQuery); err == nil { - return true, nil - } - - if errors.Is(err, user.ErrUserNotFound) { - return false, nil - } - - return false, err -} - // swagger:route GET /user/orgs signed_in_user getSignedInUserOrgList // // Organizations of the actual User. @@ -571,8 +552,8 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) ChangeUserPassword(c *contextmodel.ReqContext) response.Response { - cmd := user.ChangeUserPasswordCommand{} - if err := web.Bind(c.Req, &cmd); err != nil { + form := user.ChangeUserPasswordCommand{} + if err := web.Bind(c.Req, &form); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } @@ -581,43 +562,12 @@ func (hs *HTTPServer) ChangeUserPassword(c *contextmodel.ReqContext) response.Re return errResponse } - userQuery := user.GetUserByIDQuery{ID: userID} - - usr, err := hs.userService.GetByID(c.Req.Context(), &userQuery) - if err != nil { - return response.Error(http.StatusInternalServerError, "Could not read user from database", err) + if response := hs.errOnExternalUser(c.Req.Context(), userID); response != nil { + return response } - getAuthQuery := login.GetAuthInfoQuery{UserId: usr.ID} - if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil { - oauthInfo := hs.SocialService.GetOAuthInfoProvider(authInfo.AuthModule) - if login.IsProviderEnabled(hs.Cfg, authInfo.AuthModule, oauthInfo) { - return response.Error(http.StatusBadRequest, "Cannot update external user password", err) - } - } - - passwordHashed, err := util.EncodePassword(string(cmd.OldPassword), usr.Salt) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to encode password", err) - } - if user.Password(passwordHashed) != usr.Password { - return response.Error(http.StatusUnauthorized, "Invalid old password", nil) - } - - if err := cmd.NewPassword.Validate(hs.Cfg); err != nil { - c.Logger.Warn("the new password doesn't meet the password policy criteria", "err", err) - return response.Err(err) - } - - cmd.UserID = userID - encodedPassword, err := util.EncodePassword(string(cmd.NewPassword), usr.Salt) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to encode password", err) - } - cmd.NewPassword = user.Password(encodedPassword) - - if err := hs.userService.ChangePassword(c.Req.Context(), &cmd); err != nil { - return response.Error(http.StatusInternalServerError, "Failed to change user password", err) + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, Password: &form.NewPassword, OldPassword: &form.OldPassword}); err != nil { + return response.ErrOrFallback(http.StatusInternalServerError, "Failed to change user password", err) } return response.Success("User password changed") diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index e66cbd5209e..55bd2dc61d3 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -345,12 +345,14 @@ func Test_GetUserByID(t *testing.T) { func TestHTTPServer_UpdateUser(t *testing.T) { settings := setting.NewCfg() + settings.SAMLAuthEnabled = true sqlStore := db.InitTestDB(t) hs := &HTTPServer{ Cfg: settings, SQLStore: sqlStore, AccessControl: acmock.New(), + SocialService: &socialtest.FakeSocialService{ExpectedAuthInfoProvider: &social.OAuthInfo{Enabled: true}}, } updateUserCommand := user.UpdateUserCommand{ @@ -366,7 +368,8 @@ func TestHTTPServer_UpdateUser(t *testing.T) { routePattern: "/api/users/:id", cmd: updateUserCommand, fn: func(sc *scenarioContext) { - sc.authInfoService.ExpectedUserAuth = &login.UserAuth{} + sc.authInfoService.ExpectedUserAuth = &login.UserAuth{AuthModule: login.SAMLAuthModule} + sc.fakeReqWithParams("PUT", sc.url, map[string]string{"id": "1"}).exec() assert.Equal(t, 403, sc.resp.Code) }, @@ -1086,11 +1089,13 @@ func updateUserScenario(t *testing.T, ctx updateUserContext, hs *HTTPServer) { func TestHTTPServer_UpdateSignedInUser(t *testing.T) { settings := setting.NewCfg() sqlStore := db.InitTestDB(t) + settings.SAMLAuthEnabled = true hs := &HTTPServer{ Cfg: settings, SQLStore: sqlStore, AccessControl: acmock.New(), + SocialService: &socialtest.FakeSocialService{}, } updateUserCommand := user.UpdateUserCommand{ @@ -1106,7 +1111,7 @@ func TestHTTPServer_UpdateSignedInUser(t *testing.T) { routePattern: "/api/users/", cmd: updateUserCommand, fn: func(sc *scenarioContext) { - sc.authInfoService.ExpectedUserAuth = &login.UserAuth{} + sc.authInfoService.ExpectedUserAuth = &login.UserAuth{AuthModule: login.SAMLAuthModule} sc.fakeReqWithParams("PUT", sc.url, map[string]string{"id": "1"}).exec() assert.Equal(t, 403, sc.resp.Code) }, diff --git a/pkg/api/utils.go b/pkg/api/utils.go index 08a1c525393..dae4c2da137 100644 --- a/pkg/api/utils.go +++ b/pkg/api/utils.go @@ -1,10 +1,16 @@ package api import ( + "context" + "errors" + "net/http" "net/mail" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/middleware/cookies" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/user" ) func (hs *HTTPServer) GetRedirectURL(c *contextmodel.ReqContext) string { @@ -20,6 +26,31 @@ func (hs *HTTPServer) GetRedirectURL(c *contextmodel.ReqContext) string { return redirectURL } +func (hs *HTTPServer) errOnExternalUser(ctx context.Context, userID int64) response.Response { + isExternal, err := hs.isExternalUser(ctx, userID) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to validate User", err) + } + if isExternal { + return response.Error(http.StatusForbidden, "Cannot update external User", nil) + } + return nil +} + +func (hs *HTTPServer) isExternalUser(ctx context.Context, userID int64) (bool, error) { + info, err := hs.authInfoService.GetAuthInfo(ctx, &login.GetAuthInfoQuery{UserId: userID}) + + if errors.Is(err, user.ErrUserNotFound) { + return false, nil + } + + if err != nil { + return true, err + } + + return login.IsProviderEnabled(hs.Cfg, info.AuthModule, hs.SocialService.GetOAuthInfoProvider(info.AuthModule)), nil +} + func ValidateAndNormalizeEmail(email string) (string, error) { if email == "" { return "", nil diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 9c4ce89bd0f..c2e41f53e24 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -15,7 +15,7 @@ require ( github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index c8ec4e9cce8..c82e0a06cc0 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -14,12 +14,9 @@ github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZC github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -85,8 +82,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 173065fae06..1fbc37c28f5 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,10 +5,9 @@ go 1.21.0 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.222.0 + github.com/grafana/grafana-plugin-sdk-go v0.224.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 - github.com/zeebo/assert v1.3.0 golang.org/x/mod v0.14.0 k8s.io/apimachinery v0.29.2 k8s.io/apiserver v0.29.2 @@ -50,7 +49,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/cel-go v0.17.7 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect @@ -101,6 +100,7 @@ require ( github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect @@ -136,10 +136,10 @@ require ( golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.1 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index def3107bb45..0efc1c878d3 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -105,8 +105,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= @@ -134,8 +133,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.222.0 h1:bWK35de9Kl9+lRGyh+41zW2X8MkQ4959aqUsi08/8ig= -github.com/grafana/grafana-plugin-sdk-go v0.222.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= +github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -279,6 +277,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -467,20 +466,16 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= diff --git a/pkg/apiserver/rest/dualwriter.go b/pkg/apiserver/rest/dualwriter.go index 4d1dff0c35a..f8ce6bc13b8 100644 --- a/pkg/apiserver/rest/dualwriter.go +++ b/pkg/apiserver/rest/dualwriter.go @@ -69,6 +69,8 @@ type DualWriter struct { var errDualWriterCreaterMissing = errors.New("legacy storage rest.Creater is missing") var errDualWriterListerMissing = errors.New("legacy storage rest.Lister is missing") var errDualWriterDeleterMissing = errors.New("legacy storage rest.GracefulDeleter is missing") +var errDualWriterCollectionDeleterMissing = errors.New("legacy storage rest.CollectionDeleter is missing") +var errDualWriterUpdaterMissing = errors.New("legacy storage rest.Updater is missing") type DualWriterMode int diff --git a/pkg/apiserver/rest/dualwriter_mode1.go b/pkg/apiserver/rest/dualwriter_mode1.go index a36a01c8139..80f169cd085 100644 --- a/pkg/apiserver/rest/dualwriter_mode1.go +++ b/pkg/apiserver/rest/dualwriter_mode1.go @@ -54,3 +54,23 @@ func (d *DualWriterMode1) Delete(ctx context.Context, name string, deleteValidat return legacy.Delete(ctx, name, deleteValidation, options) } + +// DeleteCollection overrides the behavior of the generic DualWriter and deletes only from LegacyStorage. +func (d *DualWriterMode1) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing + } + + return legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) +} + +func (d *DualWriterMode1) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + legacy, ok := d.Legacy.(rest.Updater) + if !ok { + klog.FromContext(ctx).Error(errDualWriterUpdaterMissing, "legacy storage rest.Updater is missing") + return nil, false, errDualWriterUpdaterMissing + } + + return legacy.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) +} diff --git a/pkg/apiserver/rest/dualwriter_mode1_test.go b/pkg/apiserver/rest/dualwriter_mode1_test.go index cca3db15f36..a71557b0848 100644 --- a/pkg/apiserver/rest/dualwriter_mode1_test.go +++ b/pkg/apiserver/rest/dualwriter_mode1_test.go @@ -2,46 +2,364 @@ package rest import ( "context" + "errors" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/apis/example" ) const kind = "dummy" -func TestMode1(t *testing.T) { - var ls = (LegacyStorage)(nil) - var s = (Storage)(nil) - lsSpy := NewLegacyStorageSpyClient(ls) - sSpy := NewStorageSpyClient(s) +var exampleObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1"}, Spec: example.PodSpec{}, Status: example.PodStatus{}} +var exampleObjDifferentRV = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "3"}, Spec: example.PodSpec{}, Status: example.PodStatus{}} +var anotherObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{}} +var failingObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "object-fail", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{}} - dw := NewDualWriterMode1(lsSpy, sSpy) +func TestMode1_Create(t *testing.T) { + type testCase struct { + name string + input runtime.Object + setupLegacyFn func(m *mock.Mock, input runtime.Object) + setupStorageFn func(m *mock.Mock, input runtime.Object) + wantErr bool + } + tests := + []testCase{ + { + name: "creating an object only in the legacy store", + input: exampleObj, + setupLegacyFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), anotherObj, mock.Anything, mock.Anything).Return(anotherObj, nil) + }, + }, + { + name: "error when creating object in the legacy store fails", + input: failingObj, + setupLegacyFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), failingObj, mock.Anything, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } - // Create: it should use the Legacy Create implementation - _, err := dw.Create(context.Background(), &dummyObject{}, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Create")) - assert.Equal(t, 0, sSpy.Counts("Storage.Create")) + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} - // Get: it should use the Legacy Get implementation - _, err = dw.Get(context.Background(), kind, &metav1.GetOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Get")) - assert.Equal(t, 0, sSpy.Counts("Storage.Get")) + ls := legacyStoreMock{m, l} + us := storageMock{m, s} - // List: it should use the Legacy List implementation - _, err = dw.List(context.Background(), &metainternalversion.ListOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.List")) - assert.Equal(t, 0, sSpy.Counts("Storage.List")) + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } - // Delete: it should use the Legacy Delete implementation - var deleteValidation = func(ctx context.Context, obj runtime.Object) error { return nil } - _, _, err = dw.Delete(context.Background(), kind, deleteValidation, &metav1.DeleteOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) - assert.Equal(t, 0, sSpy.Counts("Storage.Delete")) + dw := SelectDualWriter(Mode1, ls, us) + + obj, err := dw.Create(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + us.AssertNotCalled(t, "Create", context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode1_Get(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, name string) + setupStorageFn func(m *mock.Mock, name string) + wantErr bool + } + tests := + []testCase{ + { + name: "get an object only in the legacy store", + input: "foo", + setupLegacyFn: func(m *mock.Mock, name string) { + m.On("Get", context.Background(), name, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Get", context.Background(), name, mock.Anything).Return(anotherObj, nil) + }, + }, + { + name: "error when getting an object in the legacy store fails", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, name string) { + m.On("Get", context.Background(), name, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode1, ls, us) + + obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + us.AssertNotCalled(t, "Get", context.Background(), tt.name, &metav1.GetOptions{}) + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode1_List(t *testing.T) { + type testCase struct { + name string + setupLegacyFn func(m *mock.Mock) + setupStorageFn func(m *mock.Mock) + wantErr bool + } + tests := + []testCase{ + { + name: "error when listing an object in the legacy store is not implemented", + setupLegacyFn: func(m *mock.Mock) { + m.On("List", context.Background(), mock.Anything).Return(nil, errors.New("error")) + }, + }, + // TODO: legacy list is missing + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m) + } + + dw := SelectDualWriter(Mode1, ls, us) + + _, err := dw.List(context.Background(), &metainternalversion.ListOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + us.AssertNotCalled(t, "List", context.Background(), &metainternalversion.ListOptions{}) + } +} + +func TestMode1_Delete(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, name string) + setupStorageFn func(m *mock.Mock, name string) + wantErr bool + } + tests := + []testCase{ + { + name: "deleting an object in the legacy store", + input: "foo", + setupLegacyFn: func(m *mock.Mock, name string) { + m.On("Delete", context.Background(), name, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + }, + { + name: "error when deleting an object in the legacy store", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, name string) { + m.On("Delete", context.Background(), name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode1, ls, us) + + obj, _, err := dw.Delete(context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + us.AssertNotCalled(t, "Delete", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{}) + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode1_DeleteCollection(t *testing.T) { + type testCase struct { + name string + input *metav1.DeleteOptions + setupLegacyFn func(m *mock.Mock, input *metav1.DeleteOptions) + setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions) + wantErr bool + } + tests := + []testCase{ + { + name: "deleting a collection in the legacy store", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, + setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error deleting a collection in the legacy store", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, + setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode1, ls, us) + + obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, tt.input, &metainternalversion.ListOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + us.AssertNotCalled(t, "DeleteCollection", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{}) + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode1_Update(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, input string) + setupStorageFn func(m *mock.Mock, input string) + wantErr bool + } + tests := + []testCase{ + { + name: "update an object in legacy", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil) + }, + }, + { + name: "error updating an object in legacy", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode1, ls, us) + + obj, _, err := dw.Update(context.Background(), tt.input, UpdatedObjInfoObj{}, func(ctx context.Context, obj runtime.Object) error { return nil }, func(ctx context.Context, obj, old runtime.Object) error { return nil }, false, &metav1.UpdateOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } } diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index 85e6ac551ea..39a27fe40d1 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -35,21 +35,23 @@ func (d *DualWriterMode2) Create(ctx context.Context, obj runtime.Object, create return created, err } - c, err := enrichObject(obj, created) + accessorCreated, err := meta.Accessor(created) if err != nil { return created, err } - accessor, err := meta.Accessor(c) + accessorOld, err := meta.Accessor(obj) if err != nil { return created, err } + enrichObject(accessorOld, accessorCreated) + // create method expects an empty resource version - accessor.SetResourceVersion("") - accessor.SetUID("") + accessorCreated.SetResourceVersion("") + accessorCreated.SetUID("") - rsp, err := d.Storage.Create(ctx, c, createValidation, options) + rsp, err := d.Storage.Create(ctx, created, createValidation, options) if err != nil { klog.FromContext(ctx).Error(err, "unable to create object in Storage", "mode", 2) } @@ -60,16 +62,15 @@ func (d *DualWriterMode2) Create(ctx context.Context, obj runtime.Object, create // It retrieves an object from Storage if possible, and if not it falls back to LegacyStorage. func (d *DualWriterMode2) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { s, err := d.Storage.Get(ctx, name, &metav1.GetOptions{}) - if err == nil { - return s, err + if err != nil { + if apierrors.IsNotFound(err) { + klog.Info("object not found in storage", "name", name) + return d.Legacy.Get(ctx, name, &metav1.GetOptions{}) + } + klog.Error("unable to fetch object from storage", "error", err, "name", name) + return d.Legacy.Get(ctx, name, &metav1.GetOptions{}) } - if apierrors.IsNotFound(err) { - klog.Info("object not found in duplicate storage", "name", name) - } else { - klog.Error("unable to fetch object from duplicate storage", "error", err, "name", name) - } - - return d.Legacy.Get(ctx, name, &metav1.GetOptions{}) + return s, nil } // List overrides the behavior of the generic DualWriter. @@ -124,16 +125,28 @@ func (d *DualWriterMode2) List(ctx context.Context, options *metainternalversion return ll, nil } -func enrichObject(orig, copy runtime.Object) (runtime.Object, error) { - accessorC, err := meta.Accessor(copy) - if err != nil { - return nil, err - } - accessorO, err := meta.Accessor(orig) - if err != nil { - return nil, err +// DeleteCollection overrides the behavior of the generic DualWriter and deletes from both LegacyStorage and Storage. +func (d *DualWriterMode2) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing } + // #TODO: figure out how to handle partial deletions + deleted, err := legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) + if err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from legacy storage", "deletedObjects", deleted) + } + + res, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) + if err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from Storage", "deletedObjects", deleted) + } + + return res, err +} + +func enrichObject(accessorO, accessorC metav1.Object) { accessorC.SetLabels(accessorO.GetLabels()) ac := accessorC.GetAnnotations() @@ -141,8 +154,6 @@ func enrichObject(orig, copy runtime.Object) (runtime.Object, error) { ac[k] = v } accessorC.SetAnnotations(ac) - - return copy, nil } func (d *DualWriterMode2) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { @@ -168,3 +179,65 @@ func (d *DualWriterMode2) Delete(ctx context.Context, name string, deleteValidat return deletedLS, async, err } + +// Update overrides the generic behavior of the Storage and writes first to the legacy storage and then to storage. +func (d *DualWriterMode2) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + legacy, ok := d.Legacy.(rest.Updater) + if !ok { + return nil, false, errDualWriterUpdaterMissing + } + + var notFound bool + + // get old and new (updated) object so they can be stored in legacy store + old, err := d.Storage.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + klog.FromContext(ctx).Error(err, "could not get object", "mode", Mode2) + return nil, false, err + } + klog.FromContext(ctx).Error(err, "object not found for update, creating one", "mode", Mode2) + notFound = true + } + + // obj can be populated in case it's found or empty in case it's not found + updated, err := objInfo.UpdatedObject(ctx, old) + if err != nil { + return nil, false, err + } + + obj, created, err := legacy.Update(ctx, name, &updateWrapper{upstream: objInfo, updated: updated}, createValidation, updateValidation, forceAllowCreate, options) + if err != nil { + klog.FromContext(ctx).Error(err, "could not update in legacy storage", "mode", Mode2) + return obj, created, err + } + + if notFound { + return d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + } + + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, false, err + } + + // only if object exists + accessorOld, err := meta.Accessor(old) + if err != nil { + return nil, false, err + } + + enrichObject(accessorOld, accessor) + + // keep the same UID and resource_version + accessor.SetResourceVersion(accessorOld.GetResourceVersion()) + accessor.SetUID(accessorOld.GetUID()) + objInfo = &updateWrapper{ + upstream: objInfo, + updated: obj, + } + + // TODO: relies on GuaranteedUpdate creating the object if + // it doesn't exist: https://github.com/grafana/grafana/pull/85206 + return d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) +} diff --git a/pkg/apiserver/rest/dualwriter_mode2_test.go b/pkg/apiserver/rest/dualwriter_mode2_test.go index eb3e640528d..b6daaf98b41 100644 --- a/pkg/apiserver/rest/dualwriter_mode2_test.go +++ b/pkg/apiserver/rest/dualwriter_mode2_test.go @@ -2,70 +2,478 @@ package rest import ( "context" + "errors" "testing" - "github.com/zeebo/assert" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) -func TestMode2(t *testing.T) { - var ls = (LegacyStorage)(nil) - var s = (Storage)(nil) - lsSpy := NewLegacyStorageSpyClient(ls) - sSpy := NewStorageSpyClient(s) - - dw := NewDualWriterMode2(lsSpy, sSpy) - - // Create: it should use the Legacy Create implementation - _, err := dw.Create(context.Background(), &dummyObject{}, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Create")) - assert.Equal(t, 1, sSpy.Counts("Storage.Create")) - - // Get: it should read from Storage with LegacyStorage as a fallback - // #TODO: Currently only testing the happy path. Refactor testing to more easily test other cases. - _, err = dw.Get(context.Background(), kind, &metav1.GetOptions{}) - assert.NoError(t, err) - assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Get")) - assert.Equal(t, 1, sSpy.Counts("Storage.Get")) - - // List: it should use call both Legacy and Storage List methods - l, err := dw.List(context.Background(), &metainternalversion.ListOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.List")) - assert.Equal(t, 1, sSpy.Counts("Storage.List")) - - resList, err := meta.ExtractList(l) - assert.NoError(t, err) - - expectedItems := map[string]string{ - // Item 1: Storage should override Legacy - "Item 1": "Storage field 1", - // Item 2 shouldn't be included because it's not in Storage - // Item 3 should because it's in Legacy - "Item 3": "Legacy field 3", - } - - assert.Equal(t, len(expectedItems), len(resList)) - - for _, obj := range resList { - v, ok := obj.(*dummyObject) - assert.True(t, ok) - accessor, err := meta.Accessor(v) +var createFn = func(context.Context, runtime.Object) error { return nil } +var exampleOption = &metainternalversion.ListOptions{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "foo", + }, +} + +func TestMode2_Create(t *testing.T) { + type testCase struct { + name string + input runtime.Object + setupLegacyFn func(m *mock.Mock, input runtime.Object) + setupStorageFn func(m *mock.Mock, input runtime.Object) + wantErr bool + } + tests := + []testCase{ + { + name: "creating an object in both the LegacyStorage and Storage", + input: exampleObj, + setupLegacyFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error when creating object in the legacy store fails", + input: failingObj, + setupLegacyFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", context.Background(), input, mock.Anything, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) + + obj, err := dw.Create(context.Background(), tt.input, createFn, &metav1.CreateOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + accessor, err := meta.Accessor(obj) assert.NoError(t, err) + assert.Equal(t, accessor.GetResourceVersion(), "") + } +} + +func TestMode2_Get(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, input string) + setupStorageFn func(m *mock.Mock, input string) + wantErr bool + } + tests := + []testCase{ + { + name: "getting an object from storage", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(anotherObj, nil) + }, + }, + { + name: "object not present in storage but present in legacy store", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(nil, errors.New("error")) + }, + }, + { + name: "error when getting object in both stores fails", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(nil, errors.New("error")) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) + + obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode2_List(t *testing.T) { + type testCase struct { + name string + input *metainternalversion.ListOptions + setupLegacyFn func(m *mock.Mock, input *metainternalversion.ListOptions) + setupStorageFn func(m *mock.Mock, input *metainternalversion.ListOptions) + wantErr bool + } + tests := + []testCase{ + { + name: "error when legacy list is not implmented", + input: exampleOption, + setupLegacyFn: func(m *mock.Mock, input *metainternalversion.ListOptions) { + m.On("List", context.Background(), input).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input *metainternalversion.ListOptions) { + m.On("List", context.Background(), input).Return(exampleObj, nil) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) + + obj, err := dw.List(context.Background(), exampleOption) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + } +} - k, ok := expectedItems[accessor.GetName()] - assert.True(t, ok) - assert.Equal(t, k, v.Foo) +func TestMode2_Delete(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, input string) + setupStorageFn func(m *mock.Mock, input string) + wantErr bool } + tests := + []testCase{ + { + name: "delete in legacy and storage", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + }, + { + name: "object delete in legacy not found, but found in storage", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), "not-found-legacy", mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found")) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + }, + { + name: " object delete in storage not found, but found in legacy", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), "not-found-storage", mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found")) + }, + }, + { + name: " object not found in both", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found")) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found")) + }, + wantErr: true, + }, + { + name: " object delete error", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Delete", context.Background(), input, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) + + obj, _, err := dw.Delete(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.DeleteOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode2_DeleteCollection(t *testing.T) { + type testCase struct { + name string + input *metav1.DeleteOptions + setupLegacyFn func(m *mock.Mock, input *metav1.DeleteOptions) + setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions) + wantErr bool + } + tests := + []testCase{ + { + name: "deleting a collection in both stores", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, + setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error deleting a collection in the storage when legacy store is successful", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, + setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + { + name: "error deleting a collection when error in both stores", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, + setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) + }, + setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", context.Background(), mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) - // Delete: it should use call both Legacy and Storage Delete methods - var deleteValidation = func(ctx context.Context, obj runtime.Object) error { return nil } - _, _, err = dw.Delete(context.Background(), kind, deleteValidation, &metav1.DeleteOptions{}) - assert.NoError(t, err) - assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) - assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, tt.input, &metainternalversion.ListOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } +} + +func TestMode2_Update(t *testing.T) { + type testCase struct { + name string + input string + setupLegacyFn func(m *mock.Mock, input string) + setupStorageFn func(m *mock.Mock, input string) + setupGetFn func(m *mock.Mock, input string) + wantErr bool + } + tests := + []testCase{ + { + name: "update an object in both stores", + input: "foo", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupGetFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(exampleObjDifferentRV, nil) + }, + }, + { + name: "object is not found in storage", + input: "not-found", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupGetFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not found")) + }, + }, + { + name: "error finding object storage", + input: "object-fail", + setupGetFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + { + name: "error updating legacy store", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + setupGetFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(exampleObjDifferentRV, nil) + }, + wantErr: true, + }, + { + name: "error updating storage", + input: "object-fail", + setupLegacyFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", context.Background(), input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + setupGetFn: func(m *mock.Mock, input string) { + m.On("Get", context.Background(), input, mock.Anything).Return(exampleObj, nil) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupGetFn != nil { + tt.setupGetFn(m, tt.input) + } + + if tt.setupLegacyFn != nil { + tt.setupLegacyFn(m, tt.input) + } + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := SelectDualWriter(Mode2, ls, us) + + obj, _, err := dw.Update(context.Background(), tt.input, UpdatedObjInfoObj{}, func(ctx context.Context, obj runtime.Object) error { return nil }, func(ctx context.Context, obj, old runtime.Object) error { return nil }, false, &metav1.UpdateOptions{}) + + if tt.wantErr { + assert.Error(t, err) + continue + } + + assert.Equal(t, obj, exampleObj) + assert.NotEqual(t, obj, anotherObj) + } } diff --git a/pkg/apiserver/rest/dualwriter_mode3.go b/pkg/apiserver/rest/dualwriter_mode3.go index e4caf32347b..f86341f88a2 100644 --- a/pkg/apiserver/rest/dualwriter_mode3.go +++ b/pkg/apiserver/rest/dualwriter_mode3.go @@ -4,6 +4,7 @@ import ( "context" apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -54,6 +55,7 @@ func (d *DualWriterMode3) Delete(ctx context.Context, name string, deleteValidat if err != nil { if !apierrors.IsNotFound(err) { klog.FromContext(ctx).Error(err, "could not delete from unified store", "mode", Mode3) + return deleted, async, err } } @@ -66,3 +68,61 @@ func (d *DualWriterMode3) Delete(ctx context.Context, name string, deleteValidat return deleted, async, err } + +// Update overrides the behavior of the generic DualWriter and writes first to Storage and then to LegacyStorage. +func (d *DualWriterMode3) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + old, err := d.Storage.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + updated, err := objInfo.UpdatedObject(ctx, old) + if err != nil { + return nil, false, err + } + objInfo = &updateWrapper{ + upstream: objInfo, + updated: updated, + } + + obj, created, err := d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + if err != nil { + klog.FromContext(ctx).Error(err, "could not write to US", "mode", Mode3) + return obj, created, err + } + + legacy, ok := d.Legacy.(rest.Updater) + if !ok { + klog.FromContext(ctx).Error(errDualWriterUpdaterMissing, "legacy storage update not implemented") + return obj, created, err + } + + _, _, errLeg := legacy.Update(ctx, name, &updateWrapper{ + upstream: objInfo, + updated: obj, + }, createValidation, updateValidation, forceAllowCreate, options) + if errLeg != nil { + klog.FromContext(ctx).Error(errLeg, "could not update object in legacy store", "mode", Mode3) + } + return obj, created, err +} + +// DeleteCollection overrides the behavior of the generic DualWriter and deletes from both LegacyStorage and Storage. +func (d *DualWriterMode3) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing + } + + // #TODO: figure out how to handle partial deletions + deleted, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) + if err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from Storage", "deletedObjects", deleted) + } + + if deleted, err := legacy.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from LegacyStorage", "deletedObjects", deleted) + } + + return deleted, err +} diff --git a/pkg/apiserver/rest/dualwriter_mode3_test.go b/pkg/apiserver/rest/dualwriter_mode3_test.go index 5e1ee9f09d0..ee3d3014bb3 100644 --- a/pkg/apiserver/rest/dualwriter_mode3_test.go +++ b/pkg/apiserver/rest/dualwriter_mode3_test.go @@ -8,6 +8,7 @@ import ( metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/apis/example" ) func TestMode3(t *testing.T) { @@ -42,4 +43,30 @@ func TestMode3(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + + // DeleteCollection: it should delete from both LegacyStorage and Storage + _, err = dw.DeleteCollection( + context.Background(), + func(context.Context, runtime.Object) error { return nil }, + &metav1.DeleteOptions{}, + &metainternalversion.ListOptions{}, + ) + assert.NoError(t, err) + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.DeleteCollection")) + assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection")) + + // Update: it should update in both storages + dummy := &example.Pod{} + uoi := UpdatedObjInfoObj{} + _, err = uoi.UpdatedObject(context.Background(), dummy) + assert.NoError(t, err) + + var validateObjFn = func(ctx context.Context, obj runtime.Object) error { return nil } + var validateObjUpdateFn = func(ctx context.Context, obj, old runtime.Object) error { return nil } + + _, _, err = dw.Update(context.Background(), kind, uoi, validateObjFn, validateObjUpdateFn, false, &metav1.UpdateOptions{}) + assert.NoError(t, err) + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Update")) + assert.Equal(t, 1, sSpy.Counts("Storage.Update")) + assert.NoError(t, err) } diff --git a/pkg/apiserver/rest/dualwriter_mode4.go b/pkg/apiserver/rest/dualwriter_mode4.go index fd8472cf400..ba5f116aaa3 100644 --- a/pkg/apiserver/rest/dualwriter_mode4.go +++ b/pkg/apiserver/rest/dualwriter_mode4.go @@ -3,6 +3,7 @@ package rest import ( "context" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -33,3 +34,13 @@ func (d *DualWriterMode4) Get(ctx context.Context, name string, options *metav1. func (d *DualWriterMode4) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { return d.Storage.Delete(ctx, name, deleteValidation, options) } + +// DeleteCollection overrides the behavior of the generic DualWriter and deletes only from Storage. +func (d *DualWriterMode4) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + return d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) +} + +// Update overrides the generic behavior of the Storage and writes only to US. +func (d *DualWriterMode4) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + return d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) +} diff --git a/pkg/apiserver/rest/dualwriter_mode4_test.go b/pkg/apiserver/rest/dualwriter_mode4_test.go index a051f80f688..41605ab5ade 100644 --- a/pkg/apiserver/rest/dualwriter_mode4_test.go +++ b/pkg/apiserver/rest/dualwriter_mode4_test.go @@ -8,6 +8,7 @@ import ( metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/apis/example" ) func TestMode4(t *testing.T) { @@ -42,4 +43,30 @@ func TestMode4(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + + // DeleteCollection: it should use the Storage DeleteCollection implementation + _, err = dw.DeleteCollection( + context.Background(), + func(context.Context, runtime.Object) error { return nil }, + &metav1.DeleteOptions{}, + &metainternalversion.ListOptions{}, + ) + assert.NoError(t, err) + assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.DeleteCollection")) + assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection")) + + // Update: it should update only in Storage + dummy := &example.Pod{} + uoi := UpdatedObjInfoObj{} + _, err = uoi.UpdatedObject(context.Background(), dummy) + assert.NoError(t, err) + + var validateObjFn = func(ctx context.Context, obj runtime.Object) error { return nil } + var validateObjUpdateFn = func(ctx context.Context, obj, old runtime.Object) error { return nil } + + _, _, err = dw.Update(context.Background(), kind, uoi, validateObjFn, validateObjUpdateFn, false, &metav1.UpdateOptions{}) + assert.NoError(t, err) + assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Update")) + assert.Equal(t, 1, sSpy.Counts("Storage.Update")) + assert.NoError(t, err) } diff --git a/pkg/apiserver/rest/spy_client.go b/pkg/apiserver/rest/spy_client.go index 3ba4f92b4f0..741148886b7 100644 --- a/pkg/apiserver/rest/spy_client.go +++ b/pkg/apiserver/rest/spy_client.go @@ -8,6 +8,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/apis/example" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/klog/v2" ) @@ -65,7 +66,7 @@ func (c *spyStorageClient) Create(ctx context.Context, obj runtime.Object, valit func (c *spyStorageClient) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { c.spy.record("Storage.Get") klog.Info("method: Storage.Get") - return nil, nil + return &example.Pod{}, nil } func (c *spyStorageClient) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { @@ -89,10 +90,18 @@ func (c *spyStorageClient) List(ctx context.Context, options *metainternalversio return &dummyList{Items: []dummyObject{i1, i2}}, nil } +type UpdatedObjInfoObj struct{} + +func (u UpdatedObjInfoObj) UpdatedObject(ctx context.Context, oldObj runtime.Object) (newObj runtime.Object, err error) { + return &example.Pod{}, nil +} + +func (u UpdatedObjInfoObj) Preconditions() *metav1.Preconditions { return &metav1.Preconditions{} } + func (c *spyStorageClient) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { c.spy.record("Storage.Update") klog.Info("method: Storage.Update") - return nil, false, nil + return &example.Pod{}, false, nil } func (c *spyStorageClient) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { @@ -101,6 +110,12 @@ func (c *spyStorageClient) Delete(ctx context.Context, name string, deleteValida return nil, false, nil } +func (c *spyStorageClient) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + c.spy.record("Storage.DeleteCollection") + klog.Info("method: Storage.DeleteCollection") + return nil, nil +} + // LegacyStorage Spy type LegacyStorageSpyClient interface { @@ -143,7 +158,7 @@ func (c *spyLegacyStorageClient) Create(ctx context.Context, obj runtime.Object, func (c *spyLegacyStorageClient) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { c.spy.record("LegacyStorage.Get") klog.Info("method: LegacyStorage.Get") - return nil, nil + return &example.Pod{}, nil } func (c *spyLegacyStorageClient) NewList() runtime.Object { @@ -175,7 +190,7 @@ func (c *spyLegacyStorageClient) List(ctx context.Context, options *metainternal func (c *spyLegacyStorageClient) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { c.spy.record("LegacyStorage.Update") klog.Info("method: LegacyStorage.Update") - return nil, false, nil + return &example.Pod{}, false, nil } func (c *spyLegacyStorageClient) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { @@ -184,6 +199,12 @@ func (c *spyLegacyStorageClient) Delete(ctx context.Context, name string, delete return nil, false, nil } +func (c *spyLegacyStorageClient) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + c.spy.record("LegacyStorage.DeleteCollection") + klog.Info("method: LegacyStorage.DeleteCollection") + return nil, nil +} + type dummyList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/pkg/apiserver/rest/storage_mocks_test.go b/pkg/apiserver/rest/storage_mocks_test.go new file mode 100644 index 00000000000..9f29bbc4a57 --- /dev/null +++ b/pkg/apiserver/rest/storage_mocks_test.go @@ -0,0 +1,135 @@ +package rest + +import ( + "context" + + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +type legacyStoreMock struct { + *mock.Mock + LegacyStorage +} + +type storageMock struct { + *mock.Mock + Storage +} + +func (m legacyStoreMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + args := m.Called(ctx, name, options) + if name == "object-fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m legacyStoreMock) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + args := m.Called(ctx, obj, createValidation, options) + acc, err := meta.Accessor(obj) + if err != nil { + return nil, args.Error(1) + } + name := acc.GetName() + if name == "object-fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m legacyStoreMock) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + args := m.Called(ctx, options) + if options.Kind == "fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m legacyStoreMock) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + if name == "object-fail" { + return nil, false, args.Error(2) + } + return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2) +} + +func (m legacyStoreMock) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + args := m.Called(ctx, name, deleteValidation, options) + if name == "object-fail" { + return nil, false, args.Error(2) + } + if name == "not-found-legacy" { + return nil, false, args.Error(2) + } + return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2) +} + +func (m legacyStoreMock) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + args := m.Called(ctx, deleteValidation, options, listOptions) + if options.Kind == "fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +// Unified Store +func (m storageMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + args := m.Called(ctx, name, options) + if name == "object-fail" { + return nil, args.Error(1) + } + if name == "not-found" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m storageMock) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + args := m.Called(ctx, obj, createValidation, options) + acc, err := meta.Accessor(obj) + if err != nil { + return nil, args.Error(1) + } + name := acc.GetName() + if name == "object-fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m storageMock) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + args := m.Called(ctx, options) + if options.Kind == "fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} + +func (m storageMock) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + if name == "object-fail" { + return nil, false, args.Error(2) + } + return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2) +} + +func (m storageMock) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + args := m.Called(ctx, name, deleteValidation, options) + if name == "object-fail" { + return nil, false, args.Error(2) + } + return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2) +} + +func (m storageMock) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + args := m.Called(ctx, deleteValidation, options, listOptions) + if options.Kind == "fail" { + return nil, args.Error(1) + } + return args.Get(0).(runtime.Object), args.Error(1) +} diff --git a/pkg/cmd/grafana-cli/commands/reset_password_command.go b/pkg/cmd/grafana-cli/commands/reset_password_command.go index 08c8b7edb57..78f0ef6b0df 100644 --- a/pkg/cmd/grafana-cli/commands/reset_password_command.go +++ b/pkg/cmd/grafana-cli/commands/reset_password_command.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/util" ) const DefaultAdminUserId = 1 @@ -58,17 +57,12 @@ func resetPassword(adminId int64, newPassword user.Password, userSvc user.Servic return ErrMustBeAdmin } - passwordHashed, err := util.EncodePassword(string(newPassword), usr.Salt) + password, err := newPassword.Hash(usr.Salt) if err != nil { return err } - cmd := user.ChangeUserPasswordCommand{ - UserID: adminId, - NewPassword: user.Password(passwordHashed), - } - - if err := userSvc.ChangePassword(context.Background(), &cmd); err != nil { + if err := userSvc.Update(context.Background(), &user.UpdateUserCommand{UserID: adminId, Password: &password}); err != nil { return fmt.Errorf("failed to update user password: %w", err) } diff --git a/pkg/cmd/grafana/main.go b/pkg/cmd/grafana/main.go index 9985f6e64e8..e9003dd078a 100644 --- a/pkg/cmd/grafana/main.go +++ b/pkg/cmd/grafana/main.go @@ -20,7 +20,18 @@ var buildBranch = "main" var buildstamp string func main() { - app := &cli.App{ + app := MainApp() + + if err := app.Run(os.Args); err != nil { + fmt.Printf("%s: %s %s\n", color.RedString("Error"), color.RedString("✗"), err) + os.Exit(1) + } + + os.Exit(0) +} + +func MainApp() *cli.App { + return &cli.App{ Name: "grafana", Usage: "Grafana server and command line interface", Authors: []*cli.Author{ @@ -52,18 +63,10 @@ func main() { return nil }, }, - gsrv.ServerCommand(version, commit, enterpriseCommit, buildBranch, buildstamp), }, CommandNotFound: cmdNotFound, EnableBashCompletion: true, } - - if err := app.Run(os.Args); err != nil { - fmt.Printf("%s: %s %s\n", color.RedString("Error"), color.RedString("✗"), err) - os.Exit(1) - } - - os.Exit(0) } func cmdNotFound(c *cli.Context, command string) { diff --git a/pkg/cmd/grafana/main_test.go b/pkg/cmd/grafana/main_test.go new file mode 100644 index 00000000000..0ec0d980643 --- /dev/null +++ b/pkg/cmd/grafana/main_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMainApp_NoDuplicateSubcommands(t *testing.T) { + app := MainApp() + found := map[string]bool{} + for _, cmd := range app.Commands { + require.False(t, found[cmd.Name], "command %q registered twice", cmd.Name) + found[cmd.Name] = true + } +} diff --git a/pkg/expr/converter.go b/pkg/expr/converter.go index 3b080f42e59..445330130ff 100644 --- a/pkg/expr/converter.go +++ b/pkg/expr/converter.go @@ -31,7 +31,7 @@ func (c *ResultConverter) Convert(ctx context.Context, dt, useDataplane, _ := shouldUseDataplane(frames, logger, c.Features.IsEnabled(ctx, featuremgmt.FlagDisableSSEDataplane)) if useDataplane { logger.Debug("Handling SSE data source query through dataplane", "datatype", dt) - result, err := handleDataplaneFrames(ctx, c.Tracer, dt, frames) + result, err := handleDataplaneFrames(ctx, c.Tracer, c.Features, dt, frames) return fmt.Sprintf("dataplane-%s", dt), result, err } diff --git a/pkg/expr/dataplane.go b/pkg/expr/dataplane.go index ae028ea5da9..3f79b11c428 100644 --- a/pkg/expr/dataplane.go +++ b/pkg/expr/dataplane.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/featuremgmt" "go.opentelemetry.io/otel/attribute" ) @@ -58,7 +59,7 @@ func shouldUseDataplane(frames data.Frames, logger log.Logger, disable bool) (dt return dt, true, nil } -func handleDataplaneFrames(ctx context.Context, tracer tracing.Tracer, t data.FrameType, frames data.Frames) (mathexp.Results, error) { +func handleDataplaneFrames(ctx context.Context, tracer tracing.Tracer, features featuremgmt.FeatureToggles, t data.FrameType, frames data.Frames) (mathexp.Results, error) { _, span := tracer.Start(ctx, "SSE.HandleDataPlaneData") defer span.End() span.SetAttributes(attribute.String("dataplane.type", string(t))) @@ -69,7 +70,8 @@ func handleDataplaneFrames(ctx context.Context, tracer tracing.Tracer, t data.Fr case data.KindTimeSeries: return handleDataplaneTimeseries(frames) case data.KindNumeric: - return handleDataplaneNumeric(frames) + sortMetrics := !features.IsEnabled(ctx, featuremgmt.FlagDisableNumericMetricsSortingInExpressions) + return handleDataplaneNumeric(frames, sortMetrics) default: return mathexp.Results{}, fmt.Errorf("kind %s (type %s) not supported by server side expressions", t.Kind(), t) } @@ -105,7 +107,7 @@ func handleDataplaneTimeseries(frames data.Frames) (mathexp.Results, error) { return res, nil } -func handleDataplaneNumeric(frames data.Frames) (mathexp.Results, error) { +func handleDataplaneNumeric(frames data.Frames, sortMetrics bool) (mathexp.Results, error) { dn, err := numeric.CollectionReaderFromFrames(frames) if err != nil { return mathexp.Results{}, err @@ -122,6 +124,9 @@ func handleDataplaneNumeric(frames data.Frames) (mathexp.Results, error) { } return mathexp.Results{Values: mathexp.Values{noData}}, nil } + if sortMetrics { + numeric.SortNumericMetricRef(nc.Refs) + } res := mathexp.Results{} res.Values = make([]mathexp.Value, 0, len(nc.Refs)) for _, n := range nc.Refs { diff --git a/pkg/expr/dataplane_test.go b/pkg/expr/dataplane_test.go index 674d4432f0a..53094212f75 100644 --- a/pkg/expr/dataplane_test.go +++ b/pkg/expr/dataplane_test.go @@ -192,7 +192,7 @@ func TestHandleDataplaneNumeric(t *testing.T) { for _, example := range validNoDataNumericExamples.AsSlice() { t.Run(example.Info().ID, func(t *testing.T) { - res, err := handleDataplaneNumeric(example.Frames("A")) + res, err := handleDataplaneNumeric(example.Frames("A"), false) require.NoError(t, err) require.Len(t, res.Values, 1) }) @@ -213,7 +213,7 @@ func TestHandleDataplaneNumeric(t *testing.T) { for _, example := range numericExamples.AsSlice() { t.Run(example.Info().ID, func(t *testing.T) { - res, err := handleDataplaneNumeric(example.Frames("A")) + res, err := handleDataplaneNumeric(example.Frames("A"), false) require.NoError(t, err) require.Len(t, res.Values, example.Info().ItemCount) }) diff --git a/pkg/expr/hysteresis_test.go b/pkg/expr/hysteresis_test.go index 9a58a1a3492..31fd875213a 100644 --- a/pkg/expr/hysteresis_test.go +++ b/pkg/expr/hysteresis_test.go @@ -95,13 +95,13 @@ func TestHysteresisExecute(t *testing.T) { ReferenceVar: "A", RefID: "B", ThresholdFunc: ThresholdIsAbove, - Conditions: []float64{loadThreshold}, + predicate: greaterThanPredicate{loadThreshold}, }, UnloadingThresholdFunc: ThresholdCommand{ ReferenceVar: "A", RefID: "B", ThresholdFunc: ThresholdIsAbove, - Conditions: []float64{unloadThreshold}, + predicate: greaterThanPredicate{unloadThreshold}, }, LoadedDimensions: tc.loadedDimensions, } diff --git a/pkg/expr/testing.go b/pkg/expr/testing.go index 1c47d6f6e03..412c38a2f26 100644 --- a/pkg/expr/testing.go +++ b/pkg/expr/testing.go @@ -2,9 +2,12 @@ package expr import ( "context" + "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" ) @@ -82,3 +85,43 @@ func (f *recordingCallResourceHandler) CallResource(_ context.Context, req *back f.recordings = append(f.recordings, req) return sender.Send(f.response) } + +func newScalar(value *float64) mathexp.Scalar { + n := mathexp.NewScalar("", value) + return n +} + +func newNumber(labels data.Labels, value *float64) mathexp.Number { + n := mathexp.NewNumber("", labels) + n.SetValue(value) + return n +} + +func newSeries(points ...float64) mathexp.Series { + series := mathexp.NewSeries("", nil, len(points)) + for idx, point := range points { + p := point + series.SetPoint(idx, time.Unix(int64(idx), 0), &p) + } + return series +} + +func newSeriesPointer(points ...*float64) mathexp.Series { + series := mathexp.NewSeries("", nil, len(points)) + for idx, point := range points { + series.SetPoint(idx, time.Unix(int64(idx), 0), point) + } + return series +} + +func newSeriesWithLabels(labels data.Labels, values ...*float64) mathexp.Series { + series := mathexp.NewSeries("", labels, len(values)) + for idx, value := range values { + series.SetPoint(idx, time.Unix(int64(idx), 0), value) + } + return series +} + +func newResults(values ...mathexp.Value) mathexp.Results { + return mathexp.Results{Values: values} +} diff --git a/pkg/expr/threshold.go b/pkg/expr/threshold.go index 6a1903f10cc..9daab174bc0 100644 --- a/pkg/expr/threshold.go +++ b/pkg/expr/threshold.go @@ -13,14 +13,19 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/util" ) +type predicate interface { + Eval(f float64) bool +} + type ThresholdCommand struct { ReferenceVar string RefID string ThresholdFunc ThresholdType - Conditions []float64 Invert bool + predicate predicate } // +enum @@ -43,15 +48,28 @@ var ( ) func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType, conditions []float64) (*ThresholdCommand, error) { + var predicate predicate switch thresholdFunc { - case ThresholdIsOutsideRange, ThresholdIsWithinRange: + case ThresholdIsOutsideRange: if len(conditions) < 2 { - return nil, fmt.Errorf("incorrect number of arguments: got %d but need 2", len(conditions)) + return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 2", thresholdFunc, len(conditions)) } - case ThresholdIsAbove, ThresholdIsBelow: + predicate = outsideRangePredicate{left: conditions[0], right: conditions[1]} + case ThresholdIsWithinRange: + if len(conditions) < 2 { + return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 2", thresholdFunc, len(conditions)) + } + predicate = withinRangePredicate{left: conditions[0], right: conditions[1]} + case ThresholdIsAbove: if len(conditions) < 1 { - return nil, fmt.Errorf("incorrect number of arguments: got %d but need 1", len(conditions)) + return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 1", thresholdFunc, len(conditions)) } + predicate = greaterThanPredicate{value: conditions[0]} + case ThresholdIsBelow: + if len(conditions) < 1 { + return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 1", thresholdFunc, len(conditions)) + } + predicate = lessThanPredicate{value: conditions[0]} default: return nil, fmt.Errorf("expected threshold function to be one of [%s], got %s", strings.Join(supportedThresholdFuncs, ", "), thresholdFunc) } @@ -60,7 +78,7 @@ func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType RefID: refID, ReferenceVar: referenceVar, ThresholdFunc: thresholdFunc, - Conditions: conditions, + predicate: predicate, }, nil } @@ -114,46 +132,51 @@ func (tc *ThresholdCommand) NeedsVars() []string { return []string{tc.ReferenceVar} } -func (tc *ThresholdCommand) Execute(ctx context.Context, now time.Time, vars mathexp.Vars, tracer tracing.Tracer) (mathexp.Results, error) { - mathExpression, err := createMathExpression(tc.ReferenceVar, tc.ThresholdFunc, tc.Conditions, tc.Invert) - if err != nil { - return mathexp.Results{}, err +func (tc *ThresholdCommand) Execute(_ context.Context, _ time.Time, vars mathexp.Vars, _ tracing.Tracer) (mathexp.Results, error) { + eval := func(maybeValue *float64) *float64 { + if maybeValue == nil { + return nil + } + result := tc.predicate.Eval(*maybeValue) + if tc.Invert { + result = !result + } + if result { + return util.Pointer(float64(1)) + } + return util.Pointer(float64(0)) } - mathCommand, err := NewMathCommand(tc.RefID, mathExpression) - if err != nil { - return mathexp.Results{}, err + refVarResult := vars[tc.ReferenceVar] + newRes := mathexp.Results{Values: make(mathexp.Values, 0, len(refVarResult.Values))} + for _, val := range refVarResult.Values { + switch v := val.(type) { + case mathexp.Series: + s := mathexp.NewSeries(tc.RefID, v.GetLabels(), v.Len()) + for i := 0; i < v.Len(); i++ { + t, value := v.GetPoint(i) + s.SetPoint(i, t, eval(value)) + } + newRes.Values = append(newRes.Values, s) + case mathexp.Number: + copyV := mathexp.NewNumber(tc.RefID, v.GetLabels()) + copyV.SetValue(eval(v.GetFloat64Value())) + newRes.Values = append(newRes.Values, copyV) + case mathexp.Scalar: + copyV := mathexp.NewScalar(tc.RefID, eval(v.GetFloat64Value())) + newRes.Values = append(newRes.Values, copyV) + case mathexp.NoData: + newRes.Values = append(newRes.Values, mathexp.NewNoData()) + default: + return newRes, fmt.Errorf("unsupported format of the input data, got type %v", val.Type()) + } } - - return mathCommand.Execute(ctx, now, vars, tracer) + return newRes, nil } func (tc *ThresholdCommand) Type() string { return TypeThreshold.String() } - -// createMathExpression converts all the info we have about a "threshold" expression in to a Math expression -func createMathExpression(referenceVar string, thresholdFunc ThresholdType, args []float64, invert bool) (string, error) { - var exp string - switch thresholdFunc { - case ThresholdIsAbove: - exp = fmt.Sprintf("${%s} > %f", referenceVar, args[0]) - case ThresholdIsBelow: - exp = fmt.Sprintf("${%s} < %f", referenceVar, args[0]) - case ThresholdIsWithinRange: - exp = fmt.Sprintf("${%s} > %f && ${%s} < %f", referenceVar, args[0], referenceVar, args[1]) - case ThresholdIsOutsideRange: - exp = fmt.Sprintf("${%s} < %f || ${%s} > %f", referenceVar, args[0], referenceVar, args[1]) - default: - return "", fmt.Errorf("failed to evaluate threshold expression: no such threshold function %s", thresholdFunc) - } - - if invert { - return fmt.Sprintf("!(%s)", exp), nil - } - return exp, nil -} - func IsSupportedThresholdFunc(name string) bool { isSupported := false @@ -237,3 +260,37 @@ func getConditionForHysteresisCommand(query map[string]any) (map[string]any, err } return condition, nil } + +type withinRangePredicate struct { + left float64 + right float64 +} + +func (r withinRangePredicate) Eval(f float64) bool { + return f > r.left && f < r.right +} + +type outsideRangePredicate struct { + left float64 + right float64 +} + +func (r outsideRangePredicate) Eval(f float64) bool { + return f < r.left || f > r.right +} + +type lessThanPredicate struct { + value float64 +} + +func (r lessThanPredicate) Eval(f float64) bool { + return f < r.value +} + +type greaterThanPredicate struct { + value float64 +} + +func (r greaterThanPredicate) Eval(f float64) bool { + return f > r.value +} diff --git a/pkg/expr/threshold_bench_test.go b/pkg/expr/threshold_bench_test.go new file mode 100644 index 00000000000..ddda45eba97 --- /dev/null +++ b/pkg/expr/threshold_bench_test.go @@ -0,0 +1,88 @@ +package expr + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/util" +) + +func BenchmarkThreshold(b *testing.B) { + results := make(mathexp.Values, 0, 1000) + for i := 0; i < cap(results); i++ { + n := newNumber(data.Labels{"test": fmt.Sprintf("series-%d", i)}, util.Pointer(float64(i))) + results = append(results, n) + } + ctx := context.Background() + timeNow := time.Now() + vars := mathexp.Vars{ + "A": newResults(results...), + } + trace := tracing.InitializeTracerForTest() + b.ResetTimer() + b.Run("greater than", func(b *testing.B) { + greater, err := NewThresholdCommand("B", "A", ThresholdIsAbove, []float64{500}) + if err != nil { + b.Fatalf("error: %s", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = greater.Execute(ctx, timeNow, vars, trace) + } + }) + b.Run("less than", func(b *testing.B) { + greater, err := NewThresholdCommand("B", "A", ThresholdIsAbove, []float64{500}) + if err != nil { + b.Fatalf("error: %s", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = greater.Execute(ctx, timeNow, vars, trace) + } + }) + b.Run("within range", func(b *testing.B) { + greater, err := NewThresholdCommand("B", "A", ThresholdIsWithinRange, []float64{400.0, 600.0}) + if err != nil { + b.Fatalf("error: %s", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = greater.Execute(ctx, timeNow, vars, trace) + } + }) + b.Run("within range, no labels", func(b *testing.B) { + greater, err := NewThresholdCommand("B", "A", ThresholdIsWithinRange, []float64{400.0, 600.0}) + if err != nil { + b.Fatalf("error: %s", err) + } + + results := make(mathexp.Values, 0, 1000) + for i := 0; i < cap(results); i++ { + n := newNumber(nil, util.Pointer(float64(i))) + results = append(results, n) + } + vars := mathexp.Vars{ + "A": newResults(results...), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = greater.Execute(ctx, timeNow, vars, trace) + } + }) + b.Run("outside range", func(b *testing.B) { + greater, err := NewThresholdCommand("B", "A", ThresholdIsOutsideRange, []float64{400.0, 600.0}) + if err != nil { + b.Fatalf("error: %s", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = greater.Execute(ctx, timeNow, vars, trace) + } + }) +} diff --git a/pkg/expr/threshold_test.go b/pkg/expr/threshold_test.go index c7e1c1f2eb6..d458304ae08 100644 --- a/pkg/expr/threshold_test.go +++ b/pkg/expr/threshold_test.go @@ -1,15 +1,22 @@ package expr import ( + "context" "encoding/json" - "fmt" "math" + "slices" "sort" "testing" + "time" + "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/util" ) func TestNewThresholdCommand(t *testing.T) { @@ -108,7 +115,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) { cmd := command.(*ThresholdCommand) require.Equal(t, []string{"A"}, cmd.NeedsVars()) require.Equal(t, ThresholdIsAbove, cmd.ThresholdFunc) - require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions) + require.Equal(t, greaterThanPredicate{20.0}, cmd.predicate) }, }, { @@ -173,10 +180,10 @@ func TestUnmarshalThresholdCommand(t *testing.T) { require.Equal(t, []string{"B"}, cmd.NeedsVars()) require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars()) require.Equal(t, ThresholdIsAbove, cmd.LoadingThresholdFunc.ThresholdFunc) - require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions) + require.Equal(t, greaterThanPredicate{100.0}, cmd.LoadingThresholdFunc.predicate) require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars()) require.Equal(t, ThresholdIsBelow, cmd.UnloadingThresholdFunc.ThresholdFunc) - require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions) + require.Equal(t, lessThanPredicate{31.0}, cmd.UnloadingThresholdFunc.predicate) require.True(t, cmd.UnloadingThresholdFunc.Invert) require.NotNil(t, cmd.LoadedDimensions) actual := make([]uint64, 0, len(cmd.LoadedDimensions)) @@ -227,74 +234,6 @@ func TestThresholdCommandVars(t *testing.T) { require.Equal(t, cmd.NeedsVars(), []string{"A"}) } -func TestCreateMathExpression(t *testing.T) { - type testCase struct { - description string - expected string - - ref string - function ThresholdType - params []float64 - } - - cases := []testCase{ - { - description: "is above", - ref: "My Ref", - function: "gt", - params: []float64{0}, - expected: "${My Ref} > 0.000000", - }, - { - description: "is below", - ref: "A", - function: "lt", - params: []float64{0}, - expected: "${A} < 0.000000", - }, - { - description: "is within", - ref: "B", - function: "within_range", - params: []float64{20, 80}, - expected: "${B} > 20.000000 && ${B} < 80.000000", - }, - { - description: "is outside", - ref: "B", - function: "outside_range", - params: []float64{20, 80}, - expected: "${B} < 20.000000 || ${B} > 80.000000", - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - expr, err := createMathExpression(tc.ref, tc.function, tc.params, false) - - require.Nil(t, err) - require.NotNil(t, expr) - - require.Equal(t, tc.expected, expr) - - t.Run("inverted", func(t *testing.T) { - expr, err := createMathExpression(tc.ref, tc.function, tc.params, true) - require.Nil(t, err) - require.NotNil(t, expr) - - require.Equal(t, fmt.Sprintf("!(%s)", tc.expected), expr) - }) - }) - } - - t.Run("should error if function is unsupported", func(t *testing.T) { - expr, err := createMathExpression("A", "foo", []float64{0}, false) - require.Equal(t, expr, "") - require.NotNil(t, err) - require.Contains(t, err.Error(), "no such threshold function") - }) -} - func TestIsSupportedThresholdFunc(t *testing.T) { type testCase struct { function ThresholdType @@ -443,3 +382,178 @@ func TestSetLoadedDimensionsToHysteresisCommand(t *testing.T) { require.Equal(t, fingerprints, cmd.(*HysteresisCommand).LoadedDimensions) }) } + +func TestThresholdExecute(t *testing.T) { + input := map[string]mathexp.Value{ + // + "no-data": mathexp.NewNoData(), + // + "series - numbers": newSeries(8, 9, 10, 11, 12), + "series - empty": newSeriesPointer(), + "series - all nils": newSeriesPointer(nil, nil, nil), + "series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(9)), nil, util.Pointer(float64(11)), nil), + "series - with NaNs": newSeries(math.NaN(), math.NaN(), math.NaN()), + // + "scalar - nil": newScalar(nil), + "scalar - NaN": newScalar(util.Pointer(math.NaN())), + "scalar - 8": newScalar(util.Pointer(float64(8))), + "scalar - 9": newScalar(util.Pointer(float64(9))), + "scalar - 10": newScalar(util.Pointer(float64(10))), + "scalar - 11": newScalar(util.Pointer(float64(11))), + "scalar - 12": newScalar(util.Pointer(float64(12))), + // + "number - nil": newNumber(data.Labels{"number": "test"}, nil), + "number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(math.NaN())), + "number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(8))), + "number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(9))), + "number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(10))), + "number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(11))), + "number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(12))), + } + keys := maps.Keys(input) + slices.Sort(keys) + testCases := []struct { + name string + pred predicate + expected map[string]mathexp.Value + errorMsg string + }{ + { + name: "greater than 10", + pred: greaterThanPredicate{10.0}, + expected: map[string]mathexp.Value{ + // + "no-data": mathexp.NewNoData(), + // + "series - numbers": newSeries(0, 0, 0, 1, 1), + "series - empty": newSeriesPointer(), + "series - all nils": newSeriesPointer(nil, nil, nil), + "series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(1)), nil), + "series - with NaNs": newSeries(0, 0, 0), + // + "scalar - nil": newScalar(nil), + "scalar - NaN": newScalar(util.Pointer(float64(0))), + "scalar - 8": newScalar(util.Pointer(float64(0))), + "scalar - 9": newScalar(util.Pointer(float64(0))), + "scalar - 10": newScalar(util.Pointer(float64(0))), + "scalar - 11": newScalar(util.Pointer(float64(1))), + "scalar - 12": newScalar(util.Pointer(float64(1))), + // + "number - nil": newNumber(data.Labels{"number": "test"}, nil), + "number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + "number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + }, + }, + { + name: "less than 10", + pred: lessThanPredicate{10.0}, + expected: map[string]mathexp.Value{ + // + "no-data": mathexp.NewNoData(), + // + "series - numbers": newSeries(1, 1, 0, 0, 0), + "series - empty": newSeriesPointer(), + "series - all nils": newSeriesPointer(nil, nil, nil), + "series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil), + "series - with NaNs": newSeries(0, 0, 0), + // + "scalar - nil": newScalar(nil), + "scalar - NaN": newScalar(util.Pointer(float64(0))), + "scalar - 8": newScalar(util.Pointer(float64(1))), + "scalar - 9": newScalar(util.Pointer(float64(1))), + "scalar - 10": newScalar(util.Pointer(float64(0))), + "scalar - 11": newScalar(util.Pointer(float64(0))), + "scalar - 12": newScalar(util.Pointer(float64(0))), + // + "number - nil": newNumber(data.Labels{"number": "test"}, nil), + "number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + "number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + "number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + }, + }, + { + name: "within range (8,11)", + pred: withinRangePredicate{8, 11}, + expected: map[string]mathexp.Value{ + // + "no-data": mathexp.NewNoData(), + // + "series - numbers": newSeries(0, 1, 1, 0, 0), + "series - empty": newSeriesPointer(), + "series - all nils": newSeriesPointer(nil, nil, nil), + "series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil), + "series - with NaNs": newSeries(0, 0, 0), + // + "scalar - nil": newScalar(nil), + "scalar - NaN": newScalar(util.Pointer(float64(0))), + "scalar - 8": newScalar(util.Pointer(float64(0))), + "scalar - 9": newScalar(util.Pointer(float64(1))), + "scalar - 10": newScalar(util.Pointer(float64(1))), + "scalar - 11": newScalar(util.Pointer(float64(0))), + "scalar - 12": newScalar(util.Pointer(float64(0))), + // + "number - nil": newNumber(data.Labels{"number": "test"}, nil), + "number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + "number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + "number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + }, + }, + { + name: "outside range (8, 11)", + pred: outsideRangePredicate{8, 11}, + expected: map[string]mathexp.Value{ + // + "no-data": mathexp.NewNoData(), + // + "series - numbers": newSeries(0, 0, 0, 0, 1), + "series - empty": newSeriesPointer(), + "series - all nils": newSeriesPointer(nil, nil, nil), + "series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(0)), nil), + "series - with NaNs": newSeries(0, 0, 0), + // + "scalar - nil": newScalar(nil), + "scalar - NaN": newScalar(util.Pointer(float64(0))), + "scalar - 8": newScalar(util.Pointer(float64(0))), + "scalar - 9": newScalar(util.Pointer(float64(0))), + "scalar - 10": newScalar(util.Pointer(float64(0))), + "scalar - 11": newScalar(util.Pointer(float64(0))), + "scalar - 12": newScalar(util.Pointer(float64(1))), + // + "number - nil": newNumber(data.Labels{"number": "test"}, nil), + "number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), + "number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := ThresholdCommand{ + predicate: tc.pred, + ReferenceVar: "A", + } + for _, name := range keys { + t.Run(name, func(t *testing.T) { + result, err := cmd.Execute(context.Background(), time.Now(), mathexp.Vars{ + "A": newResults(input[name]), + }, tracing.InitializeTracerForTest()) + require.NoError(t, err) + require.Equal(t, newResults(tc.expected[name]), result) + }) + } + }) + } +} diff --git a/pkg/plugins/backendplugin/grpcplugin/client_proto.go b/pkg/plugins/backendplugin/grpcplugin/client_proto.go index 7226c9e09a7..e208e674fbe 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_proto.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_proto.go @@ -9,6 +9,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" ) @@ -27,6 +29,8 @@ type ProtoClient interface { PID(context.Context) (string, error) PluginID() string PluginVersion() string + PluginJSON() plugins.JSONData + Backend() backendplugin.Plugin Logger() log.Logger Start(context.Context) error Stop(context.Context) error @@ -36,13 +40,13 @@ type ProtoClient interface { type protoClient struct { plugin *grpcPlugin pluginVersion string + pluginJSON plugins.JSONData mu sync.RWMutex } type ProtoClientOpts struct { - PluginID string - PluginVersion string + PluginJSON plugins.JSONData ExecutablePath string ExecutableArgs []string Env []string @@ -52,7 +56,7 @@ type ProtoClientOpts struct { func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { p := newGrpcPlugin( PluginDescriptor{ - pluginID: opts.PluginID, + pluginID: opts.PluginJSON.ID, managed: true, executablePath: opts.ExecutablePath, executableArgs: opts.ExecutableArgs, @@ -62,7 +66,7 @@ func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { func() []string { return opts.Env }, ) - return &protoClient{plugin: p, pluginVersion: opts.PluginVersion}, nil + return &protoClient{plugin: p, pluginVersion: opts.PluginJSON.Info.Version, pluginJSON: opts.PluginJSON}, nil } func (r *protoClient) PID(ctx context.Context) (string, error) { @@ -80,6 +84,14 @@ func (r *protoClient) PluginVersion() string { return r.pluginVersion } +func (r *protoClient) PluginJSON() plugins.JSONData { + return r.pluginJSON +} + +func (r *protoClient) Backend() backendplugin.Plugin { + return r.plugin +} + func (r *protoClient) Logger() log.Logger { return r.plugin.logger } diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index aa3dcd0180b..73898c981e0 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -110,6 +110,7 @@ type StaticRouteResolver interface { type ErrorResolver interface { PluginErrors(ctx context.Context) []*Error + PluginError(ctx context.Context, pluginID string) *Error } type PluginLoaderAuthorizer interface { diff --git a/pkg/plugins/manager/pipeline/initialization/steps.go b/pkg/plugins/manager/pipeline/initialization/steps.go index c27cf667602..2df09d1144a 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps.go +++ b/pkg/plugins/manager/pipeline/initialization/steps.go @@ -79,8 +79,11 @@ func newBackendProcessStarter(processManager process.Manager) *BackendClientStar // Start will start the backend plugin process. func (b *BackendClientStarter) Start(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { if err := b.processManager.Start(ctx, p); err != nil { - b.log.Error("Could not start plugin", "pluginId", p.ID, "error", err) - return nil, err + b.log.Error("Could not start plugin backend", "pluginId", p.ID, "error", err) + return nil, (&plugins.Error{ + PluginID: p.ID, + ErrorCode: plugins.ErrorCodeFailedBackendStart, + }).WithMessage(err.Error()) } return p, nil } diff --git a/pkg/plugins/manager/pipeline/validation/steps.go b/pkg/plugins/manager/pipeline/validation/steps.go index 20dee23f780..10af0cce5e4 100644 --- a/pkg/plugins/manager/pipeline/validation/steps.go +++ b/pkg/plugins/manager/pipeline/validation/steps.go @@ -106,7 +106,10 @@ func (a *AngularDetector) Validate(ctx context.Context, p *plugins.Plugin) error // Do not initialize plugins if they're using Angular and Angular support is disabled if p.Angular.Detected && !a.cfg.AngularSupportEnabled { a.log.Error("Refusing to initialize plugin because it's using Angular, which has been disabled", "pluginId", p.ID) - return errors.New("angular plugins are not supported") + return (&plugins.Error{ + PluginID: p.ID, + ErrorCode: plugins.ErrorAngular, + }).WithMessage("angular plugins are not supported") } } p.Angular.HideDeprecation = slices.Contains(a.cfg.HideAngularDeprecation, p.ID) diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index f9e263eefe4..a7583ef4a9c 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -235,9 +235,11 @@ type AppDTO struct { } const ( - errorCodeSignatureMissing ErrorCode = "signatureMissing" - errorCodeSignatureModified ErrorCode = "signatureModified" - errorCodeSignatureInvalid ErrorCode = "signatureInvalid" + errorCodeSignatureMissing ErrorCode = "signatureMissing" + errorCodeSignatureModified ErrorCode = "signatureModified" + errorCodeSignatureInvalid ErrorCode = "signatureInvalid" + ErrorCodeFailedBackendStart ErrorCode = "failedBackendStart" + ErrorAngular ErrorCode = "angular" ) type ErrorCode string @@ -246,9 +248,14 @@ type Error struct { ErrorCode `json:"errorCode"` PluginID string `json:"pluginId,omitempty"` SignatureStatus SignatureStatus `json:"status,omitempty"` + message string `json:"-"` } func (e Error) Error() string { + if e.message != "" { + return e.message + } + if e.SignatureStatus != "" { switch e.SignatureStatus { case SignatureStatusInvalid: @@ -266,6 +273,10 @@ func (e Error) Error() string { } func (e Error) AsErrorCode() ErrorCode { + if e.ErrorCode != "" { + return e.ErrorCode + } + switch e.SignatureStatus { case SignatureStatusInvalid: return errorCodeSignatureInvalid @@ -280,6 +291,28 @@ func (e Error) AsErrorCode() ErrorCode { return "" } +func (e *Error) WithMessage(m string) *Error { + e.message = m + return e +} + +func (e Error) PublicMessage() string { + switch e.ErrorCode { + case errorCodeSignatureInvalid: + return "Invalid plugin signature" + case errorCodeSignatureModified: + return "Plugin signature does not match" + case errorCodeSignatureMissing: + return "Plugin signature is missing" + case ErrorCodeFailedBackendStart: + return "Plugin failed to start" + case ErrorAngular: + return "Angular plugins are not supported" + } + + return "Plugin failed to load" +} + // Access-Control related definitions // RoleRegistration stores a role and its assignments to basic roles diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 8e99fc43fa9..012c44cb4c1 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.0 require ( - github.com/grafana/grafana-plugin-sdk-go v0.222.0 + github.com/grafana/grafana-plugin-sdk-go v0.224.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.18.0 @@ -45,7 +45,7 @@ require ( github.com/go-openapi/swag v0.22.9 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -109,10 +109,10 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.1 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index b98abb0941b..717ed270802 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -91,9 +91,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= @@ -101,7 +99,6 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -114,8 +111,7 @@ 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-plugin-sdk-go v0.222.0 h1:bWK35de9Kl9+lRGyh+41zW2X8MkQ4959aqUsi08/8ig= -github.com/grafana/grafana-plugin-sdk-go v0.222.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= +github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -396,22 +392,16 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/promlib/models/query.go b/pkg/promlib/models/query.go index bb6c996cbab..930de98ab0c 100644 --- a/pkg/promlib/models/query.go +++ b/pkg/promlib/models/query.go @@ -3,6 +3,7 @@ package models import ( "embed" "encoding/json" + "fmt" "math" "strconv" "strings" @@ -12,6 +13,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/prometheus/prometheus/model/labels" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/promlib/intervalv2" ) @@ -63,8 +66,11 @@ type PrometheusQueryProperties struct { // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname LegendFormat string `json:"legendFormat,omitempty"` - // ??? + // A set of filters applied to apply to the query Scope *ScopeSpec `json:"scope,omitempty"` + + // Additional Ad-hoc filters that take precedence over Scope on conflict. + AdhocFilters []ScopeFilter `json:"adhocFilters,omitempty"` } // ScopeSpec is a hand copy of the ScopeSpec struct from pkg/apis/scope/v0alpha1/types.go @@ -166,11 +172,12 @@ type Scope struct { Matchers []*labels.Matcher } -func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool, enableScope bool) (*Query, error) { +func Parse(span trace.Span, query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool, enableScope bool) (*Query, error) { model := &QueryModel{} if err := json.Unmarshal(query.JSON, model); err != nil { return nil, err } + span.SetAttributes(attribute.String("rawExpr", model.Expr)) // Final step value for prometheus calculatedStep, err := calculatePrometheusInterval(model.Interval, dsScrapeInterval, int64(model.IntervalMS), model.IntervalFactor, query, intervalCalculator) @@ -188,12 +195,39 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator dsScrapeInterval, timeRange, ) - if enableScope && model.Scope != nil && len(model.Scope.Filters) > 0 { - expr, err = ApplyQueryScope(expr, *model.Scope) + + if enableScope { + var scopeFilters []ScopeFilter + if model.Scope != nil { + scopeFilters = model.Scope.Filters + } + + if len(scopeFilters) > 0 { + span.SetAttributes(attribute.StringSlice("scopeFilters", func() []string { + var filters []string + for _, f := range scopeFilters { + filters = append(filters, fmt.Sprintf("%q %q %q", f.Key, f.Operator, f.Value)) + } + return filters + }())) + } + + if len(model.AdhocFilters) > 0 { + span.SetAttributes(attribute.StringSlice("adhocFilters", func() []string { + var filters []string + for _, f := range model.AdhocFilters { + filters = append(filters, fmt.Sprintf("%q %q %q", f.Key, f.Operator, f.Value)) + } + return filters + }())) + } + + expr, err = ApplyQueryFilters(expr, scopeFilters, model.AdhocFilters) if err != nil { return nil, err } } + if !model.Instant && !model.Range { // In older dashboards, we were not setting range query param and !range && !instant was run as range query model.Range = true @@ -204,6 +238,12 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator model.Exemplar = false } + span.SetAttributes( + attribute.String("expr", expr), + attribute.Int64("start_unixnano", query.TimeRange.From.UnixNano()), + attribute.Int64("stop_unixnano", query.TimeRange.To.UnixNano()), + ) + return &Query{ Expr: expr, Step: calculatedStep, diff --git a/pkg/promlib/models/query.panel.schema.json b/pkg/promlib/models/query.panel.schema.json index ffd2328f1de..f52bb24a968 100644 --- a/pkg/promlib/models/query.panel.schema.json +++ b/pkg/promlib/models/query.panel.schema.json @@ -14,6 +14,31 @@ "expr" ], "properties": { + "adhocFilters": { + "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", + "type": "array", + "items": { + "description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)", + "type": "object", + "required": [ + "key", + "value", + "operator" + ], + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, "datasource": { "description": "The datasource", "type": "object", @@ -138,7 +163,7 @@ "additionalProperties": false }, "scope": { - "description": "???", + "description": "A set of filters applied to apply to the query", "type": "object", "required": [ "title", diff --git a/pkg/promlib/models/query.request.schema.json b/pkg/promlib/models/query.request.schema.json index c8e797d1f38..979f69a6cb0 100644 --- a/pkg/promlib/models/query.request.schema.json +++ b/pkg/promlib/models/query.request.schema.json @@ -24,6 +24,31 @@ "expr" ], "properties": { + "adhocFilters": { + "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", + "type": "array", + "items": { + "description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)", + "type": "object", + "required": [ + "key", + "value", + "operator" + ], + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, "datasource": { "description": "The datasource", "type": "object", @@ -148,7 +173,7 @@ "additionalProperties": false }, "scope": { - "description": "???", + "description": "A set of filters applied to apply to the query", "type": "object", "required": [ "title", diff --git a/pkg/promlib/models/query.types.json b/pkg/promlib/models/query.types.json index 7ae0e10374e..95f7ab499d8 100644 --- a/pkg/promlib/models/query.types.json +++ b/pkg/promlib/models/query.types.json @@ -8,7 +8,7 @@ { "metadata": { "name": "default", - "resourceVersion": "1711374012365", + "resourceVersion": "1713187448137", "creationTimestamp": "2024-03-25T13:19:04Z" }, "spec": { @@ -17,6 +17,31 @@ "additionalProperties": false, "description": "PrometheusQueryProperties defines the specific properties used for prometheus", "properties": { + "adhocFilters": { + "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", + "items": { + "additionalProperties": false, + "description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)", + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value", + "operator" + ], + "type": "object" + }, + "type": "array" + }, "editorMode": { "description": "what we should show in the editor\n\n\nPossible enum values:\n - `\"builder\"` \n - `\"code\"` ", "enum": [ @@ -62,7 +87,7 @@ }, "scope": { "additionalProperties": false, - "description": "???", + "description": "A set of filters applied to apply to the query", "properties": { "category": { "type": "string" diff --git a/pkg/promlib/models/query_test.go b/pkg/promlib/models/query_test.go index 3bba6eb78d9..21925581f53 100644 --- a/pkg/promlib/models/query_test.go +++ b/pkg/promlib/models/query_test.go @@ -1,6 +1,7 @@ package models_test import ( + "context" "fmt" "reflect" "testing" @@ -10,6 +11,7 @@ import ( sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/grafana/grafana-plugin-sdk-go/experimental/schemabuilder" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" "github.com/grafana/grafana/pkg/promlib/intervalv2" "github.com/grafana/grafana/pkg/promlib/models" @@ -18,9 +20,12 @@ import ( var ( now = time.Now() intervalCalculator = intervalv2.NewCalculator() + tracer = otel.Tracer("instrumentation/package/name") ) func TestParse(t *testing.T) { + _, span := tracer.Start(context.Background(), "operation") + defer span.End() t.Run("parsing query from unified alerting", func(t *testing.T) { timeRange := backend.TimeRange{ From: now, @@ -39,7 +44,7 @@ func TestParse(t *testing.T) { RefID: "A", } - res, err := models.Parse(q, "15s", intervalCalculator, true, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, true, false) require.NoError(t, err) require.Equal(t, false, res.ExemplarQuery) }) @@ -56,7 +61,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, time.Second*30, res.Step) }) @@ -74,7 +79,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, time.Second*15, res.Step) }) @@ -92,7 +97,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, time.Minute*20, res.Step) }) @@ -110,7 +115,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, time.Minute*2, res.Step) }) @@ -128,7 +133,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "240s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "240s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, time.Minute*4, res.Step) }) @@ -147,7 +152,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr) require.Equal(t, 120*time.Second, res.Step) @@ -168,7 +173,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr) }) @@ -187,7 +192,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [120000]})", res.Expr) }) @@ -206,7 +211,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr) }) @@ -225,7 +230,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr) }) @@ -243,7 +248,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr) }) @@ -261,7 +266,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [172800]})", res.Expr) }) @@ -279,7 +284,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr) }) @@ -297,7 +302,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [0]})", res.Expr) }) @@ -315,7 +320,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [1]})", res.Expr) }) @@ -333,7 +338,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [172800000]})", res.Expr) }) @@ -351,7 +356,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [20]})", res.Expr) }) @@ -370,7 +375,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [20m0s]})", res.Expr) }) @@ -389,7 +394,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, 1*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [1m0s]})", res.Expr) require.Equal(t, 1*time.Minute, res.Step) @@ -408,7 +413,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, 2*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [135000]})", res.Expr) }) @@ -426,7 +431,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, 2*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [135000]}) + rate(ALERTS{job=\"test\" [2m15s]})", res.Expr) }) @@ -444,7 +449,7 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, 2*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "rate(ALERTS{job=\"test\" [135000]}) + rate(ALERTS{job=\"test\" [2m15s]})", res.Expr) }) @@ -463,7 +468,7 @@ func TestParse(t *testing.T) { "range": true }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, true, res.RangeQuery) }) @@ -483,7 +488,7 @@ func TestParse(t *testing.T) { "instant": true }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, true, res.RangeQuery) require.Equal(t, true, res.InstantQuery) @@ -502,13 +507,15 @@ func TestParse(t *testing.T) { "refId": "A" }`, timeRange, time.Duration(1)*time.Minute) - res, err := models.Parse(q, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, q, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, true, res.RangeQuery) }) } func TestRateInterval(t *testing.T) { + _, span := tracer.Start(context.Background(), "operation") + defer span.End() type args struct { expr string interval string @@ -633,7 +640,7 @@ func TestRateInterval(t *testing.T) { t.Run(tt.name, func(t *testing.T) { q := mockQuery(tt.args.expr, tt.args.interval, tt.args.intervalMs, tt.args.timeRange) q.MaxDataPoints = 12384 - res, err := models.Parse(q, tt.args.dsScrapeInterval, intervalCalculator, false, false) + res, err := models.Parse(span, q, tt.args.dsScrapeInterval, intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, tt.want.Expr, res.Expr) require.Equal(t, tt.want.Step, res.Step) @@ -668,7 +675,7 @@ func TestRateInterval(t *testing.T) { "utcOffsetSec":3600 }`), } - res, err := models.Parse(query, "30s", intervalCalculator, false, false) + res, err := models.Parse(span, query, "30s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "sum(rate(process_cpu_seconds_total[2m0s]))", res.Expr) require.Equal(t, 30*time.Second, res.Step) @@ -703,7 +710,7 @@ func TestRateInterval(t *testing.T) { "maxDataPoints": 1055 }`), } - res, err := models.Parse(query, "15s", intervalCalculator, false, false) + res, err := models.Parse(span, query, "15s", intervalCalculator, false, false) require.NoError(t, err) require.Equal(t, "sum(rate(cache_requests_total[1m0s]))", res.Expr) require.Equal(t, 15*time.Second, res.Step) diff --git a/pkg/promlib/models/scope.go b/pkg/promlib/models/scope.go index 9207aa747a8..04c6895ae9f 100644 --- a/pkg/promlib/models/scope.go +++ b/pkg/promlib/models/scope.go @@ -7,13 +7,13 @@ import ( "github.com/prometheus/prometheus/promql/parser" ) -func ApplyQueryScope(rawExpr string, scope ScopeSpec) (string, error) { +func ApplyQueryFilters(rawExpr string, scopeFilters, adHocFilters []ScopeFilter) (string, error) { expr, err := parser.ParseExpr(rawExpr) if err != nil { return "", err } - matchers, err := scopeFiltersToMatchers(scope.Filters) + matchers, err := filtersToMatchers(scopeFilters, adHocFilters) if err != nil { return "", err } @@ -58,27 +58,38 @@ func ApplyQueryScope(rawExpr string, scope ScopeSpec) (string, error) { return expr.String(), nil } -func scopeFiltersToMatchers(filters []ScopeFilter) ([]*labels.Matcher, error) { - matchers := make([]*labels.Matcher, 0, len(filters)) - for _, f := range filters { - var mt labels.MatchType - switch f.Operator { - case FilterOperatorEquals: - mt = labels.MatchEqual - case FilterOperatorNotEquals: - mt = labels.MatchNotEqual - case FilterOperatorRegexMatch: - mt = labels.MatchRegexp - case FilterOperatorRegexNotMatch: - mt = labels.MatchNotRegexp - default: - return nil, fmt.Errorf("unknown operator %q", f.Operator) - } - m, err := labels.NewMatcher(mt, f.Key, f.Value) +func filtersToMatchers(scopeFilters, adhocFilters []ScopeFilter) ([]*labels.Matcher, error) { + filterMap := make(map[string]*labels.Matcher) + + for _, filter := range append(scopeFilters, adhocFilters...) { + matcher, err := filterToMatcher(filter) if err != nil { return nil, err } - matchers = append(matchers, m) + filterMap[filter.Key] = matcher } + + matchers := make([]*labels.Matcher, 0, len(filterMap)) + for _, matcher := range filterMap { + matchers = append(matchers, matcher) + } + return matchers, nil } + +func filterToMatcher(f ScopeFilter) (*labels.Matcher, error) { + var mt labels.MatchType + switch f.Operator { + case FilterOperatorEquals: + mt = labels.MatchEqual + case FilterOperatorNotEquals: + mt = labels.MatchNotEqual + case FilterOperatorRegexMatch: + mt = labels.MatchRegexp + case FilterOperatorRegexNotMatch: + mt = labels.MatchNotRegexp + default: + return nil, fmt.Errorf("unknown operator %q", f.Operator) + } + return labels.NewMatcher(mt, f.Key, f.Value) +} diff --git a/pkg/promlib/models/scope_test.go b/pkg/promlib/models/scope_test.go new file mode 100644 index 00000000000..a517633a292 --- /dev/null +++ b/pkg/promlib/models/scope_test.go @@ -0,0 +1,105 @@ +package models + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestApplyQueryFilters(t *testing.T) { + tests := []struct { + name string + query string + adhocFilters []ScopeFilter + scopeFilters []ScopeFilter + expected string + expectErr bool + }{ + { + name: "No filters with no existing filter", + query: `http_requests_total`, + expected: `http_requests_total`, + expectErr: false, + }, + { + name: "No filters with existing filter", + query: `http_requests_total{job="prometheus"}`, + expected: `http_requests_total{job="prometheus"}`, + expectErr: false, + }, + { + name: "Adhoc filter with existing filter", + query: `http_requests_total{job="prometheus"}`, + adhocFilters: []ScopeFilter{ + {Key: "method", Value: "get", Operator: FilterOperatorEquals}, + }, + expected: `http_requests_total{job="prometheus",method="get"}`, + expectErr: false, + }, + { + name: "Adhoc filter with no existing filter", + query: `http_requests_total`, + adhocFilters: []ScopeFilter{ + {Key: "method", Value: "get", Operator: FilterOperatorEquals}, + {Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, + }, + expected: `http_requests_total{job="prometheus",method="get"}`, + expectErr: false, + }, + { + name: "Scope filter", + query: `http_requests_total{job="prometheus"}`, + scopeFilters: []ScopeFilter{ + {Key: "status", Value: "200", Operator: FilterOperatorEquals}, + }, + expected: `http_requests_total{job="prometheus",status="200"}`, + expectErr: false, + }, + { + name: "Adhoc and Scope filter no existing filter", + query: `http_requests_total`, + scopeFilters: []ScopeFilter{ + {Key: "status", Value: "200", Operator: FilterOperatorEquals}, + }, + adhocFilters: []ScopeFilter{ + {Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, + }, + expected: `http_requests_total{job="prometheus",status="200"}`, + expectErr: false, + }, + { + name: "Adhoc and Scope filter conflict - adhoc wins", + query: `http_requests_total{job="prometheus"}`, + scopeFilters: []ScopeFilter{ + {Key: "status", Value: "404", Operator: FilterOperatorEquals}, + }, + adhocFilters: []ScopeFilter{ + {Key: "status", Value: "200", Operator: FilterOperatorEquals}, + }, + expected: `http_requests_total{job="prometheus",status="200"}`, + expectErr: false, + }, + { + name: "Adhoc filters with more complex expression", + query: `capacity_bytes{job="prometheus"} + available_bytes{job="grafana"} / 1024`, + adhocFilters: []ScopeFilter{ + {Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, + }, + expected: `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + expr, err := ApplyQueryFilters(tt.query, tt.scopeFilters, tt.adhocFilters) + + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, expr, tt.expected) + } + }) + } +} diff --git a/pkg/promlib/querydata/request.go b/pkg/promlib/querydata/request.go index aabd347b9ae..b9afb827111 100644 --- a/pkg/promlib/querydata/request.go +++ b/pkg/promlib/querydata/request.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana-plugin-sdk-go/backend/log" @@ -94,14 +93,8 @@ func (s *QueryData) Execute(ctx context.Context, req *backend.QueryDataRequest) hasPrometheusDataplaneFeatureFlag := cfg.FeatureToggles().IsEnabled("prometheusDataplane") for _, q := range req.Queries { - query, err := models.Parse(q, s.TimeInterval, s.intervalCalculator, fromAlert, hasPromQLScopeFeatureFlag) - if err != nil { - return &result, err - } - - r := s.fetch(ctx, s.client, query, hasPrometheusDataplaneFeatureFlag) + r := s.handleQuery(ctx, q, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag) if r == nil { - s.log.FromContext(ctx).Debug("Received nil response from runQuery", "query", query.Expr) continue } result.Responses[q.RefID] = *r @@ -110,10 +103,24 @@ func (s *QueryData) Execute(ctx context.Context, req *backend.QueryDataRequest) return &result, nil } -func (s *QueryData) fetch(ctx context.Context, client *client.Client, q *models.Query, enablePrometheusDataplane bool) *backend.DataResponse { - traceCtx, end := s.trace(ctx, q) - defer end() +func (s *QueryData) handleQuery(ctx context.Context, bq backend.DataQuery, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag bool) *backend.DataResponse { + traceCtx, span := s.tracer.Start(ctx, "datasource.prometheus") + defer span.End() + query, err := models.Parse(span, bq, s.TimeInterval, s.intervalCalculator, fromAlert, hasPromQLScopeFeatureFlag) + if err != nil { + return &backend.DataResponse{ + Error: err, + } + } + r := s.fetch(traceCtx, s.client, query, hasPrometheusDataplaneFeatureFlag) + if r == nil { + s.log.FromContext(ctx).Debug("Received nil response from runQuery", "query", query.Expr) + } + return r +} + +func (s *QueryData) fetch(traceCtx context.Context, client *client.Client, q *models.Query, enablePrometheusDataplane bool) *backend.DataResponse { logger := s.log.FromContext(traceCtx) logger.Debug("Sending query", "start", q.Start, "end", q.End, "step", q.Step, "query", q.Expr) @@ -218,11 +225,3 @@ func (s *QueryData) exemplarQuery(ctx context.Context, c *client.Client, q *mode }() return s.parseResponse(ctx, q, res, enablePrometheusDataplaneFlag) } - -func (s *QueryData) trace(ctx context.Context, q *models.Query) (context.Context, func()) { - return utils.StartTrace(ctx, s.tracer, "datasource.prometheus", - attribute.String("expr", q.Expr), - attribute.Int64("start_unixnano", q.Start.UnixNano()), - attribute.Int64("stop_unixnano", q.End.UnixNano()), - ) -} diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index 3e9a1a0fc34..c222de708ca 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -112,7 +112,7 @@ func TestService_DeclareFixedRoles(t *testing.T) { }, }, wantErr: true, - err: accesscontrol.ErrInvalidBuiltinRole, + err: accesscontrol.ErrInvalidBuiltinRole.Build(accesscontrol.ErrInvalidBuiltinRoleData("WrongAdmin")), }, { name: "should add multiple registrations at once", @@ -224,7 +224,7 @@ func TestService_DeclarePluginRoles(t *testing.T) { }, }, wantErr: true, - err: accesscontrol.ErrInvalidBuiltinRole, + err: accesscontrol.ErrInvalidBuiltinRole.Build(accesscontrol.ErrInvalidBuiltinRoleData("WrongAdmin")), }, { name: "should add multiple registrations at once", diff --git a/pkg/services/accesscontrol/errors.go b/pkg/services/accesscontrol/errors.go index 175f5015329..1152c6f6fa0 100644 --- a/pkg/services/accesscontrol/errors.go +++ b/pkg/services/accesscontrol/errors.go @@ -3,18 +3,48 @@ package accesscontrol import ( "errors" "fmt" + + "github.com/grafana/grafana/pkg/util/errutil" +) + +const ( + invalidBuiltInRoleMessage = `built-in role [{{ .Public.builtInRole }}] is not valid` + assignmentEntityNotFoundMessage = `{{ .Public.assignment }} not found` ) var ( + ErrInvalidBuiltinRole = errutil.BadRequest("accesscontrol.invalidBuiltInRole"). + MustTemplate(invalidBuiltInRoleMessage, errutil.WithPublic(invalidBuiltInRoleMessage)) + ErrNoneRoleAssignment = errutil.BadRequest("accesscontrol.noneRoleAssignment", errutil.WithPublicMessage("none role cannot receive permissions")) + ErrAssignmentEntityNotFound = errutil.BadRequest("accesscontrol.assignmentEntityNotFound"). + MustTemplate(assignmentEntityNotFoundMessage, errutil.WithPublic(assignmentEntityNotFoundMessage)) + + // Note: these are intended to be replaced by equivalent errutil implementations. + // Avoid creating new errors with errors.New and prefer errutil + ErrInvalidRequestBody = errors.New("invalid request body") ErrFixedRolePrefixMissing = errors.New("fixed role should be prefixed with '" + FixedRolePrefix + "'") - ErrInvalidBuiltinRole = errors.New("built-in role is not valid") - ErrNoneRoleAssignment = errors.New("none role cannot receive permissions") ErrInvalidScope = errors.New("invalid scope") ErrResolverNotFound = errors.New("no resolver found") ErrPluginIDRequired = errors.New("plugin ID is required") ErrRoleNotFound = errors.New("role not found") ) +func ErrInvalidBuiltinRoleData(builtInRole string) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "builtInRole": builtInRole, + }, + } +} + +func ErrAssignmentEntityNotFoundData(assignment string) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "assignment": assignment, + }, + } +} + type ErrorInvalidRole struct{} func (e *ErrorInvalidRole) Error() string { diff --git a/pkg/services/accesscontrol/middleware.go b/pkg/services/accesscontrol/middleware.go index f7fb8393e5f..714f37b9041 100644 --- a/pkg/services/accesscontrol/middleware.go +++ b/pkg/services/accesscontrol/middleware.go @@ -14,6 +14,7 @@ import ( "text/template" "time" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/auth/identity" @@ -180,6 +181,13 @@ func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func return func(c *contextmodel.ReqContext) { targetOrgID, err := getTargetOrg(c) if err != nil { + if errors.Is(err, ErrInvalidRequestBody) { + c.JSON(http.StatusBadRequest, map[string]string{ + "message": err.Error(), + "traceID": tracing.TraceIDFromContext(c.Req.Context(), false), + }) + return + } deny(c, nil, fmt.Errorf("failed to get target org: %w", err)) return } @@ -254,6 +262,9 @@ func UseGlobalOrgFromRequestData(cfg *setting.Cfg) OrgIDGetter { return func(c *contextmodel.ReqContext) (int64, error) { query, err := getOrgQueryFromRequest(c) if err != nil { + if errors.Is(err, ErrInvalidRequestBody) { + return NoOrgID, err + } // Special case of macaron handling invalid params return NoOrgID, org.ErrOrgNotFound.Errorf("failed to get organization from context: %w", err) } @@ -290,7 +301,9 @@ func getOrgQueryFromRequest(c *contextmodel.ReqContext) (*QueryWithOrg, error) { } if err := web.Bind(req, query); err != nil { - // Special case of macaron handling invalid params + if err.Error() == "unexpected EOF" { + return nil, fmt.Errorf("%w: unexpected end of JSON input", ErrInvalidRequestBody) + } return nil, err } diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index 95db0372e8a..b8c05dfc775 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -144,7 +144,7 @@ func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response { permissions, err := a.service.GetPermissions(c.Req.Context(), c.SignedInUser, resourceID) if err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "failed to get permissions", err) + return response.ErrOrFallback(http.StatusInternalServerError, "Failed to get permissions", err) } if a.service.options.Assignments.BuiltInRoles && !a.service.license.FeatureEnabled("accesscontrol.enforcement") { @@ -229,7 +229,7 @@ type SetResourcePermissionsForUserParams struct { func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response { userID, err := strconv.ParseInt(web.Params(c.Req)[":userID"], 10, 64) if err != nil { - return response.Error(http.StatusBadRequest, "userID is invalid", err) + return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("userID", err))) } resourceID := web.Params(c.Req)[":resourceID"] @@ -240,7 +240,7 @@ func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response { _, err = a.service.SetUserPermission(c.Req.Context(), c.SignedInUser.GetOrgID(), accesscontrol.User{ID: userID}, resourceID, cmd.Permission) if err != nil { - return response.ErrOrFallback(http.StatusBadRequest, "failed to set user permission", err) + return response.Err(err) } return permissionSetResponse(cmd) @@ -282,7 +282,7 @@ type SetResourcePermissionsForTeamParams struct { func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response { teamID, err := strconv.ParseInt(web.Params(c.Req)[":teamID"], 10, 64) if err != nil { - return response.Error(http.StatusBadRequest, "teamID is invalid", err) + return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("teamID", err))) } resourceID := web.Params(c.Req)[":resourceID"] @@ -293,7 +293,7 @@ func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response { _, err = a.service.SetTeamPermission(c.Req.Context(), c.SignedInUser.GetOrgID(), teamID, resourceID, cmd.Permission) if err != nil { - return response.ErrOrFallback(http.StatusBadRequest, "failed to set team permission", err) + return response.Err(err) } return permissionSetResponse(cmd) @@ -343,7 +343,7 @@ func (a *api) setBuiltinRolePermission(c *contextmodel.ReqContext) response.Resp _, err := a.service.SetBuiltInRolePermission(c.Req.Context(), c.SignedInUser.GetOrgID(), builtInRole, resourceID, cmd.Permission) if err != nil { - return response.ErrOrFallback(http.StatusBadRequest, "failed to set role permission", err) + return response.Err(err) } return permissionSetResponse(cmd) @@ -383,12 +383,12 @@ func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response { cmd := setPermissionsCommand{} if err := web.Bind(c.Req, &cmd); err != nil { - return response.Error(http.StatusBadRequest, "bad request data", err) + return response.Error(http.StatusBadRequest, "Bad request data: "+err.Error(), err) } _, err := a.service.SetPermissions(c.Req.Context(), c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...) if err != nil { - return response.ErrOrFallback(http.StatusBadRequest, "failed to set permission", err) + return response.Err(err) } return response.Success("Permissions updated") diff --git a/pkg/services/accesscontrol/resourcepermissions/error.go b/pkg/services/accesscontrol/resourcepermissions/error.go index 53a8164a926..985fd51cf08 100644 --- a/pkg/services/accesscontrol/resourcepermissions/error.go +++ b/pkg/services/accesscontrol/resourcepermissions/error.go @@ -1,8 +1,56 @@ package resourcepermissions -import "errors" +import ( + "github.com/grafana/grafana/pkg/util/errutil" +) + +const ( + invalidPermissionMessage = `Permission [{{ .Public.permission }}] is invalid for this resource type` + invalidAssignmentMessage = `Assignment [{{ .Public.assignment }}] is invalid for this resource type` + invalidParamMessage = `Param [{{ .Public.param }}] is invalid` + invalidRequestBody = `Request body is invalid: {{ .Public.reason }}` +) var ( - ErrInvalidPermission = errors.New("invalid permission") - ErrInvalidAssignment = errors.New("invalid assignment") + ErrInvalidParam = errutil.BadRequest("resourcePermissions.invalidParam"). + MustTemplate(invalidParamMessage, errutil.WithPublic(invalidParamMessage)) + ErrInvalidRequestBody = errutil.BadRequest("resourcePermissions.invalidRequestBody"). + MustTemplate(invalidRequestBody, errutil.WithPublic(invalidRequestBody)) + ErrInvalidPermission = errutil.BadRequest("resourcePermissions.invalidPermission"). + MustTemplate(invalidPermissionMessage, errutil.WithPublic(invalidPermissionMessage)) + ErrInvalidAssignment = errutil.BadRequest("resourcePermissions.invalidAssignment"). + MustTemplate(invalidAssignmentMessage, errutil.WithPublic(invalidAssignmentMessage)) ) + +func ErrInvalidParamData(param string, err error) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "param": param, + }, + Error: err, + } +} + +func ErrInvalidRequestBodyData(reason string) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "reason": reason, + }, + } +} + +func ErrInvalidPermissionData(permission string) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "permission": permission, + }, + } +} + +func ErrInvalidAssignmentData(assignment string) errutil.TemplateData { + return errutil.TemplateData{ + Public: map[string]any{ + "assignment": assignment, + }, + } +} diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index 9c69645c7e3..4a6f27daec3 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -2,6 +2,7 @@ package resourcepermissions import ( "context" + "errors" "fmt" "sort" @@ -286,7 +287,7 @@ func (s *Service) mapPermission(permission string) ([]string, error) { return v, nil } } - return nil, ErrInvalidPermission + return nil, ErrInvalidPermission.Build(ErrInvalidPermissionData(permission)) } func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID string) error { @@ -298,27 +299,37 @@ func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { if !s.options.Assignments.Users { - return ErrInvalidAssignment + return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("users")) } _, err := s.userService.GetSignedInUser(ctx, &user.GetSignedInUserQuery{OrgID: orgID, UserID: userID}) - return err + switch { + case errors.Is(err, user.ErrUserNotFound): + return accesscontrol.ErrAssignmentEntityNotFound.Build(accesscontrol.ErrAssignmentEntityNotFoundData("user")) + default: + return err + } } func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error { if !s.options.Assignments.Teams { - return ErrInvalidAssignment + return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("teams")) } if _, err := s.teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{OrgID: orgID, ID: teamID}); err != nil { - return err + switch { + case errors.Is(err, team.ErrTeamNotFound): + return accesscontrol.ErrAssignmentEntityNotFound.Build(accesscontrol.ErrAssignmentEntityNotFoundData("team")) + default: + return err + } } return nil } -func (s *Service) validateBuiltinRole(ctx context.Context, builtinRole string) error { +func (s *Service) validateBuiltinRole(_ context.Context, builtinRole string) error { if !s.options.Assignments.BuiltInRoles { - return ErrInvalidAssignment + return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("builtInRoles")) } if err := accesscontrol.ValidateBuiltInRoles([]string{builtinRole}); err != nil { diff --git a/pkg/services/accesscontrol/roles.go b/pkg/services/accesscontrol/roles.go index 72334fee499..a81feff00ab 100644 --- a/pkg/services/accesscontrol/roles.go +++ b/pkg/services/accesscontrol/roles.go @@ -388,7 +388,7 @@ func ValidateBuiltInRoles(builtInRoles []string) error { return ErrNoneRoleAssignment } if !org.RoleType(br).IsValid() && br != RoleGrafanaAdmin { - return fmt.Errorf("'%s' %w", br, ErrInvalidBuiltinRole) + return ErrInvalidBuiltinRole.Build(ErrInvalidBuiltinRoleData(br)) } } return nil diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 3315c62fabb..8774ae4b2a8 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -251,7 +251,7 @@ func (s *service) start(ctx context.Context) error { return err } - storeServer, err := sqlstash.ProvideSQLEntityServer(eDB) + storeServer, err := sqlstash.ProvideSQLEntityServer(eDB, s.tracing) if err != nil { return err } diff --git a/pkg/services/apiserver/storage/entity/test/watch_test.go b/pkg/services/apiserver/storage/entity/test/watch_test.go index 58e6a6a4097..6f05909e413 100644 --- a/pkg/services/apiserver/storage/entity/test/watch_test.go +++ b/pkg/services/apiserver/storage/entity/test/watch_test.go @@ -9,6 +9,7 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/apitesting" @@ -71,7 +72,11 @@ func createTestContext(t *testing.T) (entityStore.EntityStoreClient, factory.Des err = eDB.Init() require.NoError(t, err) - store, err := sqlstash.ProvideSQLEntityServer(eDB) + traceConfig, err := tracing.ParseTracingConfig(cfg) + require.NoError(t, err) + tracer, err := tracing.ProvideService(traceConfig) + require.NoError(t, err) + store, err := sqlstash.ProvideSQLEntityServer(eDB, tracer) require.NoError(t, err) client := entityStore.NewEntityStoreClientLocal(store) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index d6718f11cf4..ad8bb42c15f 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -353,6 +353,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a ID: namespaceID.String(), OrgID: orgID, ClientParams: authn.ClientParams{ + AllowGlobalOrg: true, FetchSyncedUser: true, SyncPermissions: true, }}, nil diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index b2083f3a3e4..93d46e8f823 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -84,9 +84,8 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncOrgRoles: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, @@ -102,9 +101,8 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncOrgRoles: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index 23d0d6f9042..fb6d4d006e3 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -195,7 +195,8 @@ func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, return nil } - return s.userService.Disable(ctx, &user.DisableUserCommand{UserID: userID, IsDisabled: false}) + isDisabled := false + return s.userService.Update(ctx, &user.UpdateUserCommand{UserID: userID, IsDisabled: &isDisabled}) } func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, identity *authn.Identity, createConnection bool) error { @@ -258,6 +259,13 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id needsUpdate = true } + // Sync isGrafanaAdmin permission + if id.IsGrafanaAdmin != nil && *id.IsGrafanaAdmin != usr.IsAdmin { + updateCmd.IsGrafanaAdmin = id.IsGrafanaAdmin + usr.IsAdmin = *id.IsGrafanaAdmin + needsUpdate = true + } + if needsUpdate { s.log.FromContext(ctx).Debug("Syncing user info", "id", id.ID, "update", fmt.Sprintf("%v", updateCmd)) if err := s.userService.Update(ctx, updateCmd); err != nil { @@ -265,14 +273,6 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id } } - // Sync isGrafanaAdmin permission - if id.IsGrafanaAdmin != nil && *id.IsGrafanaAdmin != usr.IsAdmin { - usr.IsAdmin = *id.IsGrafanaAdmin - if errPerms := s.userService.UpdatePermissions(ctx, usr.ID, *id.IsGrafanaAdmin); errPerms != nil { - return errPerms - } - } - return s.upsertAuthConnection(ctx, usr.ID, id, userAuth == nil) } @@ -368,14 +368,6 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar var usr *user.User var err error - // If not found, try to find the user by id - if params.UserID != nil && *params.UserID != 0 { - usr, err = s.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: *params.UserID}) - if err != nil && !errors.Is(err, user.ErrUserNotFound) { - return nil, err - } - } - // If not found, try to find the user by email address if usr == nil && params.Email != nil && *params.Email != "" { usr, err = s.userService.GetByEmail(ctx, &user.GetUserByEmailQuery{Email: *params.Email}) diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index 9449547d75a..f34b16c168d 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -25,10 +25,6 @@ func ptrBool(b bool) *bool { return &b } -func ptrInt64(i int64) *int64 { - return &i -} - func TestUserSync_SyncUserHook(t *testing.T) { userProtection := &authinfoimpl.OSSUserProtectionImpl{} @@ -120,9 +116,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { Email: "test", ClientParams: authn.ClientParams{ LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, @@ -135,9 +130,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { Email: "test", ClientParams: authn.ClientParams{ LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, @@ -159,9 +153,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, @@ -176,9 +169,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test"), - Login: nil, + Email: ptrString("test"), + Login: nil, }, }, }, @@ -200,9 +192,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: nil, - Login: ptrString("test"), + Email: nil, + Login: ptrString("test"), }, }, }, @@ -216,55 +207,13 @@ func TestUserSync_SyncUserHook(t *testing.T) { IsGrafanaAdmin: ptrBool(false), ClientParams: authn.ClientParams{ LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: nil, - Login: ptrString("test"), + Email: nil, + Login: ptrString("test"), }, SyncUser: true, }, }, }, - { - name: "sync - user found in DB - by ID", - fields: fields{ - userService: userService, - authInfoService: authFakeNil, - quotaService: "atest.FakeQuotaService{}, - }, - args: args{ - ctx: context.Background(), - id: &authn.Identity{ - ID: "", - Login: "test", - Name: "test", - Email: "test", - ClientParams: authn.ClientParams{ - SyncUser: true, - LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(1), - Email: nil, - Login: nil, - }, - }, - }, - }, - wantErr: false, - wantID: &authn.Identity{ - ID: "user:1", - Login: "test", - Name: "test", - Email: "test", - IsGrafanaAdmin: ptrBool(false), - ClientParams: authn.ClientParams{ - SyncUser: true, - LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(1), - Email: nil, - Login: nil, - }, - }, - }, - }, { name: "sync - user found in authInfo", fields: fields{ @@ -284,9 +233,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: nil, - Login: nil, + Email: nil, + Login: nil, }, }, }, @@ -303,9 +251,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: nil, - Login: nil, + Email: nil, + Login: nil, }, }, }, @@ -329,9 +276,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { ClientParams: authn.ClientParams{ SyncUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: nil, - Login: nil, + Email: nil, + Login: nil, }, }, }, @@ -360,9 +306,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { AllowSignUp: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test_create"), - Login: nil, + Email: ptrString("test_create"), + Login: nil, }, }, }, @@ -381,9 +326,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { AllowSignUp: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: ptrString("test_create"), - Login: nil, + Email: ptrString("test_create"), + Login: nil, }, }, }, @@ -408,9 +352,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { SyncUser: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(3), - Email: nil, - Login: nil, + Email: nil, + Login: ptrString("test"), }, }, }, @@ -427,9 +370,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { SyncUser: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(3), - Email: nil, - Login: nil, + Email: nil, + Login: ptrString("test"), }, }, }, @@ -455,9 +397,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { SyncUser: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(3), - Email: nil, - Login: nil, + Email: nil, + Login: ptrString("test"), }, }, }, @@ -475,9 +416,8 @@ func TestUserSync_SyncUserHook(t *testing.T) { SyncUser: true, EnableUser: true, LookUpParams: login.UserLookupParams{ - UserID: ptrInt64(3), - Email: nil, - Login: nil, + Email: nil, + Login: ptrString("test"), }, }, }, @@ -569,7 +509,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { userSvc := usertest.NewUserServiceFake() called := false - userSvc.DisableFn = func(ctx context.Context, cmd *user.DisableUserCommand) error { + userSvc.UpdateFn = func(ctx context.Context, cmd *user.UpdateUserCommand) error { called = true return nil } diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 7ef5ecc7716..fe8c1135897 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -21,7 +21,6 @@ import ( "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/authn" - "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/signingkeys" "github.com/grafana/grafana/pkg/services/signingkeys/signingkeystest" "github.com/grafana/grafana/pkg/services/user" @@ -176,8 +175,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { ClientParams: authn.ClientParams{SyncUser: false, AllowSignUp: false, EnableUser: false, FetchSyncedUser: false, SyncTeams: false, SyncOrgRoles: false, CacheAuthProxyKey: "", - LookUpParams: login.UserLookupParams{UserID: (*int64)(nil), - Email: (*string)(nil), Login: (*string)(nil)}, SyncPermissions: true, + SyncPermissions: true, FetchPermissionsParams: authn.FetchPermissionsParams{ActionsLookup: []string(nil), Roles: []string{"fixed:folders:reader"}}}, Permissions: map[int64]map[string][]string(nil), IDToken: ""}, wantErr: nil, @@ -208,7 +206,6 @@ func TestExtendedJWT_Authenticate(t *testing.T) { ClientParams: authn.ClientParams{SyncUser: false, AllowSignUp: false, EnableUser: false, FetchSyncedUser: true, SyncTeams: false, SyncOrgRoles: false, CacheAuthProxyKey: "", - LookUpParams: login.UserLookupParams{UserID: (*int64)(nil), Email: (*string)(nil), Login: (*string)(nil)}, SyncPermissions: true, FetchPermissionsParams: authn.FetchPermissionsParams{ActionsLookup: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"}, diff --git a/pkg/services/authn/clients/grafana_test.go b/pkg/services/authn/clients/grafana_test.go index f77d233744f..b2cb45154ec 100644 --- a/pkg/services/authn/clients/grafana_test.go +++ b/pkg/services/authn/clients/grafana_test.go @@ -116,7 +116,6 @@ func TestGrafana_AuthenticateProxy(t *testing.T) { assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Email, identity.ClientParams.LookUpParams.Email) assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Login, identity.ClientParams.LookUpParams.Login) - assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.UserID, identity.ClientParams.LookUpParams.UserID) } else { assert.Nil(t, tt.expectedIdentity) } diff --git a/pkg/services/authn/clients/jwt_test.go b/pkg/services/authn/clients/jwt_test.go index d9476f37320..f5d0dbd09ad 100644 --- a/pkg/services/authn/clients/jwt_test.go +++ b/pkg/services/authn/clients/jwt_test.go @@ -57,9 +57,8 @@ func TestAuthenticateJWT(t *testing.T) { SyncPermissions: true, SyncTeams: true, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: stringPtr("eai.doe@cor.po"), - Login: stringPtr("eai-doe"), + Email: stringPtr("eai.doe@cor.po"), + Login: stringPtr("eai-doe"), }, }, }, @@ -111,9 +110,8 @@ func TestAuthenticateJWT(t *testing.T) { SyncPermissions: true, SyncTeams: false, LookUpParams: login.UserLookupParams{ - UserID: nil, - Email: stringPtr("eai.doe@cor.po"), - Login: stringPtr("eai-doe"), + Email: stringPtr("eai.doe@cor.po"), + Login: stringPtr("eai-doe"), }, }, }, diff --git a/pkg/services/authn/clients/ldap.go b/pkg/services/authn/clients/ldap.go index d505979cb9c..e78f0ff119c 100644 --- a/pkg/services/authn/clients/ldap.go +++ b/pkg/services/authn/clients/ldap.go @@ -99,7 +99,8 @@ func (c *LDAP) disableUser(ctx context.Context, username string) (*authn.Identit // Disable the user c.logger.Debug("User was removed from the LDAP directory tree, disabling it", "username", username, "authID", authinfo.AuthId) - if errDisable := c.userService.Disable(ctx, &user.DisableUserCommand{UserID: dbUser.ID, IsDisabled: true}); errDisable != nil { + isDiabled := true + if errDisable := c.userService.Update(ctx, &user.UpdateUserCommand{UserID: dbUser.ID, IsDisabled: &isDiabled}); errDisable != nil { return nil, errDisable } diff --git a/pkg/services/authn/clients/ldap_test.go b/pkg/services/authn/clients/ldap_test.go index 592d74ed0f2..cd08fa2ac01 100644 --- a/pkg/services/authn/clients/ldap_test.go +++ b/pkg/services/authn/clients/ldap_test.go @@ -187,7 +187,7 @@ func setupLDAPTestCase(tt *ldapTestCase) *LDAP { userService := &usertest.FakeUserService{ ExpectedError: tt.expectedUserErr, ExpectedUser: &tt.expectedUser, - DisableFn: func(ctx context.Context, cmd *user.DisableUserCommand) error { + UpdateFn: func(ctx context.Context, cmd *user.UpdateUserCommand) error { tt.disableCalled = true return nil }, diff --git a/pkg/services/authn/clients/oauth_test.go b/pkg/services/authn/clients/oauth_test.go index 65916f3b435..95838da0159 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -308,7 +308,6 @@ func TestOAuth_Authenticate(t *testing.T) { assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Email, identity.ClientParams.LookUpParams.Email) assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Login, identity.ClientParams.LookUpParams.Login) - assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.UserID, identity.ClientParams.LookUpParams.UserID) } else { assert.Nil(t, tt.expectedIdentity) } diff --git a/pkg/services/cloudmigration/api/api.go b/pkg/services/cloudmigration/api/api.go index 6df1306a5f6..c7188fbb86f 100644 --- a/pkg/services/cloudmigration/api/api.go +++ b/pkg/services/cloudmigration/api/api.go @@ -1,10 +1,7 @@ package api import ( - "bytes" - "encoding/json" "fmt" - "io" "net/http" "strconv" @@ -172,7 +169,6 @@ func (cma *CloudMigrationAPI) CreateMigration(c *contextmodel.ReqContext) respon func (cma *CloudMigrationAPI) RunMigration(c *contextmodel.ReqContext) response.Response { ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.RunMigration") defer span.End() - logger := cma.log.FromContext(ctx) stringID := web.Params(c.Req)[":id"] id, err := strconv.ParseInt(stringID, 10, 64) @@ -180,70 +176,10 @@ func (cma *CloudMigrationAPI) RunMigration(c *contextmodel.ReqContext) response. return response.Error(http.StatusBadRequest, "id is invalid", err) } - // Get migration to read the auth token - migration, err := cma.cloudMigrationService.GetMigration(ctx, id) + result, err := cma.cloudMigrationService.RunMigration(ctx, id) if err != nil { - return response.Error(http.StatusInternalServerError, "migration get error", err) + return response.Error(http.StatusInternalServerError, "migration run error", err) } - // get CMS path from the config - domain, err := cma.cloudMigrationService.ParseCloudMigrationConfig() - if err != nil { - return response.Error(http.StatusInternalServerError, "config parse error", err) - } - path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/migrate-data", migration.ClusterSlug, domain) - - // Get migration data JSON - body, err := cma.cloudMigrationService.GetMigrationDataJSON(ctx, id) - if err != nil { - cma.log.Error("error getting the json request body for migration run", "err", err.Error()) - return response.Error(http.StatusInternalServerError, "migration data get error", err) - } - - req, err := http.NewRequest(http.MethodPost, path, bytes.NewReader(body)) - if err != nil { - cma.log.Error("error creating http request for cloud migration run", "err", err.Error()) - return response.Error(http.StatusInternalServerError, "http request error", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", migration.StackID, migration.AuthToken)) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - cma.log.Error("error sending http request for cloud migration run", "err", err.Error()) - return response.Error(http.StatusInternalServerError, "http request error", err) - } else if resp.StatusCode >= 400 { - cma.log.Error("received error response for cloud migration run", "statusCode", resp.StatusCode) - return response.Error(http.StatusInternalServerError, "http request error", fmt.Errorf("http request error while migrating data")) - } - - defer func() { - if err := resp.Body.Close(); err != nil { - logger.Error("closing request body: %w", err) - } - }() - - // read response so we can unmarshal it - respData, err := io.ReadAll(resp.Body) - if err != nil { - logger.Error("reading response body: %w", err) - return response.Error(http.StatusInternalServerError, "reading migration run response", err) - } - var result cloudmigration.MigrateDataResponseDTO - if err := json.Unmarshal(respData, &result); err != nil { - logger.Error("unmarshalling response body: %w", err) - return response.Error(http.StatusInternalServerError, "unmarshalling migration run response", err) - } - - runID, err := cma.cloudMigrationService.SaveMigrationRun(ctx, &cloudmigration.CloudMigrationRun{ - CloudMigrationUID: stringID, - Result: respData, - }) - if err != nil { - response.Error(http.StatusInternalServerError, "migration run save error", err) - } - - result.RunID = runID return response.JSON(http.StatusOK, result) } @@ -309,22 +245,9 @@ func (cma *CloudMigrationAPI) GetMigrationRunList(c *contextmodel.ReqContext) re ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetMigrationRunList") defer span.End() - migrationStatuses, err := cma.cloudMigrationService.GetMigrationStatusList(ctx, web.Params(c.Req)[":id"]) + runList, err := cma.cloudMigrationService.GetMigrationRunList(ctx, web.Params(c.Req)[":id"]) if err != nil { - return response.Error(http.StatusInternalServerError, "migration status error", err) - } - - runList := cloudmigration.CloudMigrationRunList{Runs: []cloudmigration.MigrateDataResponseDTO{}} - for _, s := range migrationStatuses { - // attempt to bind the raw result to a list of response item DTOs - r := cloudmigration.MigrateDataResponseDTO{ - Items: []cloudmigration.MigrateDataResponseItemDTO{}, - } - if err := json.Unmarshal(s.Result, &r); err != nil { - return response.Error(http.StatusInternalServerError, "error unmarshalling migration response items", err) - } - r.RunID = s.ID - runList.Runs = append(runList.Runs, r) + return response.Error(http.StatusInternalServerError, "list migration status error", err) } return response.JSON(http.StatusOK, runList) diff --git a/pkg/services/cloudmigration/cloudmigration.go b/pkg/services/cloudmigration/cloudmigration.go index d632b61d18e..7fdf5fff4d8 100644 --- a/pkg/services/cloudmigration/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigration.go @@ -7,16 +7,15 @@ import ( type Service interface { CreateToken(context.Context) (CreateAccessTokenResponse, error) ValidateToken(context.Context, CloudMigration) error - // migration - GetMigration(context.Context, int64) (*CloudMigration, error) - GetMigrationList(context.Context) (*CloudMigrationListResponse, error) - CreateMigration(context.Context, CloudMigrationRequest) (*CloudMigrationResponse, error) - GetMigrationDataJSON(context.Context, int64) ([]byte, error) - UpdateMigration(context.Context, int64, CloudMigrationRequest) (*CloudMigrationResponse, error) - GetMigrationStatus(context.Context, string, string) (*CloudMigrationRun, error) - GetMigrationStatusList(context.Context, string) ([]*CloudMigrationRun, error) - DeleteMigration(context.Context, int64) (*CloudMigration, error) - SaveMigrationRun(context.Context, *CloudMigrationRun) (int64, error) - ParseCloudMigrationConfig() (string, error) + CreateMigration(context.Context, CloudMigrationRequest) (*CloudMigrationResponse, error) + GetMigration(context.Context, int64) (*CloudMigration, error) + DeleteMigration(context.Context, int64) (*CloudMigration, error) + UpdateMigration(context.Context, int64, CloudMigrationRequest) (*CloudMigrationResponse, error) + GetMigrationList(context.Context) (*CloudMigrationListResponse, error) + + RunMigration(context.Context, int64) (*MigrateDataResponseDTO, error) + SaveMigrationRun(context.Context, *CloudMigrationRun) (int64, error) + GetMigrationStatus(context.Context, string, string) (*CloudMigrationRun, error) + GetMigrationRunList(context.Context, string) (*CloudMigrationRunList, error) } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go index 61925b26487..3aaa12e6b11 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go @@ -1,20 +1,22 @@ package cloudmigrationimpl import ( - "bytes" "context" "encoding/base64" "encoding/json" "fmt" "net/http" + "strconv" "time" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/cloudmigration" "github.com/grafana/grafana/pkg/services/cloudmigration/api" + "github.com/grafana/grafana/pkg/services/cloudmigration/cmsclient" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" @@ -33,7 +35,8 @@ type Service struct { log *log.ConcreteLogger cfg *setting.Cfg - features featuremgmt.FeatureToggles + features featuremgmt.FeatureToggles + cmsClient cmsclient.Client dsService datasources.DataSourceService gcomService gcom.Service @@ -70,9 +73,9 @@ func ProvideService( tracer tracing.Tracer, dashboardService dashboards.DashboardService, folderService folder.Service, -) cloudmigration.Service { +) (cloudmigration.Service, error) { if !features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrations) { - return &NoopServiceImpl{} + return &NoopServiceImpl{}, nil } s := &Service{ @@ -90,11 +93,18 @@ func ProvideService( } s.api = api.RegisterApi(routeRegister, s, tracer) + // get CMS path from the config + domain, err := s.parseCloudMigrationConfig() + if err != nil { + return nil, fmt.Errorf("config parse error: %w", err) + } + s.cmsClient = cmsclient.NewCMSClient(domain) + if err := s.registerMetrics(prom, s.metrics); err != nil { s.log.Warn("error registering prom metrics", "error", err.Error()) } - return s + return s, nil } func (s *Service) CreateToken(ctx context.Context) (cloudmigration.CreateAccessTokenResponse, error) { @@ -213,44 +223,9 @@ func (s *Service) findAccessPolicyByName(ctx context.Context, regionSlug, access func (s *Service) ValidateToken(ctx context.Context, cm cloudmigration.CloudMigration) error { ctx, span := s.tracer.Start(ctx, "CloudMigrationService.ValidateToken") defer span.End() - logger := s.log.FromContext(ctx) - // get CMS path from the config - domain, err := s.ParseCloudMigrationConfig() - if err != nil { - return fmt.Errorf("config parse error: %w", err) - } - path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/validate-key", cm.ClusterSlug, domain) - - // validation is an empty POST to CMS with the authorization header included - req, err := http.NewRequest("POST", path, bytes.NewReader(nil)) - if err != nil { - logger.Error("error creating http request for token validation", "err", err.Error()) - return fmt.Errorf("http request error: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", cm.StackID, cm.AuthToken)) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - logger.Error("error sending http request for token validation", "err", err.Error()) - return fmt.Errorf("http request error: %w", err) - } - - defer func() { - if err := resp.Body.Close(); err != nil { - logger.Error("closing request body", "err", err.Error()) - } - }() - - if resp.StatusCode != 200 { - var errResp map[string]any - if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil { - logger.Error("decoding error response", "err", err.Error()) - } else { - return fmt.Errorf("token validation failure: %v", errResp) - } + if err := s.cmsClient.ValidateKey(ctx, cm); err != nil { + return fmt.Errorf("validating key: %w", err) } return nil @@ -323,10 +298,52 @@ func (s *Service) UpdateMigration(ctx context.Context, id int64, cm cloudmigrati return nil, nil } -func (s *Service) GetMigrationDataJSON(ctx context.Context, id int64) ([]byte, error) { +func (s *Service) RunMigration(ctx context.Context, id int64) (*cloudmigration.MigrateDataResponseDTO, error) { + // Get migration to read the auth token + migration, err := s.GetMigration(ctx, id) + if err != nil { + return nil, fmt.Errorf("migration get error: %w", err) + } + + // Get migration data JSON + request, err := s.getMigrationDataJSON(ctx) + if err != nil { + s.log.Error("error getting the json request body for migration run", "err", err.Error()) + return nil, fmt.Errorf("migration data get error: %w", err) + } + + // Call the cms service + resp, err := s.cmsClient.MigrateData(ctx, *migration, *request) + if err != nil { + s.log.Error("error migrating data: %w", err) + return nil, fmt.Errorf("migrate data error: %w", err) + } + + // TODO update cloud migration run schema to treat the result as a first-class citizen + respData, err := json.Marshal(resp) + if err != nil { + s.log.Error("error marshalling migration response data: %w", err) + return nil, fmt.Errorf("marshalling migration response data: %w", err) + } + + // save the result of the migration + runID, err := s.SaveMigrationRun(ctx, &cloudmigration.CloudMigrationRun{ + CloudMigrationUID: strconv.Itoa(int(id)), + Result: respData, + }) + if err != nil { + response.Error(http.StatusInternalServerError, "migration run save error", err) + } + + resp.RunID = runID + + return resp, nil +} + +func (s *Service) getMigrationDataJSON(ctx context.Context) (*cloudmigration.MigrateDataRequestDTO, error) { var migrationDataSlice []cloudmigration.MigrateDataRequestItemDTO // Data sources - dataSources, err := s.getDataSources(ctx, id) + dataSources, err := s.getDataSources(ctx) if err != nil { s.log.Error("Failed to get datasources", "err", err) return nil, err @@ -341,7 +358,7 @@ func (s *Service) GetMigrationDataJSON(ctx context.Context, id int64) ([]byte, e } // Dashboards - dashboards, err := s.getDashboards(ctx, id) + dashboards, err := s.getDashboards(ctx) if err != nil { s.log.Error("Failed to get dashboards", "err", err) return nil, err @@ -358,7 +375,7 @@ func (s *Service) GetMigrationDataJSON(ctx context.Context, id int64) ([]byte, e } // Folders - folders, err := s.getFolders(ctx, id) + folders, err := s.getFolders(ctx) if err != nil { s.log.Error("Failed to get folders", "err", err) return nil, err @@ -372,18 +389,14 @@ func (s *Service) GetMigrationDataJSON(ctx context.Context, id int64) ([]byte, e Data: f, }) } - migrationData := cloudmigration.MigrateDataRequestDTO{ + migrationData := &cloudmigration.MigrateDataRequestDTO{ Items: migrationDataSlice, } - result, err := json.Marshal(migrationData) - if err != nil { - s.log.Error("Failed to marshal datasources", "err", err) - return nil, err - } - return result, nil + + return migrationData, nil } -func (s *Service) getDataSources(ctx context.Context, id int64) ([]datasources.AddDataSourceCommand, error) { +func (s *Service) getDataSources(ctx context.Context) ([]datasources.AddDataSourceCommand, error) { dataSources, err := s.dsService.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{}) if err != nil { s.log.Error("Failed to get all datasources", "err", err) @@ -420,7 +433,7 @@ func (s *Service) getDataSources(ctx context.Context, id int64) ([]datasources.A return result, err } -func (s *Service) getFolders(ctx context.Context, id int64) ([]folder.Folder, error) { +func (s *Service) getFolders(ctx context.Context) ([]folder.Folder, error) { reqCtx := contexthandler.FromContext(ctx) folders, err := s.folderService.GetFolders(ctx, folder.GetFoldersQuery{ SignedInUser: reqCtx.SignedInUser, @@ -437,7 +450,7 @@ func (s *Service) getFolders(ctx context.Context, id int64) ([]folder.Folder, er return result, nil } -func (s *Service) getDashboards(ctx context.Context, id int64) ([]dashboards.Dashboard, error) { +func (s *Service) getDashboards(ctx context.Context) ([]dashboards.Dashboard, error) { dashs, err := s.dashboardService.GetAllDashboards(ctx) if err != nil { return nil, err @@ -471,12 +484,26 @@ func (s *Service) GetMigrationStatus(ctx context.Context, id string, runID strin return cmr, nil } -func (s *Service) GetMigrationStatusList(ctx context.Context, migrationID string) ([]*cloudmigration.CloudMigrationRun, error) { - cmrs, err := s.store.GetMigrationStatusList(ctx, migrationID) +func (s *Service) GetMigrationRunList(ctx context.Context, migrationID string) (*cloudmigration.CloudMigrationRunList, error) { + runs, err := s.store.GetMigrationStatusList(ctx, migrationID) if err != nil { return nil, fmt.Errorf("retrieving migration statuses from db: %w", err) } - return cmrs, nil + + runList := &cloudmigration.CloudMigrationRunList{Runs: []cloudmigration.MigrateDataResponseDTO{}} + for _, s := range runs { + // attempt to bind the raw result to a list of response item DTOs + r := cloudmigration.MigrateDataResponseDTO{ + Items: []cloudmigration.MigrateDataResponseItemDTO{}, + } + if err := json.Unmarshal(s.Result, &r); err != nil { + return nil, fmt.Errorf("error unmarshalling migration response items: %w", err) + } + r.RunID = s.ID + runList.Runs = append(runList.Runs, r) + } + + return runList, nil } func (s *Service) DeleteMigration(ctx context.Context, id int64) (*cloudmigration.CloudMigration, error) { @@ -487,7 +514,7 @@ func (s *Service) DeleteMigration(ctx context.Context, id int64) (*cloudmigratio return c, nil } -func (s *Service) ParseCloudMigrationConfig() (string, error) { +func (s *Service) parseCloudMigrationConfig() (string, error) { if s.cfg == nil { return "", fmt.Errorf("cfg cannot be nil") } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go index 3b10128a897..5f6e73d7f30 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go @@ -42,7 +42,7 @@ func (s *NoopServiceImpl) GetMigrationStatus(ctx context.Context, id string, run return nil, cloudmigration.ErrFeatureDisabledError } -func (s *NoopServiceImpl) GetMigrationStatusList(ctx context.Context, id string) ([]*cloudmigration.CloudMigrationRun, error) { +func (s *NoopServiceImpl) GetMigrationRunList(ctx context.Context, id string) (*cloudmigration.CloudMigrationRunList, error) { return nil, cloudmigration.ErrFeatureDisabledError } @@ -54,10 +54,6 @@ func (s *NoopServiceImpl) SaveMigrationRun(ctx context.Context, cmr *cloudmigrat return -1, cloudmigration.ErrInternalNotImplementedError } -func (s *NoopServiceImpl) GetMigrationDataJSON(ctx context.Context, id int64) ([]byte, error) { +func (s *NoopServiceImpl) RunMigration(context.Context, int64) (*cloudmigration.MigrateDataResponseDTO, error) { return nil, cloudmigration.ErrFeatureDisabledError } - -func (s *NoopServiceImpl) ParseCloudMigrationConfig() (string, error) { - return "", cloudmigration.ErrFeatureDisabledError -} diff --git a/pkg/services/cloudmigration/cmsclient/cmsclient.go b/pkg/services/cloudmigration/cmsclient/cmsclient.go new file mode 100644 index 00000000000..e614d36a4a8 --- /dev/null +++ b/pkg/services/cloudmigration/cmsclient/cmsclient.go @@ -0,0 +1,114 @@ +package cmsclient + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/cloudmigration" +) + +type Client interface { + ValidateKey(context.Context, cloudmigration.CloudMigration) error + MigrateData(context.Context, cloudmigration.CloudMigration, cloudmigration.MigrateDataRequestDTO) (*cloudmigration.MigrateDataResponseDTO, error) +} + +const logPrefix = "cloudmigration.cmsclient" + +func NewCMSClient(domain string) Client { + return &clientImpl{ + domain: domain, + log: log.New(logPrefix), + } +} + +type clientImpl struct { + domain string + log *log.ConcreteLogger +} + +func (c *clientImpl) ValidateKey(ctx context.Context, cm cloudmigration.CloudMigration) error { + logger := c.log.FromContext(ctx) + + path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/validate-key", cm.ClusterSlug, c.domain) + + // validation is an empty POST to CMS with the authorization header included + req, err := http.NewRequest("POST", path, bytes.NewReader(nil)) + if err != nil { + logger.Error("error creating http request for token validation", "err", err.Error()) + return fmt.Errorf("http request error: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", cm.StackID, cm.AuthToken)) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logger.Error("error sending http request for token validation", "err", err.Error()) + return fmt.Errorf("http request error: %w", err) + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logger.Error("closing request body", "err", err.Error()) + } + }() + + if resp.StatusCode != 200 { + var errResp map[string]any + if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil { + logger.Error("decoding error response", "err", err.Error()) + } else { + return fmt.Errorf("token validation failure: %v", errResp) + } + } + + return nil +} + +func (c *clientImpl) MigrateData(ctx context.Context, cm cloudmigration.CloudMigration, request cloudmigration.MigrateDataRequestDTO) (*cloudmigration.MigrateDataResponseDTO, error) { + logger := c.log.FromContext(ctx) + + path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/migrate-data", cm.ClusterSlug, c.domain) + + body, err := json.Marshal(request) + if err != nil { + return nil, fmt.Errorf("error marshaling request: %w", err) + } + + // Send the request to cms with the associated auth token + req, err := http.NewRequest(http.MethodPost, path, bytes.NewReader(body)) + if err != nil { + c.log.Error("error creating http request for cloud migration run", "err", err.Error()) + return nil, fmt.Errorf("http request error: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", cm.StackID, cm.AuthToken)) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + c.log.Error("error sending http request for cloud migration run", "err", err.Error()) + return nil, fmt.Errorf("http request error: %w", err) + } else if resp.StatusCode >= 400 { + c.log.Error("received error response for cloud migration run", "statusCode", resp.StatusCode) + return nil, fmt.Errorf("http request error: %w", err) + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logger.Error("closing request body: %w", err) + } + }() + + var result cloudmigration.MigrateDataResponseDTO + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + logger.Error("unmarshalling response body: %w", err) + return nil, fmt.Errorf("unmarshalling migration run response: %w", err) + } + + return &result, nil +} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index daef6b36959..d4834ab39ce 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -766,8 +766,9 @@ var ( { Name: "kubernetesPlaylists", Description: "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaAppPlatformSquad, + Expression: "true", RequiresRestart: true, // changes the API routing }, { @@ -1164,13 +1165,6 @@ var ( HideFromDocs: true, HideFromAdminPage: true, }, - { - Name: "usePrometheusFrontendPackage", - Description: "Use the @grafana/prometheus frontend package in core Prometheus.", - Stage: FeatureStageGeneralAvailability, - FrontendOnly: true, - Owner: grafanaObservabilityMetricsSquad, - }, { Name: "oauthRequireSubClaim", Description: "Require that sub claims is present in oauth tokens.", @@ -1203,6 +1197,14 @@ var ( Stage: FeatureStageExperimental, Owner: identityAccessTeam, }, + { + Name: "disableNumericMetricsSortingInExpressions", + Description: "In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels.", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaObservabilityMetricsSquad, + RequiresRestart: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index bc4043f1f78..79355f63171 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -101,7 +101,7 @@ panelMonitoring,GA,@grafana/dataviz-squad,false,false,true enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,false,false,false formatString,preview,@grafana/dataviz-squad,false,false,true transformationsVariableSupport,preview,@grafana/dataviz-squad,false,false,true -kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,false,true,false +kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesQueryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,true,true,false cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false @@ -155,8 +155,8 @@ betterPageScrolling,GA,@grafana/grafana-frontend-platform,false,false,true authAPIAccessTokenAuth,experimental,@grafana/identity-access-team,false,false,false scopeFilters,experimental,@grafana/dashboards-squad,false,false,false ssoSettingsSAML,experimental,@grafana/identity-access-team,false,false,false -usePrometheusFrontendPackage,GA,@grafana/observability-metrics,false,false,true oauthRequireSubClaim,experimental,@grafana/identity-access-team,false,false,false newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,false,false cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false accessActionSets,experimental,@grafana/identity-access-team,false,false,false +disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 753ab0885a4..389557e105a 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -631,10 +631,6 @@ const ( // Use the new SSO Settings API to configure the SAML connector FlagSsoSettingsSAML = "ssoSettingsSAML" - // FlagUsePrometheusFrontendPackage - // Use the @grafana/prometheus frontend package in core Prometheus. - FlagUsePrometheusFrontendPackage = "usePrometheusFrontendPackage" - // FlagOauthRequireSubClaim // Require that sub claims is present in oauth tokens. FlagOauthRequireSubClaim = "oauthRequireSubClaim" @@ -650,4 +646,8 @@ const ( // FlagAccessActionSets // Introduces action sets for resource permissions FlagAccessActionSets = "accessActionSets" + + // FlagDisableNumericMetricsSortingInExpressions + // In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. + FlagDisableNumericMetricsSortingInExpressions = "disableNumericMetricsSortingInExpressions" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 78ab30dc6e6..387c6e40226 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1310,12 +1310,15 @@ { "metadata": { "name": "kubernetesPlaylists", - "resourceVersion": "1712639261786", - "creationTimestamp": "2024-04-09T05:07:41Z" + "resourceVersion": "1713248683044", + "creationTimestamp": "2024-04-09T05:07:41Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-04-16 06:24:43.044143 +0000 UTC" + } }, "spec": { "description": "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", - "stage": "experimental", + "stage": "GA", "codeowner": "@grafana/grafana-app-platform-squad", "requiresRestart": true } @@ -1468,6 +1471,7 @@ "name": "usePrometheusFrontendPackage", "resourceVersion": "1712841379965", "creationTimestamp": "2024-04-09T05:07:41Z", + "deletionTimestamp": "2024-04-12T23:12:45Z", "annotations": { "grafana.app/updatedTimestamp": "2024-04-11 13:16:19.965312 +0000 UTC" } @@ -2116,6 +2120,19 @@ "stage": "experimental", "codeowner": "@grafana/identity-access-team" } + }, + { + "metadata": { + "name": "disableNumericMetricsSortingInExpressions", + "resourceVersion": "1713208725143", + "creationTimestamp": "2024-04-15T19:18:45Z" + }, + "spec": { + "description": "In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels.", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics", + "requiresRestart": true + } } ] } \ No newline at end of file diff --git a/pkg/services/grpcserver/interceptors/logging.go b/pkg/services/grpcserver/interceptors/logging.go new file mode 100644 index 00000000000..96ceda3fdb2 --- /dev/null +++ b/pkg/services/grpcserver/interceptors/logging.go @@ -0,0 +1,29 @@ +package interceptors + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "google.golang.org/grpc" +) + +func LoggingUnaryInterceptor(cfg *setting.Cfg, logger log.Logger) grpc.UnaryServerInterceptor { + return func( + ctx context.Context, + req any, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, + ) (resp any, err error) { + resp, err = handler(ctx, req) + if cfg.GRPCServerEnableLogging { + ctxLogger := logger.FromContext(ctx) + if err != nil { + ctxLogger.Error("gRPC call", "method", info.FullMethod, "req", req, "err", err) + } else { + ctxLogger.Info("gRPC call", "method", info.FullMethod, "req", req, "resp", resp) + } + } + return resp, err + } +} diff --git a/pkg/services/grpcserver/service.go b/pkg/services/grpcserver/service.go index 456a26932df..0eaf246e04d 100644 --- a/pkg/services/grpcserver/service.go +++ b/pkg/services/grpcserver/service.go @@ -73,6 +73,7 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe grpc_middleware.ChainUnaryServer( grpcAuth.UnaryServerInterceptor(authenticator.Authenticate), interceptors.TracingUnaryInterceptor(tracer), + interceptors.LoggingUnaryInterceptor(s.cfg, s.logger), // needs to be registered after tracing interceptor to get trace id middleware.UnaryServerInstrumentInterceptor(grpcRequestDuration), ), ), diff --git a/pkg/services/ldap/api/service.go b/pkg/services/ldap/api/service.go index 2d5de2a1da2..368e3acff6b 100644 --- a/pkg/services/ldap/api/service.go +++ b/pkg/services/ldap/api/service.go @@ -212,7 +212,8 @@ func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Resp return response.Error(http.StatusBadRequest, errMsg, err) } - if err := s.userService.Disable(c.Req.Context(), &user.DisableUserCommand{IsDisabled: true, UserID: usr.ID}); err != nil { + isDisabled := true + if err := s.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: usr.ID, IsDisabled: &isDisabled}); err != nil { return response.Error(http.StatusInternalServerError, "Failed to disable the user", err) } @@ -331,9 +332,6 @@ func (s *Service) identityFromLDAPUser(user *login.ExternalUserInfo) *authn.Iden EnableUser: true, SyncOrgRoles: !s.cfg.LDAPSkipOrgRoleSync, AllowSignUp: s.cfg.LDAPAllowSignup, - LookUpParams: login.UserLookupParams{ - UserID: &user.UserId, - }, }, } } diff --git a/pkg/services/login/model.go b/pkg/services/login/model.go index 3ec4b55d99e..b11ec2161f3 100644 --- a/pkg/services/login/model.go +++ b/pkg/services/login/model.go @@ -104,9 +104,8 @@ type GetUserByAuthInfoQuery struct { type UserLookupParams struct { // Describes lookup order as well - UserID *int64 // if set, will try to find the user by id - Email *string // if set, will try to find the user by email - Login *string // if set, will try to find the user by login + Email *string // if set, will try to find the user by email + Login *string // if set, will try to find the user by login } type GetAuthInfoQuery struct { diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index ceb2e3c882b..8ac14a765fb 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -288,6 +288,7 @@ func (s *ServiceImpl) readNavigationSettings() { "grafana-app-observability-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApplication, Text: "Application", Icon: "graph-bar"}, "grafana-pyroscope-app": {SectionID: navtree.NavIDExplore, SortWeight: 1, Text: "Profiles"}, "grafana-lokiexplore-app": {SectionID: navtree.NavIDExplore, SortWeight: 2, Text: "Logs"}, + "grafana-exploretraces-app": {SectionID: navtree.NavIDExplore, SortWeight: 3, Text: "Traces"}, "grafana-kowalski-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightFrontend, Text: "Frontend", Icon: "frontend-observability"}, "grafana-synthetic-monitoring-app": {SectionID: navtree.NavIDTestingAndSynthetics, SortWeight: 2, Text: "Synthetics"}, "grafana-oncall-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 1, Text: "OnCall"}, diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 8143a6eafa4..9d0f062d2ad 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -192,7 +192,7 @@ func (srv *ProvisioningSrv) RoutePutContactPoint(c *contextmodel.ReqContext, cp func (srv *ProvisioningSrv) RouteDeleteContactPoint(c *contextmodel.ReqContext, UID string) response.Response { err := srv.contactPointService.DeleteContactPoint(c.Req.Context(), c.SignedInUser.GetOrgID(), UID) if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "failed to delete contact point", err) } return response.JSON(http.StatusAccepted, util.DynMap{"message": "contactpoint deleted"}) } diff --git a/pkg/services/ngalert/api/api_ruler_validation_test.go b/pkg/services/ngalert/api/api_ruler_validation_test.go index 31fd3bc210f..f2332211129 100644 --- a/pkg/services/ngalert/api/api_ruler_validation_test.go +++ b/pkg/services/ngalert/api/api_ruler_validation_test.go @@ -827,14 +827,12 @@ func TestValidateRuleNodeNotificationSettings(t *testing.T) { notificationSettings: models.CopyNotificationSettings(validNotificationSettings(), models.NSMuts.WithGroupBy(model.AlertNameLabel, models.FolderTitleLabel)), }, { - name: "group by missing alert name label is invalid", + name: "group by missing alert name label is valid", notificationSettings: models.CopyNotificationSettings(validNotificationSettings(), models.NSMuts.WithGroupBy(models.FolderTitleLabel)), - expErrorContains: model.AlertNameLabel, }, { - name: "group by missing folder name label is invalid", + name: "group by missing folder name label is valid", notificationSettings: models.CopyNotificationSettings(validNotificationSettings(), models.NSMuts.WithGroupBy(model.AlertNameLabel)), - expErrorContains: models.FolderTitleLabel, }, { name: "group wait empty is valid", diff --git a/pkg/services/ngalert/models/notifications.go b/pkg/services/ngalert/models/notifications.go index 3271eb6d0ba..8d40c5d88b1 100644 --- a/pkg/services/ngalert/models/notifications.go +++ b/pkg/services/ngalert/models/notifications.go @@ -3,7 +3,6 @@ package models import ( "encoding/binary" "errors" - "fmt" "hash/fnv" "slices" "unsafe" @@ -12,8 +11,11 @@ import ( "github.com/prometheus/common/model" ) -// groupByAll is a special value defined by alertmanager that can be used in a Route's GroupBy field to aggregate by all possible labels. -const groupByAll = "..." +// GroupByAll is a special value defined by alertmanager that can be used in a Route's GroupBy field to aggregate by all possible labels. +const GroupByAll = "..." + +// DefaultNotificationSettingsGroupBy are the default required GroupBy fields for notification settings. +var DefaultNotificationSettingsGroupBy = []string{FolderTitleLabel, model.AlertNameLabel} type ListNotificationSettingsQuery struct { OrgID int64 @@ -32,33 +34,50 @@ type NotificationSettings struct { MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"` } +// NormalizedGroupBy returns a consistent and ordered GroupBy. +// - If the GroupBy is empty, it returns nil so that the parent group can be inherited. +// - If the GroupBy contains the special label '...', it returns only '...'. +// - Otherwise, it returns the default GroupBy labels followed by any custom labels in sorted order. +// +// To ensure consistent and valid generated routes, this should be used instead of GroupBy when generating fingerprints +// or fingerprint-level routes. +func (s *NotificationSettings) NormalizedGroupBy() []string { + if len(s.GroupBy) == 0 { + // Inherit group from parent. + return nil + } + + defaultGroupBySet := make(map[string]struct{}, len(DefaultNotificationSettingsGroupBy)) + for _, lbl := range DefaultNotificationSettingsGroupBy { + defaultGroupBySet[lbl] = struct{}{} + } + + var customLabels []string + for _, lbl := range s.GroupBy { + if lbl == GroupByAll { + return []string{GroupByAll} + } + if _, ok := defaultGroupBySet[lbl]; !ok { + customLabels = append(customLabels, lbl) + } + } + + // Sort the custom labels to ensure consistent ordering while keeping the required labels in the front. + slices.Sort(customLabels) + + normalized := make([]string, 0, len(DefaultNotificationSettingsGroupBy)+len(customLabels)) + normalized = append(normalized, DefaultNotificationSettingsGroupBy...) + return append(normalized, customLabels...) +} + // Validate checks if the NotificationSettings object is valid. // It returns an error if any of the validation checks fail. // The receiver must be specified. -// If GroupBy is not empty, it must contain both model.AlertNameLabel and FolderTitleLabel or the special label '...'. // GroupWait, GroupInterval, RepeatInterval must be positive durations. func (s *NotificationSettings) Validate() error { if s.Receiver == "" { return errors.New("receiver must be specified") } - if len(s.GroupBy) > 0 { - alertName, folderTitle := false, false - for _, lbl := range s.GroupBy { - if lbl == groupByAll { - alertName, folderTitle = true, true - break - } - if lbl == model.AlertNameLabel { - alertName = true - } - if lbl == FolderTitleLabel { - folderTitle = true - } - } - if !alertName || !folderTitle { - return fmt.Errorf("group by override must contain two required labels: '%s' and '%s' or '...' (group by all)", model.AlertNameLabel, FolderTitleLabel) - } - } if s.GroupWait != nil && *s.GroupWait < 0 { return errors.New("group wait must be a positive duration") } @@ -153,8 +172,7 @@ func (s *NotificationSettings) Fingerprint() data.Fingerprint { } writeString(s.Receiver) - // TODO: Should we sort the group by labels? - for _, gb := range s.GroupBy { + for _, gb := range s.NormalizedGroupBy() { writeString(gb) } writeDuration(s.GroupWait) diff --git a/pkg/services/ngalert/models/notifications_test.go b/pkg/services/ngalert/models/notifications_test.go index 34cd1b034c7..14e3a3e89bb 100644 --- a/pkg/services/ngalert/models/notifications_test.go +++ b/pkg/services/ngalert/models/notifications_test.go @@ -41,14 +41,12 @@ func TestValidate(t *testing.T) { notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(model.AlertNameLabel, FolderTitleLabel)), }, { - name: "group by missing alert name label is invalid", + name: "group by missing alert name label is valid", notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(FolderTitleLabel)), - expErrorContains: model.AlertNameLabel, }, { - name: "group by missing folder name label is invalid", + name: "group by missing folder name label is valid", notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(model.AlertNameLabel)), - expErrorContains: FolderTitleLabel, }, { name: "group wait empty is valid", @@ -118,6 +116,18 @@ func TestNotificationSettingsLabels(t *testing.T) { AutogeneratedRouteReceiverNameLabel: "receiver name", }, }, + { + name: "default notification settings with hardcoded default group by", + notificationSettings: NotificationSettings{ + Receiver: "receiver name", + GroupBy: DefaultNotificationSettingsGroupBy, + }, + labels: data.Labels{ + AutogeneratedRouteLabel: "true", + AutogeneratedRouteReceiverNameLabel: "receiver name", + AutogeneratedRouteSettingsHashLabel: "6027cdeaff62ba3f", + }, + }, { name: "custom notification settings", notificationSettings: NotificationSettings{ @@ -131,7 +141,7 @@ func TestNotificationSettingsLabels(t *testing.T) { labels: data.Labels{ AutogeneratedRouteLabel: "true", AutogeneratedRouteReceiverNameLabel: "receiver name", - AutogeneratedRouteSettingsHashLabel: "f0e23250cefc4a31", + AutogeneratedRouteSettingsHashLabel: "47164c92f2986a35", }, }, } @@ -143,3 +153,60 @@ func TestNotificationSettingsLabels(t *testing.T) { }) } } + +func TestNormalizedGroupBy(t *testing.T) { + validNotificationSettings := NotificationSettingsGen() + + testCases := []struct { + name string + notificationSettings NotificationSettings + expectedNormalizedGroupBy []string + }{ + { + name: "default group by is normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(DefaultNotificationSettingsGroupBy...)), + expectedNormalizedGroupBy: DefaultNotificationSettingsGroupBy, + }, + { + name: "group by ... is normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(GroupByAll)), + expectedNormalizedGroupBy: []string{GroupByAll}, + }, + { + name: "group by empty is normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy()), + }, + { + name: "group by with ... and other labels is not normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(FolderTitleLabel, model.AlertNameLabel, GroupByAll)), + expectedNormalizedGroupBy: []string{GroupByAll}, + }, + { + name: "group by missing alert name label is not normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(FolderTitleLabel)), + expectedNormalizedGroupBy: DefaultNotificationSettingsGroupBy, + }, + { + name: "group by missing folder name label is not normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(model.AlertNameLabel)), + expectedNormalizedGroupBy: DefaultNotificationSettingsGroupBy, + }, + { + name: "group by with all required labels plus extra is normal", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy(FolderTitleLabel, model.AlertNameLabel, "custom")), + expectedNormalizedGroupBy: []string{FolderTitleLabel, model.AlertNameLabel, "custom"}, + }, + { + name: "ensure consistent ordering, required labels in front followed by sorted custom labels ", + notificationSettings: CopyNotificationSettings(validNotificationSettings(), NSMuts.WithGroupBy("custom", model.AlertNameLabel, "something", FolderTitleLabel, "other")), + expectedNormalizedGroupBy: []string{FolderTitleLabel, model.AlertNameLabel, "custom", "other", "something"}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + normalized := tt.notificationSettings.NormalizedGroupBy() + require.Equal(t, normalized, tt.expectedNormalizedGroupBy) + }) + } +} diff --git a/pkg/services/ngalert/notifier/autogen_alertmanager.go b/pkg/services/ngalert/notifier/autogen_alertmanager.go index af5de7947a5..003ff617e33 100644 --- a/pkg/services/ngalert/notifier/autogen_alertmanager.go +++ b/pkg/services/ngalert/notifier/autogen_alertmanager.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/prometheus/alertmanager/pkg/labels" - "github.com/prometheus/common/model" "golang.org/x/exp/maps" "github.com/grafana/grafana/pkg/infra/log" @@ -121,7 +120,7 @@ func generateRouteFromSettings(defaultReceiver string, settings map[data.Fingerp ObjectMatchers: definitions.ObjectMatchers{contactMatcher}, Continue: false, // Since we'll have many rules from different folders using this policy, we ensure it has these necessary groupings. - GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel}, + GroupByStr: append([]string{}, models.DefaultNotificationSettingsGroupBy...), } receiverRoutes[s.Receiver] = receiverRoute autoGenRoot.Routes = append(autoGenRoot.Routes, receiverRoute) @@ -140,7 +139,7 @@ func generateRouteFromSettings(defaultReceiver string, settings map[data.Fingerp ObjectMatchers: definitions.ObjectMatchers{settingMatcher}, Continue: false, // Only a single setting-specific route should match. - GroupByStr: s.GroupBy, // Note: in order to pass validation at least FolderTitleLabel and AlertNameLabel are always included. + GroupByStr: s.NormalizedGroupBy(), MuteTimeIntervals: s.MuteTimeIntervals, GroupWait: s.GroupWait, GroupInterval: s.GroupInterval, diff --git a/pkg/services/ngalert/notifier/autogen_alertmanager_test.go b/pkg/services/ngalert/notifier/autogen_alertmanager_test.go index 6ac486dbeb3..14e50e844ff 100644 --- a/pkg/services/ngalert/notifier/autogen_alertmanager_test.go +++ b/pkg/services/ngalert/notifier/autogen_alertmanager_test.go @@ -134,17 +134,17 @@ func TestAddAutogenConfig(t *testing.T) { MuteTimeIntervals: []string{"maintenance"}, }), withChildRoutes(basicContactRoute("receiver1"), &definitions.Route{ - Receiver: "receiver1", - ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "dde34b8127e68f31"), - GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), - }, &definitions.Route{ Receiver: "receiver1", - ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "ed4038c5d6733607"), - GroupByStr: []string{model.AlertNameLabel, models.FolderTitleLabel, "custom"}, + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "4f095749ddf3eeeb"), + GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel, "custom"}, GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), GroupWait: util.Pointer(model.Duration(2 * time.Minute)), RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)), MuteTimeIntervals: []string{"maintenance"}, + }, &definitions.Route{ + Receiver: "receiver1", + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "dde34b8127e68f31"), + GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), }), withChildRoutes(basicContactRoute("receiver2"), &definitions.Route{ Receiver: "receiver2", @@ -153,8 +153,8 @@ func TestAddAutogenConfig(t *testing.T) { }), withChildRoutes(basicContactRoute("receiver4"), &definitions.Route{ Receiver: "receiver4", - ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "5e5ab8d592b12e86"), - GroupByStr: []string{model.AlertNameLabel, models.FolderTitleLabel, "custom"}, + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "b3a2fa5e615dcc7e"), + GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel, "custom"}, }), withChildRoutes(basicContactRoute("receiver3"), &definitions.Route{ Receiver: "receiver3", @@ -164,6 +164,71 @@ func TestAddAutogenConfig(t *testing.T) { }, }), }, + { + name: "settings with custom options and nil groupBy, groupBy should inherit from parent", + existingConfig: configGen([]string{"receiver1"}, nil), + storeSettings: []models.NotificationSettings{ + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy()), + }, + expRoute: withChildRoutes(rootRoute(), &definitions.Route{ + Receiver: "default", + ObjectMatchers: matcher(models.AutogeneratedRouteLabel, "true"), + Routes: []*definitions.Route{ + withChildRoutes(basicContactRoute("receiver1"), &definitions.Route{ + Receiver: "receiver1", + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "dde34b8127e68f31"), + GroupByStr: nil, + GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), + }), + }, + }), + }, + { + name: "settings with nil groupBy should have different fingerprint than default groupBy", + existingConfig: configGen([]string{"receiver1"}, nil), + storeSettings: []models.NotificationSettings{ + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy()), + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy(models.DefaultNotificationSettingsGroupBy...)), + }, + expRoute: withChildRoutes(rootRoute(), &definitions.Route{ + Receiver: "default", + ObjectMatchers: matcher(models.AutogeneratedRouteLabel, "true"), + Routes: []*definitions.Route{ + withChildRoutes(basicContactRoute("receiver1"), &definitions.Route{ + Receiver: "receiver1", + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "dde34b8127e68f31"), + GroupByStr: nil, + GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), + }, &definitions.Route{ + Receiver: "receiver1", + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "e1f3a275a8918385"), // Different hash. + GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel}, + GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), + }), + }, + }), + }, + { + name: "settings with incomplete required groupBy labels will be completed and should have the same fingerprint", + existingConfig: configGen([]string{"receiver1"}, nil), + storeSettings: []models.NotificationSettings{ + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy(models.FolderTitleLabel)), + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy(model.AlertNameLabel)), + models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute)), models.NSMuts.WithGroupBy(models.DefaultNotificationSettingsGroupBy...)), + }, + expRoute: withChildRoutes(rootRoute(), &definitions.Route{ + Receiver: "default", + ObjectMatchers: matcher(models.AutogeneratedRouteLabel, "true"), + Routes: []*definitions.Route{ + withChildRoutes(basicContactRoute("receiver1"), &definitions.Route{ + Receiver: "receiver1", + ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "e1f3a275a8918385"), + GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel}, + GroupInterval: util.Pointer(model.Duration(1 * time.Minute)), + }), + }, + }), + }, { name: "when skipInvalid=true, invalid settings are skipped", existingConfig: configGen([]string{"receiver1", "receiver2", "receiver3"}, nil), diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 37a3c03925a..02eeb66978d 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -338,7 +338,7 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in } } if fullRemoval && isContactPointInUse(name, []*apimodels.Route{revision.cfg.AlertmanagerConfig.Route}) { - return fmt.Errorf("contact point '%s' is currently used by a notification policy", name) + return ErrContactPointReferenced } return ecp.xact.InTransaction(ctx, func(ctx context.Context) error { diff --git a/pkg/services/ngalert/provisioning/errors.go b/pkg/services/ngalert/provisioning/errors.go index 31e50224ffe..33ab23544e6 100644 --- a/pkg/services/ngalert/provisioning/errors.go +++ b/pkg/services/ngalert/provisioning/errors.go @@ -19,6 +19,8 @@ var ( ErrTimeIntervalExists = errutil.BadRequest("alerting.notifications.time-intervals.nameExists", errutil.WithPublicMessage("Time interval with this name already exists. Use a different name or update existing one.")) ErrTimeIntervalInvalid = errutil.BadRequest("alerting.notifications.time-intervals.invalidFormat").MustTemplate("Invalid format of the submitted time interval", errutil.WithPublic("Time interval is in invalid format. Correct the payload and try again.")) ErrTimeIntervalInUse = errutil.Conflict("alerting.notifications.time-intervals.used", errutil.WithPublicMessage("Time interval is used by one or many notification policies")) + + ErrContactPointReferenced = errutil.BadRequest("alerting.notifications.contact-points.referenced", errutil.WithPublicMessage("Contact point is currently referenced by a notification policy.")) ) func makeErrBadAlertmanagerConfiguration(err error) error { diff --git a/pkg/services/org/orgimpl/store_test.go b/pkg/services/org/orgimpl/store_test.go index a25cb650b6e..1c62a471641 100644 --- a/pkg/services/org/orgimpl/store_test.go +++ b/pkg/services/org/orgimpl/store_test.go @@ -773,7 +773,7 @@ func TestIntegration_SQLStore_GetOrgUsers_PopulatesCorrectly(t *testing.T) { assert.Equal(t, int64(1), actual.UserID) assert.Equal(t, "viewer@localhost", actual.Email) assert.Equal(t, "Viewer Localhost", actual.Name) - assert.Equal(t, "Viewer", actual.Login) + assert.Equal(t, "viewer", actual.Login) assert.Equal(t, "Viewer", actual.Role) assert.Equal(t, constNow.AddDate(-10, 0, 0), actual.LastSeenAt) assert.Equal(t, constNow, actual.Created) diff --git a/pkg/services/pluginsintegration/loader/fakes.go b/pkg/services/pluginsintegration/loader/fakes.go index 108882835ab..2a6809deb3c 100644 --- a/pkg/services/pluginsintegration/loader/fakes.go +++ b/pkg/services/pluginsintegration/loader/fakes.go @@ -36,3 +36,7 @@ func (t *fakeErrorTracker) Errors(ctx context.Context) []*plugins.Error { } return nil } + +func (t *fakeErrorTracker) Error(ctx context.Context, pluginID string) *plugins.Error { + return &plugins.Error{} +} diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 8a49857047a..f45cb3bfd76 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -65,10 +65,10 @@ func ProvideInitializationStage(cfg *config.PluginManagementCfg, pr registry.Ser InitializeFuncs: []initialization.InitializeFunc{ ExternalServiceRegistrationStep(cfg, externalServiceRegistry, tracer), initialization.BackendClientInitStep(pluginEnvProvider, bp), - initialization.PluginRegistrationStep(pr), initialization.BackendProcessStartStep(pm), RegisterPluginRolesStep(roleRegistry), ReportBuildMetrics, + initialization.PluginRegistrationStep(pr), }, }) } diff --git a/pkg/services/pluginsintegration/pipeline/steps.go b/pkg/services/pluginsintegration/pipeline/steps.go index 85adcc6c60d..5401323d41b 100644 --- a/pkg/services/pluginsintegration/pipeline/steps.go +++ b/pkg/services/pluginsintegration/pipeline/steps.go @@ -89,6 +89,7 @@ func newRegisterPluginRoles(registry plugins.RoleRegistry) *RegisterPluginRoles func (r *RegisterPluginRoles) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { if err := r.roleRegistry.DeclarePluginRoles(ctx, p.ID, p.Name, p.Roles); err != nil { r.log.Warn("Declare plugin roles failed.", "pluginId", p.ID, "error", err) + return nil, err } return p, nil } diff --git a/pkg/services/pluginsintegration/pluginerrs/errors.go b/pkg/services/pluginsintegration/pluginerrs/errors.go index 3cf2a08e090..026a0a121af 100644 --- a/pkg/services/pluginsintegration/pluginerrs/errors.go +++ b/pkg/services/pluginsintegration/pluginerrs/errors.go @@ -28,6 +28,16 @@ func (s *Store) PluginErrors(ctx context.Context) []*plugins.Error { return errs } +func (s *Store) PluginError(ctx context.Context, pluginID string) *plugins.Error { + err := s.errs.Error(ctx, pluginID) + if err == nil { + return nil + } + + err.ErrorCode = err.AsErrorCode() + return err +} + type ErrorRegistry struct { errs map[string]*plugins.Error log log.Logger @@ -37,6 +47,7 @@ type ErrorTracker interface { Record(ctx context.Context, err *plugins.Error) Clear(ctx context.Context, pluginID string) Errors(ctx context.Context) []*plugins.Error + Error(ctx context.Context, pluginID string) *plugins.Error } func ProvideErrorTracker() *ErrorRegistry { @@ -65,3 +76,7 @@ func (r *ErrorRegistry) Errors(_ context.Context) []*plugins.Error { } return errs } + +func (r *ErrorRegistry) Error(_ context.Context, pluginID string) *plugins.Error { + return r.errs[pluginID] +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index d1c66575964..59faffe6db4 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -382,7 +382,7 @@ func (ss *SQLStore) RecursiveQueriesAreSupported() (bool, error) { }); err != nil { var driverErr *mysql.MySQLError if errors.As(err, &driverErr) { - if driverErr.Number == mysqlerr.ER_PARSE_ERROR { + if driverErr.Number == mysqlerr.ER_PARSE_ERROR || driverErr.Number == mysqlerr.ER_NOT_SUPPORTED_YET { return false, nil } } diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index a592ca9b99c..fbc6e06c3cd 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -501,7 +501,7 @@ func overrideMaps(maps ...map[string]any) map[string]any { } func isSecret(fieldName string) bool { - secretFieldPatterns := []string{"secret"} + secretFieldPatterns := []string{"secret", "private", "certificate"} for _, v := range secretFieldPatterns { if strings.Contains(strings.ToLower(fieldName), strings.ToLower(v)) { diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 6bd7206c719..8d576ce06f4 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -1309,16 +1309,22 @@ func TestService_decryptSecrets(t *testing.T) { setup: func(env testEnv) { env.secrets.On("Decrypt", mock.Anything, []byte("client_secret"), mock.Anything).Return([]byte("decrypted-client-secret"), nil).Once() env.secrets.On("Decrypt", mock.Anything, []byte("other_secret"), mock.Anything).Return([]byte("decrypted-other-secret"), nil).Once() + env.secrets.On("Decrypt", mock.Anything, []byte("private_key"), mock.Anything).Return([]byte("decrypted-private-key"), nil).Once() + env.secrets.On("Decrypt", mock.Anything, []byte("certificate"), mock.Anything).Return([]byte("decrypted-certificate"), nil).Once() }, settings: map[string]any{ "enabled": true, "client_secret": base64.RawStdEncoding.EncodeToString([]byte("client_secret")), "other_secret": base64.RawStdEncoding.EncodeToString([]byte("other_secret")), + "private_key": base64.RawStdEncoding.EncodeToString([]byte("private_key")), + "certificate": base64.RawStdEncoding.EncodeToString([]byte("certificate")), }, want: map[string]any{ "enabled": true, "client_secret": "decrypted-client-secret", "other_secret": "decrypted-other-secret", + "private_key": "decrypted-private-key", + "certificate": "decrypted-certificate", }, }, { @@ -1356,7 +1362,7 @@ func TestService_decryptSecrets(t *testing.T) { wantErr: true, }, { - name: "should return an error decryption fails", + name: "should return an error if decryption fails", setup: func(env testEnv) { env.secrets.On("Decrypt", mock.Anything, []byte("client_secret"), mock.Anything).Return(nil, errors.New("decryption failed")).Once() }, diff --git a/pkg/services/store/entity/server/service.go b/pkg/services/store/entity/server/service.go index 7e315a5777b..03961dd5ea9 100644 --- a/pkg/services/store/entity/server/service.go +++ b/pkg/services/store/entity/server/service.go @@ -64,6 +64,7 @@ func ProvideService( if err != nil { return nil, err } + tracingCfg.ServiceName = "unified-storage" tracing, err := tracing.ProvideService(tracingCfg) if err != nil { @@ -114,7 +115,7 @@ func (s *service) start(ctx context.Context) error { return err } - store, err := sqlstash.ProvideSQLEntityServer(eDB) + store, err := sqlstash.ProvideSQLEntityServer(eDB, s.tracing) if err != nil { return err } diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server.go b/pkg/services/store/entity/sqlstash/sql_storage_server.go index 7c89961dd5a..46339ae2e35 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server.go @@ -20,22 +20,30 @@ import ( folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/store/entity/db" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) const entityTable = "entity" const entityHistoryTable = "entity_history" +var ( + errorUserNotFoundInContext = errors.New("can not find user in context") + errorNextPageTokenNotSupported = errors.New("nextPageToken not yet supported") + errorEntityAlreadyExists = errors.New("entity already exists") +) + // Make sure we implement correct interfaces var _ entity.EntityStoreServer = &sqlEntityServer{} -var _ SqlEntityServer = &sqlEntityServer{} -func ProvideSQLEntityServer(db db.EntityDBInterface /*, cfg *setting.Cfg */) (SqlEntityServer, error) { +func ProvideSQLEntityServer(db db.EntityDBInterface, tracer tracing.Tracer /*, cfg *setting.Cfg */) (SqlEntityServer, error) { ctx, cancel := context.WithCancel(context.Background()) entityServer := &sqlEntityServer{ @@ -43,6 +51,7 @@ func ProvideSQLEntityServer(db db.EntityDBInterface /*, cfg *setting.Cfg */) (Sq log: log.New("sql-entity-server"), ctx: ctx, cancel: cancel, + tracer: tracer, } if err := prometheus.Register(NewStorageMetrics()); err != nil { @@ -69,6 +78,7 @@ type sqlEntityServer struct { ctx context.Context cancel context.CancelFunc stream chan *entity.EntityWatchResponse + tracer tracing.Tracer } func (s *sqlEntityServer) Init() error { @@ -224,14 +234,26 @@ func readEntity(rows *sql.Rows, r FieldSelectRequest) (*entity.Entity, error) { } func (s *sqlEntityServer) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.Entity, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Read") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "read"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } - return s.read(ctx, s.sess, r) + res, err := s.read(ctx, s.sess, r) + if err != nil { + ctxLogger.Error("read error", "error", err) + } + return res, err } func (s *sqlEntityServer) read(ctx context.Context, tx session.SessionQuerier, r *entity.ReadEntityRequest) (*entity.Entity, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.read") + defer span.End() + table := entityTable where := []string{} args := []any{} @@ -288,7 +310,12 @@ func (s *sqlEntityServer) read(ctx context.Context, tx session.SessionQuerier, r //nolint:gocyclo func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequest) (*entity.CreateEntityResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Create") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "create"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } @@ -301,10 +328,12 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ if createdBy == "" { modifier, err := appcontext.User(ctx) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return nil, err } if modifier == nil { - return nil, fmt.Errorf("can not find user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return nil, err } createdBy = store.GetUserIDString(modifier) } @@ -329,7 +358,8 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ // if we found an existing entity if current.Guid != "" { - return fmt.Errorf("entity already exists") + ctxLogger.Error("entity already exists", "error", errorEntityAlreadyExists) + return errorEntityAlreadyExists } // generate guid for new entity @@ -342,6 +372,7 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ // parse provided key key, err := entity.ParseKey(r.Entity.Key) if err != nil { + ctxLogger.Error("error parsing key", "error", err) return err } @@ -387,21 +418,21 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ labels, err := json.Marshal(r.Entity.Labels) if err != nil { - s.log.Error("error marshalling labels", "msg", err.Error()) + ctxLogger.Error("error marshalling labels", "msg", err.Error()) return err } current.Labels = r.Entity.Labels fields, err := json.Marshal(r.Entity.Fields) if err != nil { - s.log.Error("error marshalling fields", "msg", err.Error()) + ctxLogger.Error("error marshalling fields", "msg", err.Error()) return err } current.Fields = r.Entity.Fields errors, err := json.Marshal(r.Entity.Errors) if err != nil { - s.log.Error("error marshalling errors", "msg", err.Error()) + ctxLogger.Error("error marshalling errors", "msg", err.Error()) return err } current.Errors = r.Entity.Errors @@ -465,14 +496,14 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ } // 1. Add row to the `entity_history` values - if err := s.dialect.Insert(ctx, tx, entityHistoryTable, values); err != nil { - s.log.Error("error inserting entity history", "msg", err.Error()) + if err = s.insert(ctx, tx, entityHistoryTable, values); err != nil { + ctxLogger.Error("insert entity_history error", "error", err) return err } // 2. Add row to the main `entity` table - if err := s.dialect.Insert(ctx, tx, entityTable, values); err != nil { - s.log.Error("error inserting entity", "msg", err.Error()) + if err = s.insert(ctx, tx, entityTable, values); err != nil { + ctxLogger.Error("insert entity error", "error", err) return err } @@ -482,7 +513,7 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ case folder.RESOURCE: err = s.updateFolderTree(ctx, tx, current.Namespace) if err != nil { - s.log.Error("error updating folder tree", "msg", err.Error()) + ctxLogger.Error("error updating folder tree", "error", err.Error()) return err } } @@ -493,7 +524,7 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ return s.setLabels(ctx, tx, current.Guid, current.Labels) }) if err != nil { - s.log.Error("error creating entity", "msg", err.Error()) + ctxLogger.Error("error creating entity", "msg", err.Error()) rsp.Status = entity.CreateEntityResponse_ERROR } @@ -508,7 +539,12 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ //nolint:gocyclo func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequest) (*entity.UpdateEntityResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Update") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "update"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } @@ -521,10 +557,12 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ if updatedBy == "" { modifier, err := appcontext.User(ctx) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return nil, err } if modifier == nil { - return nil, fmt.Errorf("can not find user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return nil, errorUserNotFoundInContext } updatedBy = store.GetUserIDString(modifier) } @@ -606,21 +644,21 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ labels, err := json.Marshal(r.Entity.Labels) if err != nil { - s.log.Error("error marshalling labels", "msg", err.Error()) + ctxLogger.Error("error marshalling labels", "msg", err.Error()) return err } updated.Labels = r.Entity.Labels fields, err := json.Marshal(r.Entity.Fields) if err != nil { - s.log.Error("error marshalling fields", "msg", err.Error()) + ctxLogger.Error("error marshalling fields", "msg", err.Error()) return err } updated.Fields = r.Entity.Fields errors, err := json.Marshal(r.Entity.Errors) if err != nil { - s.log.Error("error marshalling errors", "msg", err.Error()) + ctxLogger.Error("error marshalling errors", "msg", err.Error()) return err } updated.Errors = r.Entity.Errors @@ -686,8 +724,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ } // 1. Add the `entity_history` values - if err := s.dialect.Insert(ctx, tx, entityHistoryTable, values); err != nil { - s.log.Error("error inserting entity history", "msg", err.Error()) + if err := s.insert(ctx, tx, entityHistoryTable, values); err != nil { return err } @@ -703,7 +740,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ delete(values, "created_at") delete(values, "created_by") - err = s.dialect.Update( + err = s.update( ctx, tx, entityTable, @@ -713,7 +750,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ }, ) if err != nil { - s.log.Error("error updating entity", "msg", err.Error()) + ctxLogger.Error("error updating entity", "error", err.Error()) return err } @@ -723,7 +760,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ case folder.RESOURCE: err = s.updateFolderTree(ctx, tx, updated.Namespace) if err != nil { - s.log.Error("error updating folder tree", "msg", err.Error()) + ctxLogger.Error("error updating folder tree", "msg", err.Error()) return err } } @@ -734,7 +771,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ return s.setLabels(ctx, tx, updated.Guid, updated.Labels) }) if err != nil { - s.log.Error("error updating entity", "msg", err.Error()) + ctxLogger.Error("error updating entity", "msg", err.Error()) rsp.Status = entity.UpdateEntityResponse_ERROR } @@ -750,6 +787,9 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ } func (s *sqlEntityServer) setLabels(ctx context.Context, tx *session.SessionTx, guid string, labels map[string]string) error { + ctx, span := s.tracer.Start(ctx, "storage_server.setLabels") + defer span.End() + s.log.Debug("setLabels", "guid", guid, "labels", labels) // Clear the old labels @@ -781,7 +821,12 @@ func (s *sqlEntityServer) setLabels(ctx context.Context, tx *session.SessionTx, } func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequest) (*entity.DeleteEntityResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Delete") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "delete"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } @@ -826,6 +871,9 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ rsp.Status = entity.DeleteEntityResponse_DELETED return nil }) + if err != nil { + ctxLogger.Error("delete error", "error", err) + } if rsp.Status == entity.DeleteEntityResponse_DELETED { // k8s expects us to return the entity as it was before the deletion, but with the updated RV @@ -846,6 +894,10 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ } func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, ent *entity.Entity) (*entity.Entity, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.doDelete") + defer span.End() + ctxLogger := s.log.FromContext(ctx) + updated := proto.Clone(ent).(*entity.Entity) // Update resource version @@ -864,19 +916,19 @@ func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, e labels, err := json.Marshal(updated.Labels) if err != nil { - s.log.Error("error marshalling labels", "msg", err.Error()) + ctxLogger.Error("error marshalling labels", "msg", err.Error()) return nil, err } fields, err := json.Marshal(updated.Fields) if err != nil { - s.log.Error("error marshalling fields", "msg", err.Error()) + ctxLogger.Error("error marshalling fields", "msg", err.Error()) return nil, err } errors, err := json.Marshal(updated.Errors) if err != nil { - s.log.Error("error marshalling errors", "msg", err.Error()) + ctxLogger.Error("error marshalling errors", "msg", err.Error()) return nil, err } @@ -922,21 +974,17 @@ func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, e } // 1. Add the `entity_history` values - if err := s.dialect.Insert(ctx, tx, entityHistoryTable, values); err != nil { - s.log.Error("error inserting entity history", "msg", err.Error()) + if err := s.insert(ctx, tx, entityHistoryTable, values); err != nil { return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity WHERE guid=?", updated.Guid) - if err != nil { + if err = s.exec(ctx, tx, "DELETE FROM entity WHERE guid=?", updated.Guid); err != nil { return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity_labels WHERE guid=?", updated.Guid) - if err != nil { + if err = s.exec(ctx, tx, "DELETE FROM entity_labels WHERE guid=?", updated.Guid); err != nil { return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity_ref WHERE guid=?", updated.Guid) - if err != nil { + if err = s.exec(ctx, tx, "DELETE FROM entity_ref WHERE guid=?", updated.Guid); err != nil { return nil, err } @@ -956,22 +1004,36 @@ func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, e } func (s *sqlEntityServer) History(ctx context.Context, r *entity.EntityHistoryRequest) (*entity.EntityHistoryResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.History") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "history"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } user, err := appcontext.User(ctx) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return nil, err } if user == nil { - return nil, fmt.Errorf("missing user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return nil, errorUserNotFoundInContext } - return s.history(ctx, r) + res, err := s.history(ctx, r) + if err != nil { + ctxLogger.Error("history error", "error", err) + } + return res, err } func (s *sqlEntityServer) history(ctx context.Context, r *entity.EntityHistoryRequest) (*entity.EntityHistoryResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.history") + defer span.End() + var limit int64 = 100 if r.Limit > 0 && r.Limit < 100 { limit = r.Limit @@ -1039,7 +1101,7 @@ func (s *sqlEntityServer) history(ctx context.Context, r *entity.EntityHistoryRe s.log.Debug("history", "query", query, "args", args) - rows, err := s.sess.Query(ctx, query, args...) + rows, err := s.query(ctx, query, args...) if err != nil { return nil, err } @@ -1148,16 +1210,23 @@ func ParseSortBy(sort string) (*SortBy, error) { //nolint:gocyclo func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest) (*entity.EntityListResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.List") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "list"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } user, err := appcontext.User(ctx) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return nil, err } if user == nil { - return nil, fmt.Errorf("missing user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return nil, errorUserNotFoundInContext } fields := s.getReadFields(r) @@ -1242,15 +1311,17 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest) err = s.sess.Get(ctx, rvMaxRow, query, args...) if err != nil { if !errors.Is(err, sql.ErrNoRows) { + ctxLogger.Error("error running rvMaxQuery", "error", err) return nil, err } } - s.log.Debug("getting max rv", "maxRv", rvMaxRow.Rv, "cnt", rvMaxRow.Cnt, "query", query, "args", args) + ctxLogger.Debug("getting max rv", "maxRv", rvMaxRow.Rv, "cnt", rvMaxRow.Cnt, "query", query, "args", args) // if we have a page token, use that to specify the first record continueToken, err := GetContinueToken(r) if err != nil { + ctxLogger.Error("error getting continue token", "error", err) return nil, err } if continueToken != nil { @@ -1333,16 +1404,18 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest) query, args = entityQuery.ToQuery() - s.log.Debug("listing", "query", query, "args", args) + ctxLogger.Debug("listing", "query", query, "args", args) - rows, err := s.sess.Query(ctx, query, args...) + rows, err := s.query(ctx, query, args...) if err != nil { + ctxLogger.Error("error running list query", "error", err) return nil, err } defer func() { _ = rows.Close() }() for rows.Next() { result, err := readEntity(rows, r) if err != nil { + ctxLogger.Error("error reading rows to entity", "error", err) return rsp, err } @@ -1355,33 +1428,40 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest) rsp.Results = append(rsp.Results, result) } + span.AddEvent("processed rows", trace.WithAttributes(attribute.Int("row_count", len(rsp.Results)))) return rsp, err } func (s *sqlEntityServer) Watch(w entity.EntityStore_WatchServer) error { + ctxLogger := s.log.FromContext(log.WithContextualAttributes(w.Context(), []any{"method", "watch"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return err } user, err := appcontext.User(w.Context()) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return err } if user == nil { - return fmt.Errorf("missing user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return errorUserNotFoundInContext } r, err := w.Recv() if err != nil { + ctxLogger.Error("recv error", "error", err) return err } // collect and send any historical events if r.SendInitialEvents { - r.Since, err = s.watchInit(r, w) + r.Since, err = s.watchInit(w.Context(), r, w) if err != nil { - s.log.Error("watch init error", "err", err) + ctxLogger.Error("watch init error", "err", err) return err } } else if r.Since == 0 { @@ -1391,7 +1471,7 @@ func (s *sqlEntityServer) Watch(w entity.EntityStore_WatchServer) error { // subscribe to new events err = s.watch(r, w) if err != nil { - s.log.Error("watch error", "err", err) + ctxLogger.Error("watch error", "err", err) return err } @@ -1399,7 +1479,11 @@ func (s *sqlEntityServer) Watch(w entity.EntityStore_WatchServer) error { } // watchInit is a helper function to send the initial set of entities to the client -func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.EntityStore_WatchServer) (int64, error) { +func (s *sqlEntityServer) watchInit(ctx context.Context, r *entity.EntityWatchRequest, w entity.EntityStore_WatchServer) (int64, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.watchInit") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "watchInit"})) + lastRv := r.Since fields := s.getReadFields(r) @@ -1426,6 +1510,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit for _, k := range r.Key { key, err := entity.ParseKey(k) if err != nil { + ctxLogger.Error("error parsing key", "error", err, "key", k) return lastRv, err } @@ -1483,9 +1568,9 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit err = func() error { query, args := entityQuery.ToQuery() - s.log.Debug("watch init", "query", query, "args", args) + ctxLogger.Debug("watch init", "query", query, "args", args) - rows, err := s.sess.Query(w.Context(), query, args...) + rows, err := s.query(ctx, query, args...) if err != nil { return err } @@ -1514,7 +1599,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit Entity: result, } - s.log.Debug("sending init event", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) + ctxLogger.Debug("sending init event", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) err = w.Send(resp) if err != nil { @@ -1526,6 +1611,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit return nil }() if err != nil { + ctxLogger.Error("watchInit error", "error", err) return lastRv, err } } @@ -1541,6 +1627,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit } err = w.Send(resp) if err != nil { + ctxLogger.Error("error sending bookmark event", "error", err) return lastRv, err } } @@ -1572,6 +1659,10 @@ func (s *sqlEntityServer) poller(stream chan *entity.EntityWatchResponse) { } func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse) (int64, error) { + ctx, span := s.tracer.Start(s.ctx, "storage_server.poll") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "poll"})) + rr := &entity.ReadEntityRequest{ WithBody: true, WithStatus: true, @@ -1595,7 +1686,7 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse query, args := entityQuery.ToQuery() - rows, err := s.sess.Query(s.ctx, query, args...) + rows, err := s.query(ctx, query, args...) if err != nil { return err } @@ -1604,7 +1695,7 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse found := int64(0) for rows.Next() { // check if the context is done - if s.ctx.Err() != nil { + if ctx.Err() != nil { hasmore = false return nil } @@ -1616,6 +1707,7 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse updated, err := readEntity(rows, rr) if err != nil { + ctxLogger.Error("poll error readEntity", "error", err) return err } @@ -1637,16 +1729,16 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse WithBody: rr.WithBody, WithStatus: rr.WithStatus, } - history, err := s.history(s.ctx, rr) + history, err := s.history(ctx, rr) if err != nil { - s.log.Error("error reading previous entity", "guid", updated.Guid, "err", err) + ctxLogger.Error("error reading previous entity", "guid", updated.Guid, "err", err) return err } result.Previous = history.Versions[0] } - s.log.Debug("sending poll result", "guid", updated.Guid, "action", updated.Action, "rv", updated.ResourceVersion) + ctxLogger.Debug("sending poll result", "guid", updated.Guid, "action", updated.Action, "rv", updated.ResourceVersion) out <- result } @@ -1654,6 +1746,7 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse return nil }() if err != nil { + ctxLogger.Error("poll error", "error", err) return since, err } } @@ -1827,20 +1920,28 @@ func (s *sqlEntityServer) watchEvent(r *entity.EntityWatchRequest, result *entit } func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.ReferenceRequest) (*entity.EntityListResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.FindReferences") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "findReferences"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) return nil, err } user, err := appcontext.User(ctx) if err != nil { + ctxLogger.Error("error getting user from ctx", "error", err) return nil, err } if user == nil { - return nil, fmt.Errorf("missing user in context") + ctxLogger.Error("could not find user in context", "error", errorUserNotFoundInContext) + return nil, errorUserNotFoundInContext } if r.NextPageToken != "" { - return nil, fmt.Errorf("not yet supported") + ctxLogger.Error("nextPageToken not yet supported", "error", errorNextPageTokenNotSupported) + return nil, errorNextPageTokenNotSupported } fields := []string{ @@ -1855,8 +1956,9 @@ func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.Referenc " FROM entity_ref AS er JOIN entity AS e ON er.guid = e.guid" + " WHERE er.namespace=? AND er.group=? AND er.resource=? AND er.resolved_to=?" - rows, err := s.sess.Query(ctx, sql, r.Namespace, r.Group, r.Resource, r.Name) + rows, err := s.query(ctx, sql, r.Namespace, r.Group, r.Resource, r.Name) if err != nil { + ctxLogger.Error("query error", "error", err) return nil, err } defer func() { _ = rows.Close() }() @@ -1875,6 +1977,7 @@ func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.Referenc err = rows.Scan(args...) if err != nil { + ctxLogger.Error("error scanning rows", "error", err) return rsp, err } @@ -1890,3 +1993,44 @@ func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.Referenc return rsp, err } + +func (s *sqlEntityServer) query(ctx context.Context, query string, args ...any) (*sql.Rows, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.query", trace.WithAttributes(attribute.String("query", query))) + defer span.End() + + rows, err := s.sess.Query(ctx, query, args...) + if err != nil { + return nil, err + } + return rows, nil +} + +func (s *sqlEntityServer) exec(ctx context.Context, tx *session.SessionTx, statement string, args ...any) error { + ctx, span := s.tracer.Start(ctx, "storage_server.exec", trace.WithAttributes(attribute.String("statement", statement))) + defer span.End() + + _, err := tx.Exec(ctx, statement, args...) + return err +} + +func (s *sqlEntityServer) insert(ctx context.Context, tx *session.SessionTx, table string, values map[string]any) error { + ctx, span := s.tracer.Start(ctx, "storage_server.insert", trace.WithAttributes(attribute.String("table", table))) + defer span.End() + + err := s.dialect.Insert(ctx, tx, table, values) + return err +} + +func (s *sqlEntityServer) update(ctx context.Context, tx *session.SessionTx, table string, row map[string]any, where map[string]any) error { + ctx, span := s.tracer.Start(ctx, "storage_server.db_update", trace.WithAttributes(attribute.String("table", table))) + defer span.End() + + err := s.dialect.Update( + ctx, + tx, + table, + row, + where, + ) + return err +} diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server_test.go b/pkg/services/store/entity/sqlstash/sql_storage_server_test.go index 2f193b78bdf..00353826fad 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server_test.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" @@ -132,7 +133,12 @@ func setUpTestServer(t *testing.T) entity.EntityStoreServer { featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorage)) require.NoError(t, err) - s, err := ProvideSQLEntityServer(entityDB) + traceConfig, err := tracing.ParseTracingConfig(sqlStore.Cfg) + require.NoError(t, err) + tracer, err := tracing.ProvideService(traceConfig) + require.NoError(t, err) + + s, err := ProvideSQLEntityServer(entityDB, tracer) require.NoError(t, err) return s } diff --git a/pkg/services/store/entity/tests/common_test.go b/pkg/services/store/entity/tests/common_test.go index 1dd45739cc0..58d1444c9bb 100644 --- a/pkg/services/store/entity/tests/common_test.go +++ b/pkg/services/store/entity/tests/common_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/components/satokengen" @@ -85,7 +86,11 @@ func createTestContext(t *testing.T) testContext { err = eDB.Init() require.NoError(t, err) - store, err := sqlstash.ProvideSQLEntityServer(eDB) + traceConfig, err := tracing.ParseTracingConfig(env.Cfg) + require.NoError(t, err) + tracer, err := tracing.ProvideService(traceConfig) + require.NoError(t, err) + store, err := sqlstash.ProvideSQLEntityServer(eDB, tracer) require.NoError(t, err) client := entity.NewEntityStoreClientLocal(store) diff --git a/pkg/services/user/error.go b/pkg/services/user/error.go index 8fd192704fb..9460ed6f7f9 100644 --- a/pkg/services/user/error.go +++ b/pkg/services/user/error.go @@ -22,4 +22,5 @@ var ( ErrEmptyUsernameAndEmail = errutil.BadRequest( "user.empty-username-and-email", errutil.WithPublicMessage("Need to specify either username or email"), ) + ErrPasswordMissmatch = errutil.BadRequest("user.password-missmatch", errutil.WithPublicMessage("Invalid old password")) ) diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 0c852186578..13aaed4ae78 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -82,15 +82,13 @@ type UpdateUserCommand struct { Login string `json:"login"` Theme string `json:"theme"` - UserID int64 `json:"-"` - EmailVerified *bool `json:"-"` -} + UserID int64 `json:"-"` + IsDisabled *bool `json:"-"` + EmailVerified *bool `json:"-"` + IsGrafanaAdmin *bool `json:"-"` -type ChangeUserPasswordCommand struct { - OldPassword Password `json:"oldPassword"` - NewPassword Password `json:"newPassword"` - - UserID int64 `json:"-"` + Password *Password `json:"-"` + OldPassword *Password `json:"-"` } type UpdateUserLastSeenAtCommand struct { @@ -176,11 +174,6 @@ func (auth *AuthModuleConversion) ToDB() ([]byte, error) { return []byte{}, nil } -type DisableUserCommand struct { - UserID int64 `xorm:"user_id"` - IsDisabled bool -} - type BatchDisableUsersCommand struct { UserIDs []int64 `xorm:"user_ids"` IsDisabled bool @@ -299,3 +292,8 @@ type AdminCreateUserResponse struct { ID int64 `json:"id"` Message string `json:"message"` } + +type ChangeUserPasswordCommand struct { + OldPassword Password `json:"oldPassword"` + NewPassword Password `json:"newPassword"` +} diff --git a/pkg/services/user/password.go b/pkg/services/user/password.go index 70e0e131e58..78c4f8585ab 100644 --- a/pkg/services/user/password.go +++ b/pkg/services/user/password.go @@ -4,6 +4,7 @@ import ( "unicode" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/errutil" ) @@ -26,13 +27,21 @@ func (p Password) Validate(config *setting.Cfg) error { return ValidatePassword(string(p), config) } +func (p Password) Hash(salt string) (Password, error) { + hashed, err := util.EncodePassword(string(p), salt) + if err != nil { + return "", err + } + return Password(hashed), nil +} + // ValidatePassword checks if a new password meets the required criteria based on the given configuration. // If BasicAuthStrongPasswordPolicy is disabled, it only checks for password length. // Otherwise, it ensures the password meets the minimum length requirement and contains at least one uppercase letter, // one lowercase letter, one number, and one symbol. func ValidatePassword(newPassword string, config *setting.Cfg) error { if !config.BasicAuthStrongPasswordPolicy { - if len(newPassword) <= 4 { + if len(newPassword) < 4 { return ErrPasswordTooShort.Errorf("new password is too short") } return nil diff --git a/pkg/services/user/password_test.go b/pkg/services/user/password_test.go index 1953d1a1529..3240c122524 100644 --- a/pkg/services/user/password_test.go +++ b/pkg/services/user/password_test.go @@ -25,7 +25,7 @@ func TestPasswowrdService_ValidatePasswordHardcodePolicy(t *testing.T) { strongPasswordPolicyEnabled: false, }, {name: "should not return error when the password has 4 characters and strong password policy is disabled", - passwordTest: LOWERCASE, + passwordTest: "test", expectedError: nil, strongPasswordPolicyEnabled: false, }, diff --git a/pkg/services/user/user.go b/pkg/services/user/user.go index 1282546d676..06d8b113978 100644 --- a/pkg/services/user/user.go +++ b/pkg/services/user/user.go @@ -16,16 +16,12 @@ type Service interface { GetByLogin(context.Context, *GetUserByLoginQuery) (*User, error) GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error) Update(context.Context, *UpdateUserCommand) error - ChangePassword(context.Context, *ChangeUserPasswordCommand) error UpdateLastSeenAt(context.Context, *UpdateUserLastSeenAtCommand) error SetUsingOrg(context.Context, *SetUsingOrgCommand) error GetSignedInUserWithCacheCtx(context.Context, *GetSignedInUserQuery) (*SignedInUser, error) GetSignedInUser(context.Context, *GetSignedInUserQuery) (*SignedInUser, error) - NewAnonymousSignedInUser(context.Context) (*SignedInUser, error) Search(context.Context, *SearchUsersQuery) (*SearchUserQueryResult, error) - Disable(context.Context, *DisableUserCommand) error BatchDisableUsers(context.Context, *BatchDisableUsersCommand) error - UpdatePermissions(context.Context, int64, bool) error SetUserHelpFlag(context.Context, *SetUserHelpFlagCommand) error GetProfile(context.Context, *GetUserProfileQuery) (*UserProfileDTO, error) } diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index b5912134afe..c0bc043e499 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -28,17 +28,13 @@ type store interface { GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error) GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error) Update(context.Context, *user.UpdateUserCommand) error - ChangePassword(context.Context, *user.ChangeUserPasswordCommand) error UpdateLastSeenAt(context.Context, *user.UpdateUserLastSeenAtCommand) error GetSignedInUser(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error) UpdateUser(context.Context, *user.User) error GetProfile(context.Context, *user.GetUserProfileQuery) (*user.UserProfileDTO, error) SetHelpFlag(context.Context, *user.SetUserHelpFlagCommand) error - UpdatePermissions(context.Context, int64, bool) error BatchDisableUsers(context.Context, *user.BatchDisableUsersCommand) error - Disable(context.Context, *user.DisableUserCommand) error Search(context.Context, *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) - Count(ctx context.Context) (int64, error) CountUserAccountsWithEmptyRole(ctx context.Context) (int64, error) } @@ -303,31 +299,45 @@ func (ss *sqlStore) loginConflict(ctx context.Context, sess *db.Session, login, } func (ss *sqlStore) Update(ctx context.Context, cmd *user.UpdateUserCommand) error { - cmd.Login = strings.ToLower(cmd.Login) - cmd.Email = strings.ToLower(cmd.Email) - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { user := user.User{ Name: cmd.Name, - Email: cmd.Email, - Login: cmd.Login, + Email: strings.ToLower(cmd.Email), + Login: strings.ToLower(cmd.Login), Theme: cmd.Theme, Updated: time.Now(), } q := sess.ID(cmd.UserID).Where(ss.notServiceAccountFilter()) + if cmd.Password != nil { + user.Password = *cmd.Password + } + + if cmd.IsDisabled != nil { + sess.UseBool("is_disabled") + user.IsDisabled = *cmd.IsDisabled + } + if cmd.EmailVerified != nil { q.UseBool("email_verified") user.EmailVerified = *cmd.EmailVerified } + if cmd.IsGrafanaAdmin != nil { + q.UseBool("is_admin") + user.IsAdmin = *cmd.IsGrafanaAdmin + } + if _, err := q.Update(&user); err != nil { return err } - if err := ss.userCaseInsensitiveLoginConflict(ctx, sess, user.Login, user.Email); err != nil { - return err + if cmd.IsGrafanaAdmin != nil && !*cmd.IsGrafanaAdmin { + // validate that after update there is at least one server admin + if err := validateOneAdminLeft(ctx, sess); err != nil { + return err + } } sess.PublishAfterCommit(&events.UserUpdated{ @@ -342,18 +352,6 @@ func (ss *sqlStore) Update(ctx context.Context, cmd *user.UpdateUserCommand) err }) } -func (ss *sqlStore) ChangePassword(ctx context.Context, cmd *user.ChangeUserPasswordCommand) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - user := user.User{ - Password: cmd.NewPassword, - Updated: time.Now(), - } - - _, err := sess.ID(cmd.UserID).Where(ss.notServiceAccountFilter()).Update(&user) - return err - }) -} - func (ss *sqlStore) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { if cmd.UserID <= 0 { return user.ErrUpdateInvalidID @@ -476,28 +474,6 @@ func (ss *sqlStore) SetHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCo }) } -// UpdatePermissions sets the user Server Admin flag -func (ss *sqlStore) UpdatePermissions(ctx context.Context, userID int64, isAdmin bool) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - var user user.User - if _, err := sess.ID(userID).Where(ss.notServiceAccountFilter()).Get(&user); err != nil { - return err - } - - user.IsAdmin = isAdmin - sess.UseBool("is_admin") - _, err := sess.ID(user.ID).Update(&user) - if err != nil { - return err - } - // validate that after update there is at least one server admin - if err := validateOneAdminLeft(ctx, sess); err != nil { - return err - } - return nil - }) -} - func (ss *sqlStore) Count(ctx context.Context) (int64, error) { type result struct { Count int64 @@ -570,25 +546,6 @@ func (ss *sqlStore) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisabl }) } -func (ss *sqlStore) Disable(ctx context.Context, cmd *user.DisableUserCommand) error { - return ss.db.WithDbSession(ctx, func(dbSess *db.Session) error { - usr := user.User{} - sess := dbSess.Table("user") - - if has, err := sess.ID(cmd.UserID).Where(ss.notServiceAccountFilter()).Get(&usr); err != nil { - return err - } else if !has { - return user.ErrUserNotFound - } - - usr.IsDisabled = cmd.IsDisabled - sess.UseBool("is_disabled") - - _, err := sess.ID(cmd.UserID).Update(&usr) - return err - }) -} - func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { result := user.SearchUserQueryResult{ Users: make([]*user.UserSearchHitDTO, 0), diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index 13e399493f5..cf559938a87 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -419,8 +419,31 @@ func TestIntegrationUserDataAccess(t *testing.T) { }) t.Run("Change user password", func(t *testing.T) { - err := userStore.ChangePassword(context.Background(), &user.ChangeUserPasswordCommand{}) + id, err := userStore.Insert(context.Background(), &user.User{ + Email: "password@test.com", + Name: "password", + Login: "password", + Password: "password", + Salt: "salt", + Created: time.Now(), + Updated: time.Now(), + }) require.NoError(t, err) + + err = userStore.Update(context.Background(), &user.UpdateUserCommand{ + UserID: id, + Password: passwordPtr("updated"), + }) + require.NoError(t, err) + + updated, err := userStore.GetByID(context.Background(), id) + require.NoError(t, err) + + assert.Equal(t, updated.Salt, "salt") + assert.Equal(t, updated.Name, "password") + assert.Equal(t, updated.Login, "password") + assert.Equal(t, updated.Email, "password@test.com") + assert.Equal(t, updated.Password, user.Password("updated")) }) t.Run("update last seen at", func(t *testing.T) { @@ -479,54 +502,47 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - grafana admin users", func(t *testing.T) { ss := db.InitTestDB(t) _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) - createUserCmd := user.CreateUserCommand{ - Email: fmt.Sprint("admin", "@test.com"), + usr, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{ + Email: "admin@test.com", Name: "admin", Login: "admin", IsAdmin: true, - } - usr, err := usrSvc.Create(context.Background(), &createUserCmd) + }) require.Nil(t, err) - // Cannot make themselves a non-admin - updatePermsError := userStore.UpdatePermissions(context.Background(), usr.ID, false) - require.Equal(t, user.ErrLastGrafanaAdmin, updatePermsError) + // Cannot make user non grafana admin if it is the last one + err = userStore.Update(context.Background(), &user.UpdateUserCommand{ + UserID: usr.ID, + IsGrafanaAdmin: boolPtr(false), + }) + require.ErrorIs(t, user.ErrLastGrafanaAdmin, err) - query := user.GetUserByIDQuery{ID: usr.ID} - queryResult, getUserError := userStore.GetByID(context.Background(), query.ID) - require.Nil(t, getUserError) - require.True(t, queryResult.IsAdmin) + usr, err = userStore.GetByID(context.Background(), usr.ID) + require.NoError(t, err) + require.True(t, usr.IsAdmin) - // One user - const email = "user@test.com" - const username = "user" - createUserCmd = user.CreateUserCommand{ - Email: email, - Name: "user", - Login: username, - } - _, err = usrSvc.Create(context.Background(), &createUserCmd) - require.Nil(t, err) + // Create another admin user + _, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{ + Email: "admin2@test.com", + Name: "admin2", + Login: "admin2", + IsAdmin: true, + }) + require.NoError(t, err) - // When trying to create a new user with the same email, an error is returned - createUserCmd = user.CreateUserCommand{ - Email: email, - Name: "user2", - Login: "user2", - SkipOrgSetup: true, - } - _, err = usrSvc.Create(context.Background(), &createUserCmd) - require.Equal(t, user.ErrUserAlreadyExists, err) + // Now first admin user should be able to be downgraded + err = userStore.Update(context.Background(), &user.UpdateUserCommand{ + UserID: usr.ID, + IsGrafanaAdmin: boolPtr(false), + }) + require.NoError(t, err) - // When trying to create a new user with the same login, an error is returned - createUserCmd = user.CreateUserCommand{ - Email: "user2@test.com", - Name: "user2", - Login: username, - SkipOrgSetup: true, - } - _, err = usrSvc.Create(context.Background(), &createUserCmd) - require.Equal(t, user.ErrUserAlreadyExists, err) + updated, err := userStore.GetByID(context.Background(), usr.ID) + require.NoError(t, err) + require.False(t, updated.IsAdmin) + require.Equal(t, usr.Email, updated.Email) + require.Equal(t, usr.Login, updated.Login) + require.Equal(t, usr.Name, updated.Name) }) t.Run("GetProfile", func(t *testing.T) { @@ -787,8 +803,16 @@ func TestIntegrationUserDataAccess(t *testing.T) { Updated: time.Now(), }) require.NoError(t, err) - err = userStore.Disable(context.Background(), &user.DisableUserCommand{UserID: id}) + + err = userStore.Update(context.Background(), &user.UpdateUserCommand{ + UserID: id, + IsDisabled: boolPtr(true), + }) require.NoError(t, err) + + usr, err := userStore.GetByID(context.Background(), id) + require.NoError(t, err) + require.True(t, usr.IsDisabled) }) t.Run("Testing DB - multiple users", func(t *testing.T) { @@ -923,15 +947,6 @@ func TestIntegrationUserUpdate(t *testing.T) { } }) - t.Run("Testing DB - update generates duplicate user", func(t *testing.T) { - err := userStore.Update(context.Background(), &user.UpdateUserCommand{ - Login: "loginuser2", - UserID: users[0].ID, - }) - - require.Error(t, err) - }) - t.Run("Testing DB - update lowercases existing user", func(t *testing.T) { err := userStore.Update(context.Background(), &user.UpdateUserCommand{ Login: "loginUSER0", @@ -964,8 +979,8 @@ func TestIntegrationUserUpdate(t *testing.T) { require.Equal(t, "Change Name", result.Name) // Unchanged - require.Equal(t, "loginUSER3", result.Login) - require.Equal(t, "USER3@test.com", result.Email) + require.Equal(t, "loginuser3", result.Login) + require.Equal(t, "user3@test.com", result.Email) }) } @@ -1041,3 +1056,12 @@ func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Servi return orgService, usrSvc } + +func passwordPtr(s string) *user.Password { + password := user.Password(s) + return &password +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index 4dbe3e4114d..9a085c39ccb 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/models/roletype" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" @@ -135,9 +134,9 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use // create user usr := &user.User{ UID: cmd.UID, - Email: cmd.Email, + Email: strings.ToLower(cmd.Email), Name: cmd.Name, - Login: cmd.Login, + Login: strings.ToLower(cmd.Login), Company: cmd.Company, IsAdmin: cmd.IsAdmin, IsDisabled: cmd.IsDisabled, @@ -161,11 +160,14 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use usr.Rands = rands if len(cmd.Password) > 0 { - encodedPassword, err := util.EncodePassword(string(cmd.Password), usr.Salt) + if err := cmd.Password.Validate(s.cfg); err != nil { + return nil, err + } + + usr.Password, err = cmd.Password.Hash(usr.Salt) if err != nil { return nil, err } - usr.Password = user.Password(encodedPassword) } _, err = s.store.Insert(ctx, usr) @@ -209,14 +211,7 @@ func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error } func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) { - user, err := s.store.GetByID(ctx, query.ID) - if err != nil { - return nil, err - } - if err := s.store.CaseInsensitiveLoginConflict(ctx, user.Login, user.Email); err != nil { - return nil, err - } - return user, nil + return s.store.GetByID(ctx, query.ID) } func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { @@ -228,16 +223,37 @@ func (s *Service) GetByEmail(ctx context.Context, query *user.GetUserByEmailQuer } func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error { - cmd.Login = strings.ToLower(cmd.Login) - cmd.Email = strings.ToLower(cmd.Email) + usr, err := s.store.GetByID(ctx, cmd.UserID) + if err != nil { + return err + } + + if cmd.OldPassword != nil { + old, err := cmd.OldPassword.Hash(usr.Salt) + if err != nil { + return err + } + + if old != usr.Password { + return user.ErrPasswordMissmatch.Errorf("old password does not match stored password") + } + } + + if cmd.Password != nil { + if err := cmd.Password.Validate(s.cfg); err != nil { + return err + } + + hashed, err := cmd.Password.Hash(usr.Salt) + if err != nil { + return err + } + cmd.Password = &hashed + } return s.store.Update(ctx, cmd) } -func (s *Service) ChangePassword(ctx context.Context, cmd *user.ChangeUserPasswordCommand) error { - return s.store.ChangePassword(ctx, cmd) -} - func (s *Service) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { u, err := s.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{ UserID: cmd.UserID, @@ -326,47 +342,14 @@ func (s *Service) GetSignedInUser(ctx context.Context, query *user.GetSignedInUs return signedInUser, err } -func (s *Service) NewAnonymousSignedInUser(ctx context.Context) (*user.SignedInUser, error) { - if !s.cfg.AnonymousEnabled { - return nil, fmt.Errorf("anonymous access is disabled") - } - - usr := &user.SignedInUser{ - IsAnonymous: true, - OrgRole: roletype.RoleType(s.cfg.AnonymousOrgRole), - } - - if s.cfg.AnonymousOrgName == "" { - return usr, nil - } - - getOrg := org.GetOrgByNameQuery{Name: s.cfg.AnonymousOrgName} - anonymousOrg, err := s.orgService.GetByName(ctx, &getOrg) - if err != nil { - return nil, err - } - - usr.OrgID = anonymousOrg.ID - usr.OrgName = anonymousOrg.Name - return usr, nil -} - func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { return s.store.Search(ctx, query) } -func (s *Service) Disable(ctx context.Context, cmd *user.DisableUserCommand) error { - return s.store.Disable(ctx, cmd) -} - func (s *Service) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { return s.store.BatchDisableUsers(ctx, cmd) } -func (s *Service) UpdatePermissions(ctx context.Context, userID int64, isAdmin bool) error { - return s.store.UpdatePermissions(ctx, userID, isAdmin) -} - func (s *Service) SetUserHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { return s.store.SetHelpFlag(ctx, cmd) } diff --git a/pkg/services/user/userimpl/user_test.go b/pkg/services/user/userimpl/user_test.go index 7d19e5b6696..6e362ae92ab 100644 --- a/pkg/services/user/userimpl/user_test.go +++ b/pkg/services/user/userimpl/user_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/team/teamtest" @@ -127,41 +126,6 @@ func TestUserService(t *testing.T) { assert.Equal(t, query2.OrgID, result2.OrgID) }) - t.Run("NewAnonymousSignedInUser", func(t *testing.T) { - t.Run("should error when anonymous access is disabled", func(t *testing.T) { - userService.cfg = setting.NewCfg() - userService.cfg.AnonymousEnabled = false - _, err := userService.NewAnonymousSignedInUser(context.Background()) - require.Error(t, err) - }) - - t.Run("should return user when anonymous access is enabled and org is not set", func(t *testing.T) { - userService.cfg = setting.NewCfg() - userService.cfg.AnonymousEnabled = true - u, err := userService.NewAnonymousSignedInUser(context.Background()) - require.NoError(t, err) - require.Equal(t, true, u.IsAnonymous) - require.Equal(t, int64(0), u.UserID) - require.Equal(t, "", u.OrgName) - require.Equal(t, roletype.RoleType(""), u.OrgRole) - }) - - t.Run("should return user with org info when anonymous access is enabled and org is set", func(t *testing.T) { - userService.cfg = setting.NewCfg() - userService.cfg.AnonymousEnabled = true - userService.cfg.AnonymousOrgName = "anonymous" - userService.cfg.AnonymousOrgRole = "anonymous" - orgService.ExpectedOrg = &org.Org{Name: "anonymous", ID: 123} - u, err := userService.NewAnonymousSignedInUser(context.Background()) - require.NoError(t, err) - require.Equal(t, true, u.IsAnonymous) - require.Equal(t, int64(0), u.UserID) - require.Equal(t, orgService.ExpectedOrg.ID, u.OrgID) - require.Equal(t, orgService.ExpectedOrg.Name, u.OrgName) - require.Equal(t, roletype.RoleType(userService.cfg.AnonymousOrgRole), u.OrgRole) - }) - }) - t.Run("Can set using org", func(t *testing.T) { cmd := user.SetUsingOrgCommand{UserID: 2, OrgID: 1} orgService.ExpectedUserOrgDTO = []*org.UserOrgDTO{{OrgID: 1}} @@ -190,6 +154,32 @@ func TestUserService(t *testing.T) { }) } +func TestService_Update(t *testing.T) { + t.Run("should return error if old password does not match stored password", func(t *testing.T) { + stored, err := user.Password("test").Hash("salt") + require.NoError(t, err) + service := &Service{store: &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}}} + + err = service.Update(context.Background(), &user.UpdateUserCommand{ + OldPassword: passwordPtr("test123"), + }) + + assert.ErrorIs(t, err, user.ErrPasswordMissmatch) + }) + + t.Run("should return error new password is not valid", func(t *testing.T) { + stored, err := user.Password("test").Hash("salt") + require.NoError(t, err) + service := &Service{cfg: setting.NewCfg(), store: &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}}} + + err = service.Update(context.Background(), &user.UpdateUserCommand{ + OldPassword: passwordPtr("test"), + Password: passwordPtr("asd"), + }) + require.ErrorIs(t, err, user.ErrPasswordTooShort) + }) +} + func TestMetrics(t *testing.T) { userStore := newUserStoreFake() orgService := orgtest.NewOrgServiceFake() @@ -270,10 +260,6 @@ func (f *FakeUserStore) Update(ctx context.Context, cmd *user.UpdateUserCommand) return f.ExpectedError } -func (f *FakeUserStore) ChangePassword(ctx context.Context, cmd *user.ChangeUserPasswordCommand) error { - return f.ExpectedError -} - func (f *FakeUserStore) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { return f.ExpectedError } @@ -302,10 +288,6 @@ func (f *FakeUserStore) BatchDisableUsers(ctx context.Context, cmd *user.BatchDi return f.ExpectedError } -func (f *FakeUserStore) Disable(ctx context.Context, cmd *user.DisableUserCommand) error { - return f.ExpectedError -} - func (f *FakeUserStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { return f.ExpectedSearchUserQueryResult, f.ExpectedError } diff --git a/pkg/services/user/usertest/fake.go b/pkg/services/user/usertest/fake.go index b1e0aa81f3d..b33a04ce552 100644 --- a/pkg/services/user/usertest/fake.go +++ b/pkg/services/user/usertest/fake.go @@ -19,7 +19,6 @@ type FakeUserService struct { UpdateFn func(ctx context.Context, cmd *user.UpdateUserCommand) error GetSignedInUserFn func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) CreateFn func(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) - DisableFn func(ctx context.Context, cmd *user.DisableUserCommand) error BatchDisableUsersFn func(ctx context.Context, cmd *user.BatchDisableUsersCommand) error counter int @@ -68,10 +67,6 @@ func (f *FakeUserService) Update(ctx context.Context, cmd *user.UpdateUserComman return f.ExpectedError } -func (f *FakeUserService) ChangePassword(ctx context.Context, cmd *user.ChangeUserPasswordCommand) error { - return f.ExpectedError -} - func (f *FakeUserService) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { return f.ExpectedError } @@ -102,13 +97,6 @@ func (f *FakeUserService) Search(ctx context.Context, query *user.SearchUsersQue return &f.ExpectedSearchUsers, f.ExpectedError } -func (f *FakeUserService) Disable(ctx context.Context, cmd *user.DisableUserCommand) error { - if f.DisableFn != nil { - return f.DisableFn(ctx, cmd) - } - return f.ExpectedError -} - func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { if f.BatchDisableUsersFn != nil { return f.BatchDisableUsersFn(ctx, cmd) @@ -116,10 +104,6 @@ func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.Batch return f.ExpectedError } -func (f *FakeUserService) UpdatePermissions(ctx context.Context, userID int64, isAdmin bool) error { - return f.ExpectedError -} - func (f *FakeUserService) SetUserHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { return f.ExpectedError } diff --git a/pkg/services/user/usertest/mock.go b/pkg/services/user/usertest/mock.go index 590a66c9b08..7668bf3548f 100644 --- a/pkg/services/user/usertest/mock.go +++ b/pkg/services/user/usertest/mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package usertest @@ -32,24 +32,6 @@ func (_m *MockService) BatchDisableUsers(_a0 context.Context, _a1 *user.BatchDis return r0 } -// ChangePassword provides a mock function with given fields: _a0, _a1 -func (_m *MockService) ChangePassword(_a0 context.Context, _a1 *user.ChangeUserPasswordCommand) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for ChangePassword") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *user.ChangeUserPasswordCommand) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Create provides a mock function with given fields: _a0, _a1 func (_m *MockService) Create(_a0 context.Context, _a1 *user.CreateUserCommand) (*user.User, error) { ret := _m.Called(_a0, _a1) @@ -128,24 +110,6 @@ func (_m *MockService) Delete(_a0 context.Context, _a1 *user.DeleteUserCommand) return r0 } -// Disable provides a mock function with given fields: _a0, _a1 -func (_m *MockService) Disable(_a0 context.Context, _a1 *user.DisableUserCommand) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for Disable") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *user.DisableUserCommand) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // GetByEmail provides a mock function with given fields: _a0, _a1 func (_m *MockService) GetByEmail(_a0 context.Context, _a1 *user.GetUserByEmailQuery) (*user.User, error) { ret := _m.Called(_a0, _a1) @@ -346,36 +310,6 @@ func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{} return r0 } -// NewAnonymousSignedInUser provides a mock function with given fields: _a0 -func (_m *MockService) NewAnonymousSignedInUser(_a0 context.Context) (*user.SignedInUser, error) { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for NewAnonymousSignedInUser") - } - - var r0 *user.SignedInUser - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*user.SignedInUser, error)); ok { - return rf(_a0) - } - if rf, ok := ret.Get(0).(func(context.Context) *user.SignedInUser); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*user.SignedInUser) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(_a0) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // Search provides a mock function with given fields: _a0, _a1 func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { ret := _m.Called(_a0, _a1) @@ -478,24 +412,6 @@ func (_m *MockService) UpdateLastSeenAt(_a0 context.Context, _a1 *user.UpdateUse return r0 } -// UpdatePermissions provides a mock function with given fields: _a0, _a1, _a2 -func (_m *MockService) UpdatePermissions(_a0 context.Context, _a1 int64, _a2 bool) error { - ret := _m.Called(_a0, _a1, _a2) - - if len(ret) == 0 { - panic("no return value specified for UpdatePermissions") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { - r0 = rf(_a0, _a1, _a2) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockService(t interface { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index ce987278299..ed864575f6d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -470,9 +470,10 @@ type Cfg struct { RBACSingleOrganization bool // GRPC Server. - GRPCServerNetwork string - GRPCServerAddress string - GRPCServerTLSConfig *tls.Config + GRPCServerNetwork string + GRPCServerAddress string + GRPCServerTLSConfig *tls.Config + GRPCServerEnableLogging bool // log request and response of each unary gRPC call CustomResponseHeaders map[string]string @@ -1756,6 +1757,7 @@ func readGRPCServerSettings(cfg *Cfg, iniFile *ini.File) error { cfg.GRPCServerNetwork = valueAsString(server, "network", "tcp") cfg.GRPCServerAddress = valueAsString(server, "address", "") + cfg.GRPCServerEnableLogging = server.Key("enable_logging").MustBool(false) switch cfg.GRPCServerNetwork { case "unix": if cfg.GRPCServerAddress != "" { diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 33781a74f91..fb160313cea 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -1889,7 +1889,7 @@ func TestIntegrationRuleNotificationSettings(t *testing.T) { t.Log(body) }) - t.Run("create should fail if group_by does not contain special labels", func(t *testing.T) { + t.Run("create should not fail if group_by is missing required labels but they should still be used", func(t *testing.T) { var copyD testData err = json.Unmarshal(testDataRaw, ©D) group := copyD.RuleGroup @@ -1897,10 +1897,48 @@ func TestIntegrationRuleNotificationSettings(t *testing.T) { ns.GroupBy = []string{"label1"} _, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) - require.Equalf(t, http.StatusBadRequest, status, body) + require.Equalf(t, http.StatusAccepted, status, body) + + cfg, status, body := apiClient.GetAlertmanagerConfigWithStatus(t) + if !assert.Equalf(t, http.StatusOK, status, body) { + return + } + + // Ensure that the group by contains the default required labels. + autogenRoute := cfg.AlertmanagerConfig.Route.Routes[0] + receiverRoute := autogenRoute.Routes[0] + ruleRoute := receiverRoute.Routes[0] + assert.Equal(t, []model.LabelName{ngmodels.FolderTitleLabel, model.AlertNameLabel, "label1"}, ruleRoute.GroupBy) + t.Log(body) }) + t.Run("create with '...' groupBy followed by config post should succeed", func(t *testing.T) { + var copyD testData + err = json.Unmarshal(testDataRaw, ©D) + group := copyD.RuleGroup + ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings + ns.GroupBy = []string{ngmodels.FolderTitleLabel, model.AlertNameLabel, ngmodels.GroupByAll} + + _, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) + require.Equalf(t, http.StatusAccepted, status, body) + + // Now update the config with no changes. + _, status, body = apiClient.GetAlertmanagerConfigWithStatus(t) + if !assert.Equalf(t, http.StatusOK, status, body) { + return + } + + cfg := apimodels.PostableUserConfig{} + + err = json.Unmarshal([]byte(body), &cfg) + require.NoError(t, err) + + ok, err := apiClient.PostConfiguration(t, cfg) + require.NoError(t, err) + require.True(t, ok) + }) + t.Run("should create rule and generate route", func(t *testing.T) { _, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &d.RuleGroup) require.Equalf(t, http.StatusAccepted, status, body) diff --git a/pkg/tests/api/alerting/test-data/rule-notification-settings-1-post.json b/pkg/tests/api/alerting/test-data/rule-notification-settings-1-post.json index 0331fd94e27..b281cc566a4 100644 --- a/pkg/tests/api/alerting/test-data/rule-notification-settings-1-post.json +++ b/pkg/tests/api/alerting/test-data/rule-notification-settings-1-post.json @@ -29,8 +29,8 @@ "notification_settings": { "receiver": "rule-receiver", "group_by": [ - "alertname", "grafana_folder", + "alertname", "label1" ], "group_wait": "100ms", @@ -55,4 +55,4 @@ "name": "rule-time-interval", "time_intervals":[{"times":[{"start_time":"10:00","end_time":"12:00"}]}] } -} \ No newline at end of file +} diff --git a/pkg/tests/api/plugins/data/expectedListResp.json b/pkg/tests/api/plugins/data/expectedListResp.json index 2723c7388e8..e04e45cce5f 100644 --- a/pkg/tests/api/plugins/data/expectedListResp.json +++ b/pkg/tests/api/plugins/data/expectedListResp.json @@ -1118,7 +1118,7 @@ "keywords": null }, "dependencies": { - "grafanaDependency": "", + "grafanaDependency": ">=10.4.0", "grafanaVersion": "*", "plugins": [] }, diff --git a/pkg/tsdb/grafana-postgresql-datasource/macros.go b/pkg/tsdb/grafana-postgresql-datasource/macros.go index 42c7abf1de9..f6b0f893da0 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/macros.go +++ b/pkg/tsdb/grafana-postgresql-datasource/macros.go @@ -9,7 +9,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" ) const rsIdentifier = `([_a-zA-Z0-9]+)` diff --git a/pkg/tsdb/grafana-postgresql-datasource/postgres.go b/pkg/tsdb/grafana-postgresql-datasource/postgres.go index 95b5ec96e1c..4f2c1ee3c90 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/postgres.go +++ b/pkg/tsdb/grafana-postgresql-datasource/postgres.go @@ -19,7 +19,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" ) func ProvideService(cfg *setting.Cfg) *Service { diff --git a/pkg/tsdb/grafana-postgresql-datasource/postgres_snapshot_test.go b/pkg/tsdb/grafana-postgresql-datasource/postgres_snapshot_test.go index b3c5a04aae6..e0d9e307ae0 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/postgres_snapshot_test.go +++ b/pkg/tsdb/grafana-postgresql-datasource/postgres_snapshot_test.go @@ -16,7 +16,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/experimental" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" ) var updateGoldenFiles = false diff --git a/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go b/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go index f5ccc6787f4..d16b9d53f31 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go +++ b/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" _ "github.com/lib/pq" ) diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go similarity index 100% rename from pkg/tsdb/sqleng/sql_engine.go rename to pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go new file mode 100644 index 00000000000..b7f422327ac --- /dev/null +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go @@ -0,0 +1,438 @@ +package sqleng + +import ( + "fmt" + "net" + "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng/util" +) + +func TestSQLEngine(t *testing.T) { + dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC) + + t.Run("Handle interpolating $__interval and $__interval_ms", func(t *testing.T) { + from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) + to := from.Add(5 * time.Minute) + timeRange := backend.TimeRange{From: from, To: to} + + text := "$__interval $__timeGroupAlias(time,$__interval) $__interval_ms" + + t.Run("interpolate 10 minutes $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Minute * 10} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "10m $__timeGroupAlias(time,10m) 600000", sql) + }) + + t.Run("interpolate 4seconds $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Second * 4} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "4s $__timeGroupAlias(time,4s) 4000", sql) + }) + + t.Run("interpolate 200 milliseconds $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Millisecond * 200} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "200ms $__timeGroupAlias(time,200ms) 200", sql) + }) + }) + + t.Run("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func(t *testing.T) { + from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) + to := from.Add(5 * time.Minute) + timeRange := backend.TimeRange{From: from, To: to} + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Second * 60} + + t.Run("interpolate __unixEpochFrom function", func(t *testing.T) { + sql := Interpolate(query, timeRange, "", "select $__unixEpochFrom()") + require.Equal(t, fmt.Sprintf("select %d", from.Unix()), sql) + }) + + t.Run("interpolate __unixEpochTo function", func(t *testing.T) { + sql := Interpolate(query, timeRange, "", "select $__unixEpochTo()") + require.Equal(t, fmt.Sprintf("select %d", to.Unix()), sql) + }) + }) + + t.Run("Given row values with int64 as time columns", func(t *testing.T) { + tSeconds := dt.Unix() + tMilliseconds := dt.UnixNano() / 1e6 + tNanoSeconds := dt.UnixNano() + var nilPointer *int64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []int64{ + tSeconds, + }), + data.NewField("time2", nil, []*int64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []int64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*int64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []int64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*int64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*int64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with uint64 as time columns", func(t *testing.T) { + tSeconds := uint64(dt.Unix()) + tMilliseconds := uint64(dt.UnixNano() / 1e6) + tNanoSeconds := uint64(dt.UnixNano()) + var nilPointer *uint64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []uint64{ + tSeconds, + }), + data.NewField("time2", nil, []*uint64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []uint64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*uint64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []uint64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*uint64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*uint64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with int32 as time columns", func(t *testing.T) { + tSeconds := int32(dt.Unix()) + var nilInt *int32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []int32{ + tSeconds, + }), + data.NewField("time2", nil, []*int32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*int32{ + nilInt, + }), + ) + for i := 0; i < 3; i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row values with uint32 as time columns", func(t *testing.T) { + tSeconds := uint32(dt.Unix()) + var nilInt *uint32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []uint32{ + tSeconds, + }), + data.NewField("time2", nil, []*uint32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*uint32{ + nilInt, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row values with float64 as time columns", func(t *testing.T) { + tSeconds := float64(dt.UnixNano()) / float64(time.Second) + tMilliseconds := float64(dt.UnixNano()) / float64(time.Millisecond) + tNanoSeconds := float64(dt.UnixNano()) + var nilPointer *float64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []float64{ + tSeconds, + }), + data.NewField("time2", nil, []*float64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []float64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*float64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []float64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*float64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*float64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with float32 as time columns", func(t *testing.T) { + tSeconds := float32(dt.Unix()) + var nilInt *float32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []float32{ + tSeconds, + }), + data.NewField("time2", nil, []*float32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*float32{ + nilInt, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + require.Equal(t, int64(tSeconds), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, int64(tSeconds), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row with value columns, would be converted to float64", func(t *testing.T) { + originFrame := data.NewFrame("", + data.NewField("value1", nil, []int64{ + int64(1), + }), + data.NewField("value2", nil, []*int64{ + util.Pointer(int64(1)), + }), + data.NewField("value3", nil, []int32{ + int32(1), + }), + data.NewField("value4", nil, []*int32{ + util.Pointer(int32(1)), + }), + data.NewField("value5", nil, []int16{ + int16(1), + }), + data.NewField("value6", nil, []*int16{ + util.Pointer(int16(1)), + }), + data.NewField("value7", nil, []int8{ + int8(1), + }), + data.NewField("value8", nil, []*int8{ + util.Pointer(int8(1)), + }), + data.NewField("value9", nil, []float64{ + float64(1), + }), + data.NewField("value10", nil, []*float64{ + util.Pointer(1.0), + }), + data.NewField("value11", nil, []float32{ + float32(1), + }), + data.NewField("value12", nil, []*float32{ + util.Pointer(float32(1)), + }), + data.NewField("value13", nil, []uint64{ + uint64(1), + }), + data.NewField("value14", nil, []*uint64{ + util.Pointer(uint64(1)), + }), + data.NewField("value15", nil, []uint32{ + uint32(1), + }), + data.NewField("value16", nil, []*uint32{ + util.Pointer(uint32(1)), + }), + data.NewField("value17", nil, []uint16{ + uint16(1), + }), + data.NewField("value18", nil, []*uint16{ + util.Pointer(uint16(1)), + }), + data.NewField("value19", nil, []uint8{ + uint8(1), + }), + data.NewField("value20", nil, []*uint8{ + util.Pointer(uint8(1)), + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + _, err := convertSQLValueColumnToFloat(originFrame, i) + require.NoError(t, err) + if i == 8 { + require.Equal(t, float64(1), originFrame.Fields[i].At(0).(float64)) + } else { + require.NotNil(t, originFrame.Fields[i].At(0).(*float64)) + require.Equal(t, float64(1), *originFrame.Fields[i].At(0).(*float64)) + } + } + }) + + t.Run("Given row with nil value columns", func(t *testing.T) { + var int64NilPointer *int64 + var int32NilPointer *int32 + var int16NilPointer *int16 + var int8NilPointer *int8 + var float64NilPointer *float64 + var float32NilPointer *float32 + var uint64NilPointer *uint64 + var uint32NilPointer *uint32 + var uint16NilPointer *uint16 + var uint8NilPointer *uint8 + + originFrame := data.NewFrame("", + data.NewField("value1", nil, []*int64{ + int64NilPointer, + }), + data.NewField("value2", nil, []*int32{ + int32NilPointer, + }), + data.NewField("value3", nil, []*int16{ + int16NilPointer, + }), + data.NewField("value4", nil, []*int8{ + int8NilPointer, + }), + data.NewField("value5", nil, []*float64{ + float64NilPointer, + }), + data.NewField("value6", nil, []*float32{ + float32NilPointer, + }), + data.NewField("value7", nil, []*uint64{ + uint64NilPointer, + }), + data.NewField("value8", nil, []*uint32{ + uint32NilPointer, + }), + data.NewField("value9", nil, []*uint16{ + uint16NilPointer, + }), + data.NewField("value10", nil, []*uint8{ + uint8NilPointer, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + t.Run("", func(t *testing.T) { + _, err := convertSQLValueColumnToFloat(originFrame, i) + require.NoError(t, err) + require.Nil(t, originFrame.Fields[i].At(0)) + }) + } + }) + + t.Run("Should not return raw connection errors", func(t *testing.T) { + err := net.OpError{Op: "Dial", Err: fmt.Errorf("inner-error")} + transformer := &testQueryResultTransformer{} + dp := DataSourceHandler{ + log: backend.NewLoggerWith("logger", "test"), + queryResultTransformer: transformer, + } + resultErr := dp.TransformQueryError(dp.log, &err) + assert.False(t, transformer.transformQueryErrorWasCalled) + errorText := resultErr.Error() + assert.NotEqual(t, err, resultErr) + assert.NotContains(t, errorText, "inner-error") + assert.Contains(t, errorText, "failed to connect to server") + }) + + t.Run("Should return non-connection errors unmodified", func(t *testing.T) { + err := fmt.Errorf("normal error") + transformer := &testQueryResultTransformer{} + dp := DataSourceHandler{ + log: backend.NewLoggerWith("logger", "test"), + queryResultTransformer: transformer, + } + resultErr := dp.TransformQueryError(dp.log, err) + assert.True(t, transformer.transformQueryErrorWasCalled) + assert.Equal(t, err, resultErr) + assert.ErrorIs(t, err, resultErr) + }) +} + +type testQueryResultTransformer struct { + transformQueryErrorWasCalled bool +} + +func (t *testQueryResultTransformer) TransformQueryError(_ log.Logger, err error) error { + t.transformQueryErrorWasCalled = true + return err +} + +func (t *testQueryResultTransformer) GetConverterList() []sqlutil.StringConverter { + return nil +} diff --git a/pkg/tsdb/sqleng/util/util.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go similarity index 100% rename from pkg/tsdb/sqleng/util/util.go rename to pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go diff --git a/pkg/tsdb/grafana-postgresql-datasource/tlsmanager.go b/pkg/tsdb/grafana-postgresql-datasource/tlsmanager.go index 116872d0613..46e68753da3 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/tlsmanager.go +++ b/pkg/tsdb/grafana-postgresql-datasource/tlsmanager.go @@ -10,7 +10,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" ) var validateCertFunc = validateCertFilePaths diff --git a/pkg/tsdb/grafana-postgresql-datasource/tlsmanager_test.go b/pkg/tsdb/grafana-postgresql-datasource/tlsmanager_test.go index 8e60e841cab..c8eda0a83a7 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/tlsmanager_test.go +++ b/pkg/tsdb/grafana-postgresql-datasource/tlsmanager_test.go @@ -12,7 +12,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 9a137ce496d..de01cc9a9f2 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mssql/sqleng" ) const rsIdentifier = `([_a-zA-Z0-9]+)` diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index 368e0141847..b0d31e2a70c 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -25,8 +25,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/mssql/kerberos" + "github.com/grafana/grafana/pkg/tsdb/mssql/sqleng" "github.com/grafana/grafana/pkg/tsdb/mssql/utils" - "github.com/grafana/grafana/pkg/tsdb/sqleng" "github.com/grafana/grafana/pkg/util" ) @@ -403,5 +403,20 @@ func (t *mssqlQueryResultTransformer) GetConverterList() []sqlutil.StringConvert }, }, }, + { + Name: "handle SQL_VARIANT", + InputScanKind: reflect.Pointer, + InputTypeName: "SQL_VARIANT", + ConversionFunc: func(in *string) (*string, error) { return in, nil }, + Replacer: &sqlutil.StringFieldReplacer{ + OutputFieldType: data.FieldTypeNullableString, + ReplaceFunc: func(in *string) (any, error) { + if in == nil { + return nil, nil + } + return in, nil + }, + }, + }, } } diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 77b2c1f4fbf..40c6ee50ac3 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -17,7 +17,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/tsdb/mssql/kerberos" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mssql/sqleng" ) // To run this test, set runMssqlTests=true @@ -92,6 +92,7 @@ func TestMSSQL(t *testing.T) { c_datetimeoffset datetimeoffset, c_uuid uniqueidentifier + c_sql_variant sql_variant ) ` @@ -114,7 +115,7 @@ func TestMSSQL(t *testing.T) { 'char10', 'varchar10', 'text', N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺', CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00'), - CONVERT(uniqueidentifier, '%s') + CONVERT(uniqueidentifier, '%s'), 'test-sql-variant' `, d, d2, d, d, d, d2, uuid) _, err = db.Exec(sql) @@ -170,6 +171,7 @@ func TestMSSQL(t *testing.T) { require.Equal(t, dt2.In(time.FixedZone("UTC-7", int(-7*60*60))).Unix(), (*frames[0].Fields[22].At(0).(*time.Time)).Unix()) require.Equal(t, uuid, *frames[0].Fields[23].At(0).(*string)) + require.Equal(t, "test-sql-variant", *frames[0].Fields[24].At(0).(*string)) }) }) diff --git a/pkg/tsdb/mssql/sqleng/sql_engine.go b/pkg/tsdb/mssql/sqleng/sql_engine.go new file mode 100644 index 00000000000..060e2bcb02e --- /dev/null +++ b/pkg/tsdb/mssql/sqleng/sql_engine.go @@ -0,0 +1,642 @@ +package sqleng + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "net" + "regexp" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +// MetaKeyExecutedQueryString is the key where the executed query should get stored +const MetaKeyExecutedQueryString = "executedQueryString" + +// SQLMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and +// timeRange to be able to generate queries that use from and to. +type SQLMacroEngine interface { + Interpolate(query *backend.DataQuery, timeRange backend.TimeRange, sql string) (string, error) +} + +// SqlQueryResultTransformer transforms a query result row to RowValues with proper types. +type SqlQueryResultTransformer interface { + // TransformQueryError transforms a query error. + TransformQueryError(logger log.Logger, err error) error + GetConverterList() []sqlutil.StringConverter +} + +type JsonData struct { + MaxOpenConns int `json:"maxOpenConns"` + MaxIdleConns int `json:"maxIdleConns"` + ConnMaxLifetime int `json:"connMaxLifetime"` + ConnectionTimeout int `json:"connectionTimeout"` + Timescaledb bool `json:"timescaledb"` + Mode string `json:"sslmode"` + ConfigurationMethod string `json:"tlsConfigurationMethod"` + TlsSkipVerify bool `json:"tlsSkipVerify"` + RootCertFile string `json:"sslRootCertFile"` + CertFile string `json:"sslCertFile"` + CertKeyFile string `json:"sslKeyFile"` + Timezone string `json:"timezone"` + Encrypt string `json:"encrypt"` + Servername string `json:"servername"` + TimeInterval string `json:"timeInterval"` + Database string `json:"database"` + SecureDSProxy bool `json:"enableSecureSocksProxy"` + SecureDSProxyUsername string `json:"secureSocksProxyUsername"` + AllowCleartextPasswords bool `json:"allowCleartextPasswords"` + AuthenticationType string `json:"authenticationType"` +} + +type DataSourceInfo struct { + JsonData JsonData + URL string + User string + Database string + ID int64 + Updated time.Time + UID string + DecryptedSecureJSONData map[string]string +} + +type DataPluginConfiguration struct { + DSInfo DataSourceInfo + TimeColumnNames []string + MetricColumnTypes []string + RowLimit int64 +} + +type DataSourceHandler struct { + macroEngine SQLMacroEngine + queryResultTransformer SqlQueryResultTransformer + db *sql.DB + timeColumnNames []string + metricColumnTypes []string + log log.Logger + dsInfo DataSourceInfo + rowLimit int64 + userError string +} + +type QueryJson struct { + RawSql string `json:"rawSql"` + Fill bool `json:"fill"` + FillInterval float64 `json:"fillInterval"` + FillMode string `json:"fillMode"` + FillValue float64 `json:"fillValue"` + Format string `json:"format"` +} + +func (e *DataSourceHandler) TransformQueryError(logger log.Logger, err error) error { + // OpError is the error type usually returned by functions in the net + // package. It describes the operation, network type, and address of + // an error. We log this error rather than return it to the client + // for security purposes. + var opErr *net.OpError + if errors.As(err, &opErr) { + logger.Error("Query error", "err", err) + return fmt.Errorf("failed to connect to server - %s", e.userError) + } + + return e.queryResultTransformer.TransformQueryError(logger, err) +} + +func NewQueryDataHandler(userFacingDefaultError string, db *sql.DB, config DataPluginConfiguration, queryResultTransformer SqlQueryResultTransformer, + macroEngine SQLMacroEngine, log log.Logger) (*DataSourceHandler, error) { + queryDataHandler := DataSourceHandler{ + queryResultTransformer: queryResultTransformer, + macroEngine: macroEngine, + timeColumnNames: []string{"time"}, + log: log, + dsInfo: config.DSInfo, + rowLimit: config.RowLimit, + userError: userFacingDefaultError, + } + + if len(config.TimeColumnNames) > 0 { + queryDataHandler.timeColumnNames = config.TimeColumnNames + } + + if len(config.MetricColumnTypes) > 0 { + queryDataHandler.metricColumnTypes = config.MetricColumnTypes + } + + queryDataHandler.db = db + return &queryDataHandler, nil +} + +type DBDataResponse struct { + dataResponse backend.DataResponse + refID string +} + +func (e *DataSourceHandler) Dispose() { + e.log.Debug("Disposing DB...") + if e.db != nil { + if err := e.db.Close(); err != nil { + e.log.Error("Failed to dispose db", "error", err) + } + } + e.log.Debug("DB disposed") +} + +func (e *DataSourceHandler) Ping() error { + return e.db.Ping() +} + +func (e *DataSourceHandler) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + result := backend.NewQueryDataResponse() + ch := make(chan DBDataResponse, len(req.Queries)) + var wg sync.WaitGroup + // Execute each query in a goroutine and wait for them to finish afterwards + for _, query := range req.Queries { + queryjson := QueryJson{ + Fill: false, + Format: "time_series", + } + err := json.Unmarshal(query.JSON, &queryjson) + if err != nil { + return nil, fmt.Errorf("error unmarshal query json: %w", err) + } + + // the fill-params are only stored inside this function, during query-interpolation. we do not support + // sending them in "from the outside" + if queryjson.Fill || queryjson.FillInterval != 0.0 || queryjson.FillMode != "" || queryjson.FillValue != 0.0 { + return nil, fmt.Errorf("query fill-parameters not supported") + } + + if queryjson.RawSql == "" { + continue + } + + wg.Add(1) + go e.executeQuery(query, &wg, ctx, ch, queryjson) + } + + wg.Wait() + + // Read results from channels + close(ch) + result.Responses = make(map[string]backend.DataResponse) + for queryResult := range ch { + result.Responses[queryResult.refID] = queryResult.dataResponse + } + + return result, nil +} + +func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitGroup, queryContext context.Context, + ch chan DBDataResponse, queryJson QueryJson) { + defer wg.Done() + queryResult := DBDataResponse{ + dataResponse: backend.DataResponse{}, + refID: query.RefID, + } + + logger := e.log.FromContext(queryContext) + + defer func() { + if r := recover(); r != nil { + logger.Error("ExecuteQuery panic", "error", r, "stack", string(debug.Stack())) + if theErr, ok := r.(error); ok { + queryResult.dataResponse.Error = theErr + } else if theErrString, ok := r.(string); ok { + queryResult.dataResponse.Error = fmt.Errorf(theErrString) + } else { + queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError) + } + ch <- queryResult + } + }() + + if queryJson.RawSql == "" { + panic("Query model property rawSql should not be empty at this point") + } + + timeRange := query.TimeRange + + errAppendDebug := func(frameErr string, err error, query string) { + var emptyFrame data.Frame + emptyFrame.SetMeta(&data.FrameMeta{ + ExecutedQueryString: query, + }) + queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err) + queryResult.dataResponse.Frames = data.Frames{&emptyFrame} + ch <- queryResult + } + + // global substitutions + interpolatedQuery := Interpolate(query, timeRange, e.dsInfo.JsonData.TimeInterval, queryJson.RawSql) + + // data source specific substitutions + interpolatedQuery, err := e.macroEngine.Interpolate(&query, timeRange, interpolatedQuery) + if err != nil { + errAppendDebug("interpolation failed", e.TransformQueryError(logger, err), interpolatedQuery) + return + } + + rows, err := e.db.QueryContext(queryContext, interpolatedQuery) + if err != nil { + errAppendDebug("db query error", e.TransformQueryError(logger, err), interpolatedQuery) + return + } + defer func() { + if err := rows.Close(); err != nil { + logger.Warn("Failed to close rows", "err", err) + } + }() + + qm, err := e.newProcessCfg(query, queryContext, rows, interpolatedQuery) + if err != nil { + errAppendDebug("failed to get configurations", err, interpolatedQuery) + return + } + + // Convert row.Rows to dataframe + stringConverters := e.queryResultTransformer.GetConverterList() + frame, err := sqlutil.FrameFromRows(rows, e.rowLimit, sqlutil.ToConverters(stringConverters...)...) + if err != nil { + errAppendDebug("convert frame from rows error", err, interpolatedQuery) + return + } + + if frame.Meta == nil { + frame.Meta = &data.FrameMeta{} + } + + frame.Meta.ExecutedQueryString = interpolatedQuery + + // If no rows were returned, clear any previously set `Fields` with a single empty `data.Field` slice. + // Then assign `queryResult.dataResponse.Frames` the current single frame with that single empty Field. + // This assures 1) our visualization doesn't display unwanted empty fields, and also that 2) + // additionally-needed frame data stays intact and is correctly passed to our visulization. + if frame.Rows() == 0 { + frame.Fields = []*data.Field{} + queryResult.dataResponse.Frames = data.Frames{frame} + ch <- queryResult + return + } + + if err := convertSQLTimeColumnsToEpochMS(frame, qm); err != nil { + errAppendDebug("converting time columns failed", err, interpolatedQuery) + return + } + + if qm.Format == dataQueryFormatSeries { + // time series has to have time column + if qm.timeIndex == -1 { + errAppendDebug("db has no time column", errors.New("no time column found"), interpolatedQuery) + return + } + + // Make sure to name the time field 'Time' to be backward compatible with Grafana pre-v8. + frame.Fields[qm.timeIndex].Name = data.TimeSeriesTimeFieldName + + for i := range qm.columnNames { + if i == qm.timeIndex || i == qm.metricIndex { + continue + } + + if t := frame.Fields[i].Type(); t == data.FieldTypeString || t == data.FieldTypeNullableString { + continue + } + + var err error + if frame, err = convertSQLValueColumnToFloat(frame, i); err != nil { + errAppendDebug("convert value to float failed", err, interpolatedQuery) + return + } + } + + tsSchema := frame.TimeSeriesSchema() + if tsSchema.Type == data.TimeSeriesTypeLong { + var err error + originalData := frame + frame, err = data.LongToWide(frame, qm.FillMissing) + if err != nil { + errAppendDebug("failed to convert long to wide series when converting from dataframe", err, interpolatedQuery) + return + } + + // Before 8x, a special metric column was used to name time series. The LongToWide transforms that into a metric label on the value field. + // But that makes series name have both the value column name AND the metric name. So here we are removing the metric label here and moving it to the + // field name to get the same naming for the series as pre v8 + if len(originalData.Fields) == 3 { + for _, field := range frame.Fields { + if len(field.Labels) == 1 { // 7x only supported one label + name, ok := field.Labels["metric"] + if ok { + field.Name = name + field.Labels = nil + } + } + } + } + } + if qm.FillMissing != nil { + // we align the start-time + startUnixTime := qm.TimeRange.From.Unix() / int64(qm.Interval.Seconds()) * int64(qm.Interval.Seconds()) + alignedTimeRange := backend.TimeRange{ + From: time.Unix(startUnixTime, 0), + To: qm.TimeRange.To, + } + + var err error + frame, err = sqlutil.ResampleWideFrame(frame, qm.FillMissing, alignedTimeRange, qm.Interval) + if err != nil { + logger.Error("Failed to resample dataframe", "err", err) + frame.AppendNotices(data.Notice{Text: "Failed to resample dataframe", Severity: data.NoticeSeverityWarning}) + } + } + } + + queryResult.dataResponse.Frames = data.Frames{frame} + ch <- queryResult +} + +// Interpolate provides global macros/substitutions for all sql datasources. +var Interpolate = func(query backend.DataQuery, timeRange backend.TimeRange, timeInterval string, sql string) string { + interval := query.Interval + + sql = strings.ReplaceAll(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10)) + sql = strings.ReplaceAll(sql, "$__interval", gtime.FormatInterval(interval)) + sql = strings.ReplaceAll(sql, "$__unixEpochFrom()", fmt.Sprintf("%d", timeRange.From.UTC().Unix())) + sql = strings.ReplaceAll(sql, "$__unixEpochTo()", fmt.Sprintf("%d", timeRange.To.UTC().Unix())) + + return sql +} + +func (e *DataSourceHandler) newProcessCfg(query backend.DataQuery, queryContext context.Context, + rows *sql.Rows, interpolatedQuery string) (*dataQueryModel, error) { + columnNames, err := rows.Columns() + if err != nil { + return nil, err + } + columnTypes, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + qm := &dataQueryModel{ + columnTypes: columnTypes, + columnNames: columnNames, + timeIndex: -1, + timeEndIndex: -1, + metricIndex: -1, + metricPrefix: false, + queryContext: queryContext, + } + + queryJson := QueryJson{} + err = json.Unmarshal(query.JSON, &queryJson) + if err != nil { + return nil, err + } + + if queryJson.Fill { + qm.FillMissing = &data.FillMissing{} + qm.Interval = time.Duration(queryJson.FillInterval * float64(time.Second)) + switch strings.ToLower(queryJson.FillMode) { + case "null": + qm.FillMissing.Mode = data.FillModeNull + case "previous": + qm.FillMissing.Mode = data.FillModePrevious + case "value": + qm.FillMissing.Mode = data.FillModeValue + qm.FillMissing.Value = queryJson.FillValue + default: + } + } + + qm.TimeRange.From = query.TimeRange.From.UTC() + qm.TimeRange.To = query.TimeRange.To.UTC() + + switch queryJson.Format { + case "time_series": + qm.Format = dataQueryFormatSeries + case "table": + qm.Format = dataQueryFormatTable + default: + panic(fmt.Sprintf("Unrecognized query model format: %q", queryJson.Format)) + } + + for i, col := range qm.columnNames { + for _, tc := range e.timeColumnNames { + if col == tc { + qm.timeIndex = i + break + } + } + + if qm.Format == dataQueryFormatTable && col == "timeend" { + qm.timeEndIndex = i + continue + } + + switch col { + case "metric": + qm.metricIndex = i + default: + if qm.metricIndex == -1 { + columnType := qm.columnTypes[i].DatabaseTypeName() + for _, mct := range e.metricColumnTypes { + if columnType == mct { + qm.metricIndex = i + continue + } + } + } + } + } + qm.InterpolatedQuery = interpolatedQuery + return qm, nil +} + +// dataQueryFormat is the type of query. +type dataQueryFormat string + +const ( + // dataQueryFormatTable identifies a table query (default). + dataQueryFormatTable dataQueryFormat = "table" + // dataQueryFormatSeries identifies a time series query. + dataQueryFormatSeries dataQueryFormat = "time_series" +) + +type dataQueryModel struct { + InterpolatedQuery string // property not set until after Interpolate() + Format dataQueryFormat + TimeRange backend.TimeRange + FillMissing *data.FillMissing // property not set until after Interpolate() + Interval time.Duration + columnNames []string + columnTypes []*sql.ColumnType + timeIndex int + timeEndIndex int + metricIndex int + metricPrefix bool + queryContext context.Context +} + +func convertSQLTimeColumnsToEpochMS(frame *data.Frame, qm *dataQueryModel) error { + if qm.timeIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeIndex); err != nil { + return fmt.Errorf("%v: %w", "failed to convert time column", err) + } + } + + if qm.timeEndIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeEndIndex); err != nil { + return fmt.Errorf("%v: %w", "failed to convert timeend column", err) + } + } + + return nil +} + +// convertSQLTimeColumnToEpochMS converts column named time to unix timestamp in milliseconds +// to make native datetime types and epoch dates work in annotation and table queries. +func convertSQLTimeColumnToEpochMS(frame *data.Frame, timeIndex int) error { + if timeIndex < 0 || timeIndex >= len(frame.Fields) { + return fmt.Errorf("timeIndex %d is out of range", timeIndex) + } + + origin := frame.Fields[timeIndex] + valueType := origin.Type() + if valueType == data.FieldTypeTime || valueType == data.FieldTypeNullableTime { + return nil + } + + newField := data.NewFieldFromFieldType(data.FieldTypeNullableTime, 0) + newField.Name = origin.Name + newField.Labels = origin.Labels + + valueLength := origin.Len() + for i := 0; i < valueLength; i++ { + v, err := origin.NullableFloatAt(i) + if err != nil { + return fmt.Errorf("unable to convert data to a time field") + } + if v == nil { + newField.Append(nil) + } else { + timestamp := time.Unix(0, int64(epochPrecisionToMS(*v))*int64(time.Millisecond)) + newField.Append(×tamp) + } + } + frame.Fields[timeIndex] = newField + + return nil +} + +// convertSQLValueColumnToFloat converts timeseries value column to float. +func convertSQLValueColumnToFloat(frame *data.Frame, Index int) (*data.Frame, error) { + if Index < 0 || Index >= len(frame.Fields) { + return frame, fmt.Errorf("metricIndex %d is out of range", Index) + } + + origin := frame.Fields[Index] + valueType := origin.Type() + if valueType == data.FieldTypeFloat64 || valueType == data.FieldTypeNullableFloat64 { + return frame, nil + } + + newField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, origin.Len()) + newField.Name = origin.Name + newField.Labels = origin.Labels + + for i := 0; i < origin.Len(); i++ { + v, err := origin.NullableFloatAt(i) + if err != nil { + return frame, err + } + newField.Set(i, v) + } + + frame.Fields[Index] = newField + + return frame, nil +} + +func SetupFillmode(query *backend.DataQuery, interval time.Duration, fillmode string) error { + rawQueryProp := make(map[string]any) + queryBytes, err := query.JSON.MarshalJSON() + if err != nil { + return err + } + err = json.Unmarshal(queryBytes, &rawQueryProp) + if err != nil { + return err + } + rawQueryProp["fill"] = true + rawQueryProp["fillInterval"] = interval.Seconds() + + switch fillmode { + case "NULL": + rawQueryProp["fillMode"] = "null" + case "previous": + rawQueryProp["fillMode"] = "previous" + default: + rawQueryProp["fillMode"] = "value" + floatVal, err := strconv.ParseFloat(fillmode, 64) + if err != nil { + return fmt.Errorf("error parsing fill value %v", fillmode) + } + rawQueryProp["fillValue"] = floatVal + } + query.JSON, err = json.Marshal(rawQueryProp) + if err != nil { + return err + } + return nil +} + +type SQLMacroEngineBase struct{} + +func NewSQLMacroEngineBase() *SQLMacroEngineBase { + return &SQLMacroEngineBase{} +} + +func (m *SQLMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { + result := "" + lastIndex := 0 + + for _, v := range re.FindAllStringSubmatchIndex(str, -1) { + groups := []string{} + for i := 0; i < len(v); i += 2 { + groups = append(groups, str[v[i]:v[i+1]]) + } + + result += str[lastIndex:v[0]] + repl(groups) + lastIndex = v[1] + } + + return result + str[lastIndex:] +} + +// epochPrecisionToMS converts epoch precision to millisecond, if needed. +// Only seconds to milliseconds supported right now +func epochPrecisionToMS(value float64) float64 { + s := strconv.FormatFloat(value, 'e', -1, 64) + if strings.HasSuffix(s, "e+09") { + return value * float64(1e3) + } + + if strings.HasSuffix(s, "e+18") { + return value / float64(time.Millisecond) + } + + return value +} diff --git a/pkg/tsdb/sqleng/sql_engine_test.go b/pkg/tsdb/mssql/sqleng/sql_engine_test.go similarity index 99% rename from pkg/tsdb/sqleng/sql_engine_test.go rename to pkg/tsdb/mssql/sqleng/sql_engine_test.go index 367f6cb143a..2e3f8da78ee 100644 --- a/pkg/tsdb/sqleng/sql_engine_test.go +++ b/pkg/tsdb/mssql/sqleng/sql_engine_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/sqleng/util" + "github.com/grafana/grafana/pkg/tsdb/mssql/sqleng/util" ) func TestSQLEngine(t *testing.T) { diff --git a/pkg/tsdb/mssql/sqleng/util/util.go b/pkg/tsdb/mssql/sqleng/util/util.go new file mode 100644 index 00000000000..f6b5d330c17 --- /dev/null +++ b/pkg/tsdb/mssql/sqleng/util/util.go @@ -0,0 +1,3 @@ +package util + +func Pointer[T any](v T) *T { return &v } diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index 05e93c0aee5..15b67672968 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mysql/sqleng" ) const rsIdentifier = `([_a-zA-Z0-9]+)` diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 2e908afc93d..357663f02b8 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -22,7 +22,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mysql/sqleng" ) const ( diff --git a/pkg/tsdb/mysql/mysql_snapshot_test.go b/pkg/tsdb/mysql/mysql_snapshot_test.go index d2ff94f889c..c251fc23c42 100644 --- a/pkg/tsdb/mysql/mysql_snapshot_test.go +++ b/pkg/tsdb/mysql/mysql_snapshot_test.go @@ -16,7 +16,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/experimental" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mysql/sqleng" _ "github.com/go-sql-driver/mysql" ) diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index c01ec8bae6c..014c56cadf5 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -13,7 +13,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/tsdb/mysql/sqleng" ) // To run this test, set runMySqlTests=true diff --git a/pkg/tsdb/mysql/sqleng/sql_engine.go b/pkg/tsdb/mysql/sqleng/sql_engine.go new file mode 100644 index 00000000000..060e2bcb02e --- /dev/null +++ b/pkg/tsdb/mysql/sqleng/sql_engine.go @@ -0,0 +1,642 @@ +package sqleng + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "net" + "regexp" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +// MetaKeyExecutedQueryString is the key where the executed query should get stored +const MetaKeyExecutedQueryString = "executedQueryString" + +// SQLMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and +// timeRange to be able to generate queries that use from and to. +type SQLMacroEngine interface { + Interpolate(query *backend.DataQuery, timeRange backend.TimeRange, sql string) (string, error) +} + +// SqlQueryResultTransformer transforms a query result row to RowValues with proper types. +type SqlQueryResultTransformer interface { + // TransformQueryError transforms a query error. + TransformQueryError(logger log.Logger, err error) error + GetConverterList() []sqlutil.StringConverter +} + +type JsonData struct { + MaxOpenConns int `json:"maxOpenConns"` + MaxIdleConns int `json:"maxIdleConns"` + ConnMaxLifetime int `json:"connMaxLifetime"` + ConnectionTimeout int `json:"connectionTimeout"` + Timescaledb bool `json:"timescaledb"` + Mode string `json:"sslmode"` + ConfigurationMethod string `json:"tlsConfigurationMethod"` + TlsSkipVerify bool `json:"tlsSkipVerify"` + RootCertFile string `json:"sslRootCertFile"` + CertFile string `json:"sslCertFile"` + CertKeyFile string `json:"sslKeyFile"` + Timezone string `json:"timezone"` + Encrypt string `json:"encrypt"` + Servername string `json:"servername"` + TimeInterval string `json:"timeInterval"` + Database string `json:"database"` + SecureDSProxy bool `json:"enableSecureSocksProxy"` + SecureDSProxyUsername string `json:"secureSocksProxyUsername"` + AllowCleartextPasswords bool `json:"allowCleartextPasswords"` + AuthenticationType string `json:"authenticationType"` +} + +type DataSourceInfo struct { + JsonData JsonData + URL string + User string + Database string + ID int64 + Updated time.Time + UID string + DecryptedSecureJSONData map[string]string +} + +type DataPluginConfiguration struct { + DSInfo DataSourceInfo + TimeColumnNames []string + MetricColumnTypes []string + RowLimit int64 +} + +type DataSourceHandler struct { + macroEngine SQLMacroEngine + queryResultTransformer SqlQueryResultTransformer + db *sql.DB + timeColumnNames []string + metricColumnTypes []string + log log.Logger + dsInfo DataSourceInfo + rowLimit int64 + userError string +} + +type QueryJson struct { + RawSql string `json:"rawSql"` + Fill bool `json:"fill"` + FillInterval float64 `json:"fillInterval"` + FillMode string `json:"fillMode"` + FillValue float64 `json:"fillValue"` + Format string `json:"format"` +} + +func (e *DataSourceHandler) TransformQueryError(logger log.Logger, err error) error { + // OpError is the error type usually returned by functions in the net + // package. It describes the operation, network type, and address of + // an error. We log this error rather than return it to the client + // for security purposes. + var opErr *net.OpError + if errors.As(err, &opErr) { + logger.Error("Query error", "err", err) + return fmt.Errorf("failed to connect to server - %s", e.userError) + } + + return e.queryResultTransformer.TransformQueryError(logger, err) +} + +func NewQueryDataHandler(userFacingDefaultError string, db *sql.DB, config DataPluginConfiguration, queryResultTransformer SqlQueryResultTransformer, + macroEngine SQLMacroEngine, log log.Logger) (*DataSourceHandler, error) { + queryDataHandler := DataSourceHandler{ + queryResultTransformer: queryResultTransformer, + macroEngine: macroEngine, + timeColumnNames: []string{"time"}, + log: log, + dsInfo: config.DSInfo, + rowLimit: config.RowLimit, + userError: userFacingDefaultError, + } + + if len(config.TimeColumnNames) > 0 { + queryDataHandler.timeColumnNames = config.TimeColumnNames + } + + if len(config.MetricColumnTypes) > 0 { + queryDataHandler.metricColumnTypes = config.MetricColumnTypes + } + + queryDataHandler.db = db + return &queryDataHandler, nil +} + +type DBDataResponse struct { + dataResponse backend.DataResponse + refID string +} + +func (e *DataSourceHandler) Dispose() { + e.log.Debug("Disposing DB...") + if e.db != nil { + if err := e.db.Close(); err != nil { + e.log.Error("Failed to dispose db", "error", err) + } + } + e.log.Debug("DB disposed") +} + +func (e *DataSourceHandler) Ping() error { + return e.db.Ping() +} + +func (e *DataSourceHandler) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + result := backend.NewQueryDataResponse() + ch := make(chan DBDataResponse, len(req.Queries)) + var wg sync.WaitGroup + // Execute each query in a goroutine and wait for them to finish afterwards + for _, query := range req.Queries { + queryjson := QueryJson{ + Fill: false, + Format: "time_series", + } + err := json.Unmarshal(query.JSON, &queryjson) + if err != nil { + return nil, fmt.Errorf("error unmarshal query json: %w", err) + } + + // the fill-params are only stored inside this function, during query-interpolation. we do not support + // sending them in "from the outside" + if queryjson.Fill || queryjson.FillInterval != 0.0 || queryjson.FillMode != "" || queryjson.FillValue != 0.0 { + return nil, fmt.Errorf("query fill-parameters not supported") + } + + if queryjson.RawSql == "" { + continue + } + + wg.Add(1) + go e.executeQuery(query, &wg, ctx, ch, queryjson) + } + + wg.Wait() + + // Read results from channels + close(ch) + result.Responses = make(map[string]backend.DataResponse) + for queryResult := range ch { + result.Responses[queryResult.refID] = queryResult.dataResponse + } + + return result, nil +} + +func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitGroup, queryContext context.Context, + ch chan DBDataResponse, queryJson QueryJson) { + defer wg.Done() + queryResult := DBDataResponse{ + dataResponse: backend.DataResponse{}, + refID: query.RefID, + } + + logger := e.log.FromContext(queryContext) + + defer func() { + if r := recover(); r != nil { + logger.Error("ExecuteQuery panic", "error", r, "stack", string(debug.Stack())) + if theErr, ok := r.(error); ok { + queryResult.dataResponse.Error = theErr + } else if theErrString, ok := r.(string); ok { + queryResult.dataResponse.Error = fmt.Errorf(theErrString) + } else { + queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError) + } + ch <- queryResult + } + }() + + if queryJson.RawSql == "" { + panic("Query model property rawSql should not be empty at this point") + } + + timeRange := query.TimeRange + + errAppendDebug := func(frameErr string, err error, query string) { + var emptyFrame data.Frame + emptyFrame.SetMeta(&data.FrameMeta{ + ExecutedQueryString: query, + }) + queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err) + queryResult.dataResponse.Frames = data.Frames{&emptyFrame} + ch <- queryResult + } + + // global substitutions + interpolatedQuery := Interpolate(query, timeRange, e.dsInfo.JsonData.TimeInterval, queryJson.RawSql) + + // data source specific substitutions + interpolatedQuery, err := e.macroEngine.Interpolate(&query, timeRange, interpolatedQuery) + if err != nil { + errAppendDebug("interpolation failed", e.TransformQueryError(logger, err), interpolatedQuery) + return + } + + rows, err := e.db.QueryContext(queryContext, interpolatedQuery) + if err != nil { + errAppendDebug("db query error", e.TransformQueryError(logger, err), interpolatedQuery) + return + } + defer func() { + if err := rows.Close(); err != nil { + logger.Warn("Failed to close rows", "err", err) + } + }() + + qm, err := e.newProcessCfg(query, queryContext, rows, interpolatedQuery) + if err != nil { + errAppendDebug("failed to get configurations", err, interpolatedQuery) + return + } + + // Convert row.Rows to dataframe + stringConverters := e.queryResultTransformer.GetConverterList() + frame, err := sqlutil.FrameFromRows(rows, e.rowLimit, sqlutil.ToConverters(stringConverters...)...) + if err != nil { + errAppendDebug("convert frame from rows error", err, interpolatedQuery) + return + } + + if frame.Meta == nil { + frame.Meta = &data.FrameMeta{} + } + + frame.Meta.ExecutedQueryString = interpolatedQuery + + // If no rows were returned, clear any previously set `Fields` with a single empty `data.Field` slice. + // Then assign `queryResult.dataResponse.Frames` the current single frame with that single empty Field. + // This assures 1) our visualization doesn't display unwanted empty fields, and also that 2) + // additionally-needed frame data stays intact and is correctly passed to our visulization. + if frame.Rows() == 0 { + frame.Fields = []*data.Field{} + queryResult.dataResponse.Frames = data.Frames{frame} + ch <- queryResult + return + } + + if err := convertSQLTimeColumnsToEpochMS(frame, qm); err != nil { + errAppendDebug("converting time columns failed", err, interpolatedQuery) + return + } + + if qm.Format == dataQueryFormatSeries { + // time series has to have time column + if qm.timeIndex == -1 { + errAppendDebug("db has no time column", errors.New("no time column found"), interpolatedQuery) + return + } + + // Make sure to name the time field 'Time' to be backward compatible with Grafana pre-v8. + frame.Fields[qm.timeIndex].Name = data.TimeSeriesTimeFieldName + + for i := range qm.columnNames { + if i == qm.timeIndex || i == qm.metricIndex { + continue + } + + if t := frame.Fields[i].Type(); t == data.FieldTypeString || t == data.FieldTypeNullableString { + continue + } + + var err error + if frame, err = convertSQLValueColumnToFloat(frame, i); err != nil { + errAppendDebug("convert value to float failed", err, interpolatedQuery) + return + } + } + + tsSchema := frame.TimeSeriesSchema() + if tsSchema.Type == data.TimeSeriesTypeLong { + var err error + originalData := frame + frame, err = data.LongToWide(frame, qm.FillMissing) + if err != nil { + errAppendDebug("failed to convert long to wide series when converting from dataframe", err, interpolatedQuery) + return + } + + // Before 8x, a special metric column was used to name time series. The LongToWide transforms that into a metric label on the value field. + // But that makes series name have both the value column name AND the metric name. So here we are removing the metric label here and moving it to the + // field name to get the same naming for the series as pre v8 + if len(originalData.Fields) == 3 { + for _, field := range frame.Fields { + if len(field.Labels) == 1 { // 7x only supported one label + name, ok := field.Labels["metric"] + if ok { + field.Name = name + field.Labels = nil + } + } + } + } + } + if qm.FillMissing != nil { + // we align the start-time + startUnixTime := qm.TimeRange.From.Unix() / int64(qm.Interval.Seconds()) * int64(qm.Interval.Seconds()) + alignedTimeRange := backend.TimeRange{ + From: time.Unix(startUnixTime, 0), + To: qm.TimeRange.To, + } + + var err error + frame, err = sqlutil.ResampleWideFrame(frame, qm.FillMissing, alignedTimeRange, qm.Interval) + if err != nil { + logger.Error("Failed to resample dataframe", "err", err) + frame.AppendNotices(data.Notice{Text: "Failed to resample dataframe", Severity: data.NoticeSeverityWarning}) + } + } + } + + queryResult.dataResponse.Frames = data.Frames{frame} + ch <- queryResult +} + +// Interpolate provides global macros/substitutions for all sql datasources. +var Interpolate = func(query backend.DataQuery, timeRange backend.TimeRange, timeInterval string, sql string) string { + interval := query.Interval + + sql = strings.ReplaceAll(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10)) + sql = strings.ReplaceAll(sql, "$__interval", gtime.FormatInterval(interval)) + sql = strings.ReplaceAll(sql, "$__unixEpochFrom()", fmt.Sprintf("%d", timeRange.From.UTC().Unix())) + sql = strings.ReplaceAll(sql, "$__unixEpochTo()", fmt.Sprintf("%d", timeRange.To.UTC().Unix())) + + return sql +} + +func (e *DataSourceHandler) newProcessCfg(query backend.DataQuery, queryContext context.Context, + rows *sql.Rows, interpolatedQuery string) (*dataQueryModel, error) { + columnNames, err := rows.Columns() + if err != nil { + return nil, err + } + columnTypes, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + qm := &dataQueryModel{ + columnTypes: columnTypes, + columnNames: columnNames, + timeIndex: -1, + timeEndIndex: -1, + metricIndex: -1, + metricPrefix: false, + queryContext: queryContext, + } + + queryJson := QueryJson{} + err = json.Unmarshal(query.JSON, &queryJson) + if err != nil { + return nil, err + } + + if queryJson.Fill { + qm.FillMissing = &data.FillMissing{} + qm.Interval = time.Duration(queryJson.FillInterval * float64(time.Second)) + switch strings.ToLower(queryJson.FillMode) { + case "null": + qm.FillMissing.Mode = data.FillModeNull + case "previous": + qm.FillMissing.Mode = data.FillModePrevious + case "value": + qm.FillMissing.Mode = data.FillModeValue + qm.FillMissing.Value = queryJson.FillValue + default: + } + } + + qm.TimeRange.From = query.TimeRange.From.UTC() + qm.TimeRange.To = query.TimeRange.To.UTC() + + switch queryJson.Format { + case "time_series": + qm.Format = dataQueryFormatSeries + case "table": + qm.Format = dataQueryFormatTable + default: + panic(fmt.Sprintf("Unrecognized query model format: %q", queryJson.Format)) + } + + for i, col := range qm.columnNames { + for _, tc := range e.timeColumnNames { + if col == tc { + qm.timeIndex = i + break + } + } + + if qm.Format == dataQueryFormatTable && col == "timeend" { + qm.timeEndIndex = i + continue + } + + switch col { + case "metric": + qm.metricIndex = i + default: + if qm.metricIndex == -1 { + columnType := qm.columnTypes[i].DatabaseTypeName() + for _, mct := range e.metricColumnTypes { + if columnType == mct { + qm.metricIndex = i + continue + } + } + } + } + } + qm.InterpolatedQuery = interpolatedQuery + return qm, nil +} + +// dataQueryFormat is the type of query. +type dataQueryFormat string + +const ( + // dataQueryFormatTable identifies a table query (default). + dataQueryFormatTable dataQueryFormat = "table" + // dataQueryFormatSeries identifies a time series query. + dataQueryFormatSeries dataQueryFormat = "time_series" +) + +type dataQueryModel struct { + InterpolatedQuery string // property not set until after Interpolate() + Format dataQueryFormat + TimeRange backend.TimeRange + FillMissing *data.FillMissing // property not set until after Interpolate() + Interval time.Duration + columnNames []string + columnTypes []*sql.ColumnType + timeIndex int + timeEndIndex int + metricIndex int + metricPrefix bool + queryContext context.Context +} + +func convertSQLTimeColumnsToEpochMS(frame *data.Frame, qm *dataQueryModel) error { + if qm.timeIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeIndex); err != nil { + return fmt.Errorf("%v: %w", "failed to convert time column", err) + } + } + + if qm.timeEndIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeEndIndex); err != nil { + return fmt.Errorf("%v: %w", "failed to convert timeend column", err) + } + } + + return nil +} + +// convertSQLTimeColumnToEpochMS converts column named time to unix timestamp in milliseconds +// to make native datetime types and epoch dates work in annotation and table queries. +func convertSQLTimeColumnToEpochMS(frame *data.Frame, timeIndex int) error { + if timeIndex < 0 || timeIndex >= len(frame.Fields) { + return fmt.Errorf("timeIndex %d is out of range", timeIndex) + } + + origin := frame.Fields[timeIndex] + valueType := origin.Type() + if valueType == data.FieldTypeTime || valueType == data.FieldTypeNullableTime { + return nil + } + + newField := data.NewFieldFromFieldType(data.FieldTypeNullableTime, 0) + newField.Name = origin.Name + newField.Labels = origin.Labels + + valueLength := origin.Len() + for i := 0; i < valueLength; i++ { + v, err := origin.NullableFloatAt(i) + if err != nil { + return fmt.Errorf("unable to convert data to a time field") + } + if v == nil { + newField.Append(nil) + } else { + timestamp := time.Unix(0, int64(epochPrecisionToMS(*v))*int64(time.Millisecond)) + newField.Append(×tamp) + } + } + frame.Fields[timeIndex] = newField + + return nil +} + +// convertSQLValueColumnToFloat converts timeseries value column to float. +func convertSQLValueColumnToFloat(frame *data.Frame, Index int) (*data.Frame, error) { + if Index < 0 || Index >= len(frame.Fields) { + return frame, fmt.Errorf("metricIndex %d is out of range", Index) + } + + origin := frame.Fields[Index] + valueType := origin.Type() + if valueType == data.FieldTypeFloat64 || valueType == data.FieldTypeNullableFloat64 { + return frame, nil + } + + newField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, origin.Len()) + newField.Name = origin.Name + newField.Labels = origin.Labels + + for i := 0; i < origin.Len(); i++ { + v, err := origin.NullableFloatAt(i) + if err != nil { + return frame, err + } + newField.Set(i, v) + } + + frame.Fields[Index] = newField + + return frame, nil +} + +func SetupFillmode(query *backend.DataQuery, interval time.Duration, fillmode string) error { + rawQueryProp := make(map[string]any) + queryBytes, err := query.JSON.MarshalJSON() + if err != nil { + return err + } + err = json.Unmarshal(queryBytes, &rawQueryProp) + if err != nil { + return err + } + rawQueryProp["fill"] = true + rawQueryProp["fillInterval"] = interval.Seconds() + + switch fillmode { + case "NULL": + rawQueryProp["fillMode"] = "null" + case "previous": + rawQueryProp["fillMode"] = "previous" + default: + rawQueryProp["fillMode"] = "value" + floatVal, err := strconv.ParseFloat(fillmode, 64) + if err != nil { + return fmt.Errorf("error parsing fill value %v", fillmode) + } + rawQueryProp["fillValue"] = floatVal + } + query.JSON, err = json.Marshal(rawQueryProp) + if err != nil { + return err + } + return nil +} + +type SQLMacroEngineBase struct{} + +func NewSQLMacroEngineBase() *SQLMacroEngineBase { + return &SQLMacroEngineBase{} +} + +func (m *SQLMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { + result := "" + lastIndex := 0 + + for _, v := range re.FindAllStringSubmatchIndex(str, -1) { + groups := []string{} + for i := 0; i < len(v); i += 2 { + groups = append(groups, str[v[i]:v[i+1]]) + } + + result += str[lastIndex:v[0]] + repl(groups) + lastIndex = v[1] + } + + return result + str[lastIndex:] +} + +// epochPrecisionToMS converts epoch precision to millisecond, if needed. +// Only seconds to milliseconds supported right now +func epochPrecisionToMS(value float64) float64 { + s := strconv.FormatFloat(value, 'e', -1, 64) + if strings.HasSuffix(s, "e+09") { + return value * float64(1e3) + } + + if strings.HasSuffix(s, "e+18") { + return value / float64(time.Millisecond) + } + + return value +} diff --git a/pkg/tsdb/mysql/sqleng/sql_engine_test.go b/pkg/tsdb/mysql/sqleng/sql_engine_test.go new file mode 100644 index 00000000000..d8bc7c39ff5 --- /dev/null +++ b/pkg/tsdb/mysql/sqleng/sql_engine_test.go @@ -0,0 +1,438 @@ +package sqleng + +import ( + "fmt" + "net" + "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana/pkg/tsdb/mysql/sqleng/util" +) + +func TestSQLEngine(t *testing.T) { + dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC) + + t.Run("Handle interpolating $__interval and $__interval_ms", func(t *testing.T) { + from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) + to := from.Add(5 * time.Minute) + timeRange := backend.TimeRange{From: from, To: to} + + text := "$__interval $__timeGroupAlias(time,$__interval) $__interval_ms" + + t.Run("interpolate 10 minutes $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Minute * 10} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "10m $__timeGroupAlias(time,10m) 600000", sql) + }) + + t.Run("interpolate 4seconds $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Second * 4} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "4s $__timeGroupAlias(time,4s) 4000", sql) + }) + + t.Run("interpolate 200 milliseconds $__interval", func(t *testing.T) { + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Millisecond * 200} + sql := Interpolate(query, timeRange, "", text) + require.Equal(t, "200ms $__timeGroupAlias(time,200ms) 200", sql) + }) + }) + + t.Run("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func(t *testing.T) { + from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) + to := from.Add(5 * time.Minute) + timeRange := backend.TimeRange{From: from, To: to} + query := backend.DataQuery{JSON: []byte("{}"), MaxDataPoints: 1500, Interval: time.Second * 60} + + t.Run("interpolate __unixEpochFrom function", func(t *testing.T) { + sql := Interpolate(query, timeRange, "", "select $__unixEpochFrom()") + require.Equal(t, fmt.Sprintf("select %d", from.Unix()), sql) + }) + + t.Run("interpolate __unixEpochTo function", func(t *testing.T) { + sql := Interpolate(query, timeRange, "", "select $__unixEpochTo()") + require.Equal(t, fmt.Sprintf("select %d", to.Unix()), sql) + }) + }) + + t.Run("Given row values with int64 as time columns", func(t *testing.T) { + tSeconds := dt.Unix() + tMilliseconds := dt.UnixNano() / 1e6 + tNanoSeconds := dt.UnixNano() + var nilPointer *int64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []int64{ + tSeconds, + }), + data.NewField("time2", nil, []*int64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []int64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*int64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []int64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*int64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*int64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with uint64 as time columns", func(t *testing.T) { + tSeconds := uint64(dt.Unix()) + tMilliseconds := uint64(dt.UnixNano() / 1e6) + tNanoSeconds := uint64(dt.UnixNano()) + var nilPointer *uint64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []uint64{ + tSeconds, + }), + data.NewField("time2", nil, []*uint64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []uint64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*uint64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []uint64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*uint64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*uint64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with int32 as time columns", func(t *testing.T) { + tSeconds := int32(dt.Unix()) + var nilInt *int32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []int32{ + tSeconds, + }), + data.NewField("time2", nil, []*int32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*int32{ + nilInt, + }), + ) + for i := 0; i < 3; i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row values with uint32 as time columns", func(t *testing.T) { + tSeconds := uint32(dt.Unix()) + var nilInt *uint32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []uint32{ + tSeconds, + }), + data.NewField("time2", nil, []*uint32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*uint32{ + nilInt, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row values with float64 as time columns", func(t *testing.T) { + tSeconds := float64(dt.UnixNano()) / float64(time.Second) + tMilliseconds := float64(dt.UnixNano()) / float64(time.Millisecond) + tNanoSeconds := float64(dt.UnixNano()) + var nilPointer *float64 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []float64{ + tSeconds, + }), + data.NewField("time2", nil, []*float64{ + util.Pointer(tSeconds), + }), + data.NewField("time3", nil, []float64{ + tMilliseconds, + }), + data.NewField("time4", nil, []*float64{ + util.Pointer(tMilliseconds), + }), + data.NewField("time5", nil, []float64{ + tNanoSeconds, + }), + data.NewField("time6", nil, []*float64{ + util.Pointer(tNanoSeconds), + }), + data.NewField("time7", nil, []*float64{ + nilPointer, + }), + ) + + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + + require.Equal(t, dt.Unix(), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[2].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[3].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[4].At(0).(*time.Time)).Unix()) + require.Equal(t, dt.Unix(), (*originFrame.Fields[5].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[6].At(0)) + }) + + t.Run("Given row values with float32 as time columns", func(t *testing.T) { + tSeconds := float32(dt.Unix()) + var nilInt *float32 + + originFrame := data.NewFrame("", + data.NewField("time1", nil, []float32{ + tSeconds, + }), + data.NewField("time2", nil, []*float32{ + util.Pointer(tSeconds), + }), + data.NewField("time7", nil, []*float32{ + nilInt, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + err := convertSQLTimeColumnToEpochMS(originFrame, i) + require.NoError(t, err) + } + require.Equal(t, int64(tSeconds), (*originFrame.Fields[0].At(0).(*time.Time)).Unix()) + require.Equal(t, int64(tSeconds), (*originFrame.Fields[1].At(0).(*time.Time)).Unix()) + require.Nil(t, originFrame.Fields[2].At(0)) + }) + + t.Run("Given row with value columns, would be converted to float64", func(t *testing.T) { + originFrame := data.NewFrame("", + data.NewField("value1", nil, []int64{ + int64(1), + }), + data.NewField("value2", nil, []*int64{ + util.Pointer(int64(1)), + }), + data.NewField("value3", nil, []int32{ + int32(1), + }), + data.NewField("value4", nil, []*int32{ + util.Pointer(int32(1)), + }), + data.NewField("value5", nil, []int16{ + int16(1), + }), + data.NewField("value6", nil, []*int16{ + util.Pointer(int16(1)), + }), + data.NewField("value7", nil, []int8{ + int8(1), + }), + data.NewField("value8", nil, []*int8{ + util.Pointer(int8(1)), + }), + data.NewField("value9", nil, []float64{ + float64(1), + }), + data.NewField("value10", nil, []*float64{ + util.Pointer(1.0), + }), + data.NewField("value11", nil, []float32{ + float32(1), + }), + data.NewField("value12", nil, []*float32{ + util.Pointer(float32(1)), + }), + data.NewField("value13", nil, []uint64{ + uint64(1), + }), + data.NewField("value14", nil, []*uint64{ + util.Pointer(uint64(1)), + }), + data.NewField("value15", nil, []uint32{ + uint32(1), + }), + data.NewField("value16", nil, []*uint32{ + util.Pointer(uint32(1)), + }), + data.NewField("value17", nil, []uint16{ + uint16(1), + }), + data.NewField("value18", nil, []*uint16{ + util.Pointer(uint16(1)), + }), + data.NewField("value19", nil, []uint8{ + uint8(1), + }), + data.NewField("value20", nil, []*uint8{ + util.Pointer(uint8(1)), + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + _, err := convertSQLValueColumnToFloat(originFrame, i) + require.NoError(t, err) + if i == 8 { + require.Equal(t, float64(1), originFrame.Fields[i].At(0).(float64)) + } else { + require.NotNil(t, originFrame.Fields[i].At(0).(*float64)) + require.Equal(t, float64(1), *originFrame.Fields[i].At(0).(*float64)) + } + } + }) + + t.Run("Given row with nil value columns", func(t *testing.T) { + var int64NilPointer *int64 + var int32NilPointer *int32 + var int16NilPointer *int16 + var int8NilPointer *int8 + var float64NilPointer *float64 + var float32NilPointer *float32 + var uint64NilPointer *uint64 + var uint32NilPointer *uint32 + var uint16NilPointer *uint16 + var uint8NilPointer *uint8 + + originFrame := data.NewFrame("", + data.NewField("value1", nil, []*int64{ + int64NilPointer, + }), + data.NewField("value2", nil, []*int32{ + int32NilPointer, + }), + data.NewField("value3", nil, []*int16{ + int16NilPointer, + }), + data.NewField("value4", nil, []*int8{ + int8NilPointer, + }), + data.NewField("value5", nil, []*float64{ + float64NilPointer, + }), + data.NewField("value6", nil, []*float32{ + float32NilPointer, + }), + data.NewField("value7", nil, []*uint64{ + uint64NilPointer, + }), + data.NewField("value8", nil, []*uint32{ + uint32NilPointer, + }), + data.NewField("value9", nil, []*uint16{ + uint16NilPointer, + }), + data.NewField("value10", nil, []*uint8{ + uint8NilPointer, + }), + ) + for i := 0; i < len(originFrame.Fields); i++ { + t.Run("", func(t *testing.T) { + _, err := convertSQLValueColumnToFloat(originFrame, i) + require.NoError(t, err) + require.Nil(t, originFrame.Fields[i].At(0)) + }) + } + }) + + t.Run("Should not return raw connection errors", func(t *testing.T) { + err := net.OpError{Op: "Dial", Err: fmt.Errorf("inner-error")} + transformer := &testQueryResultTransformer{} + dp := DataSourceHandler{ + log: backend.NewLoggerWith("logger", "test"), + queryResultTransformer: transformer, + } + resultErr := dp.TransformQueryError(dp.log, &err) + assert.False(t, transformer.transformQueryErrorWasCalled) + errorText := resultErr.Error() + assert.NotEqual(t, err, resultErr) + assert.NotContains(t, errorText, "inner-error") + assert.Contains(t, errorText, "failed to connect to server") + }) + + t.Run("Should return non-connection errors unmodified", func(t *testing.T) { + err := fmt.Errorf("normal error") + transformer := &testQueryResultTransformer{} + dp := DataSourceHandler{ + log: backend.NewLoggerWith("logger", "test"), + queryResultTransformer: transformer, + } + resultErr := dp.TransformQueryError(dp.log, err) + assert.True(t, transformer.transformQueryErrorWasCalled) + assert.Equal(t, err, resultErr) + assert.ErrorIs(t, err, resultErr) + }) +} + +type testQueryResultTransformer struct { + transformQueryErrorWasCalled bool +} + +func (t *testQueryResultTransformer) TransformQueryError(_ log.Logger, err error) error { + t.transformQueryErrorWasCalled = true + return err +} + +func (t *testQueryResultTransformer) GetConverterList() []sqlutil.StringConverter { + return nil +} diff --git a/pkg/tsdb/mysql/sqleng/util/util.go b/pkg/tsdb/mysql/sqleng/util/util.go new file mode 100644 index 00000000000..f6b5d330c17 --- /dev/null +++ b/pkg/tsdb/mysql/sqleng/util/util.go @@ -0,0 +1,3 @@ +package util + +func Pointer[T any](v T) *T { return &v } diff --git a/pkg/tsdb/mysql/standalone/datasource.go b/pkg/tsdb/mysql/standalone/datasource.go new file mode 100644 index 00000000000..3a2a1e160db --- /dev/null +++ b/pkg/tsdb/mysql/standalone/datasource.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" + mysqlds "github.com/grafana/grafana/pkg/tsdb/mysql" +) + +var ( + _ backend.QueryDataHandler = (*Datasource)(nil) + _ backend.CheckHealthHandler = (*Datasource)(nil) +) + +func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { + return &Datasource{ + Service: mysqlds.ProvideService(), + }, nil +} + +type Datasource struct { + Service *mysqlds.Service +} + +func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + return d.Service.QueryData(ctx, req) +} + +func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + return d.Service.CheckHealth(ctx, req) +} diff --git a/pkg/tsdb/mysql/standalone/main.go b/pkg/tsdb/mysql/standalone/main.go new file mode 100644 index 00000000000..fd78c834330 --- /dev/null +++ b/pkg/tsdb/mysql/standalone/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + + "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +func main() { + // Start listening to requests sent from Grafana. This call is blocking so + // it won't finish until Grafana shuts down the process or the plugin choose + // to exit by itself using os.Exit. Manage automatically manages life cycle + // of datasource instances. It accepts datasource instance factory as first + // argument. This factory will be automatically called on incoming request + // from Grafana to create different instances of SampleDatasource (per datasource + // ID). When datasource configuration changed Dispose method will be called and + // new datasource instance created using NewSampleDatasource factory. + if err := datasource.Manage("mysql", NewDatasource, datasource.ManageOpts{}); err != nil { + log.DefaultLogger.Error(err.Error()) + os.Exit(1) + } +} diff --git a/public/api-merged.json b/public/api-merged.json index 2bea2e006eb..c1c20da0c81 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -48,7 +48,7 @@ }, "/access-control/roles": { "get": { - "description": "Gets all existing roles. The response contains all global and organization local roles, for the organization which user is signed in.\n\nYou need to have a permission with action `roles:read` and scope `roles:*`.", + "description": "Gets all existing roles. The response contains all global and organization local roles, for the organization which user is signed in.\n\nYou need to have a permission with action `roles:read` and scope `roles:*`.\n\nThe `delegatable` flag reduces the set of roles to only those for which the signed-in user has permissions to assign.", "tags": [ "access_control", "enterprise" @@ -60,6 +60,11 @@ "type": "boolean", "name": "delegatable", "in": "query" + }, + { + "type": "boolean", + "name": "includeHidden", + "in": "query" } ], "responses": { diff --git a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx index c4ab8a1b183..2cbf681224e 100644 --- a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx +++ b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx @@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { Field, Input, Button, Legend, Container, useStyles2, HorizontalGroup, LinkButton } from '@grafana/ui'; +import { Field, Input, Button, Legend, Container, useStyles2, LinkButton, Stack } from '@grafana/ui'; import config from 'app/core/config'; interface EmailDTO { @@ -63,12 +63,12 @@ export const ForgottenPassword = () => { {...register('userOrEmail', { required: 'Email or username is required' })} /> - + Back to login - +

Did you forget your username or email? Contact your Grafana administrator.

diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx index c19e7221517..08f7a7f6cb0 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; // fetch polyfill import { fireEvent, render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HttpResponse, http } from 'msw'; diff --git a/public/app/core/components/RolePicker/RolePickerMenu.tsx b/public/app/core/components/RolePicker/RolePickerMenu.tsx index 1bfe1d63ce7..cb542c416ce 100644 --- a/public/app/core/components/RolePicker/RolePickerMenu.tsx +++ b/public/app/core/components/RolePicker/RolePickerMenu.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import React, { useEffect, useRef, useState } from 'react'; -import { Button, CustomScrollbar, HorizontalGroup, TextLink, useStyles2, useTheme2 } from '@grafana/ui'; +import { Button, CustomScrollbar, Stack, TextLink, useStyles2, useTheme2 } from '@grafana/ui'; import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { OrgRole, Role } from 'app/types'; @@ -256,14 +256,14 @@ export const RolePickerMenu = ({ ))}
- + - +
diff --git a/public/app/core/components/RolePicker/RolePickerSubMenu.tsx b/public/app/core/components/RolePicker/RolePickerSubMenu.tsx index 4f6b5aa2f9f..2c5ba1b151f 100644 --- a/public/app/core/components/RolePicker/RolePickerSubMenu.tsx +++ b/public/app/core/components/RolePicker/RolePickerSubMenu.tsx @@ -1,7 +1,7 @@ import { cx } from '@emotion/css'; import React from 'react'; -import { Button, CustomScrollbar, HorizontalGroup, useStyles2, useTheme2 } from '@grafana/ui'; +import { Button, CustomScrollbar, Stack, useStyles2, useTheme2 } from '@grafana/ui'; import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { Role } from 'app/types'; @@ -65,11 +65,11 @@ export const RolePickerSubMenu = ({
- + - +
); diff --git a/public/app/core/history/richHistoryLocalStorageUtils.ts b/public/app/core/history/richHistoryLocalStorageUtils.ts index 409bc4c64d6..0cd8da310cb 100644 --- a/public/app/core/history/richHistoryLocalStorageUtils.ts +++ b/public/app/core/history/richHistoryLocalStorageUtils.ts @@ -94,7 +94,7 @@ export const sortQueries = (array: RichHistoryQuery[], sortOrder: SortOrder) => export const RICH_HISTORY_SETTING_KEYS = { retentionPeriod: 'grafana.explore.richHistory.retentionPeriod', starredTabAsFirstTab: 'grafana.explore.richHistory.starredTabAsFirstTab', - legacyActiveDatasourceOnly: 'grafana.explore.richHistory.activeDatasourceOnly', + legacyActiveDatasourceOnly: 'grafana.explore.richHistory.activeDatasourceOnly', // @deprecated activeDatasourcesOnly: 'grafana.explore.richHistory.activeDatasourcesOnly', datasourceFilters: 'grafana.explore.richHistory.datasourceFilters', }; diff --git a/public/app/core/internationalization/index.tsx b/public/app/core/internationalization/index.tsx index 1b9d850c6d0..4d3e4bf047d 100644 --- a/public/app/core/internationalization/index.tsx +++ b/public/app/core/internationalization/index.tsx @@ -1,32 +1,17 @@ -import i18n, { BackendModule, InitOptions } from 'i18next'; +import i18n, { InitOptions, TFunction } from 'i18next'; import LanguageDetector, { DetectorOptions } from 'i18next-browser-languagedetector'; import React from 'react'; import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // eslint-disable-line no-restricted-imports -import { DEFAULT_LANGUAGE, LANGUAGES, VALID_LANGUAGES } from './constants'; +import { DEFAULT_LANGUAGE, VALID_LANGUAGES } from './constants'; +import { loadTranslations } from './loadTranslations'; -const getLanguagePartFromCode = (code: string) => code.split('-')[0].toLowerCase(); - -const loadTranslations: BackendModule = { - type: 'backend', - init() {}, - async read(language, namespace, callback) { - let localeDef = LANGUAGES.find((v) => v.code === language); - if (!localeDef) { - localeDef = LANGUAGES.find((v) => getLanguagePartFromCode(v.code) === getLanguagePartFromCode(language)); - } - if (!localeDef) { - return callback(new Error('No message loader available for ' + language), null); - } - const messages = await localeDef.loader(); - callback(null, messages); - }, -}; +let tFunc: TFunction | undefined; export function initializeI18n(language: string): Promise<{ language: string | undefined }> { // This is a placeholder so we can put a 'comment' in the message json files. // Starts with an underscore so it's sorted to the top of the file. Even though it is in a comment the following line is still extracted - // t('_comment', 'This file is the source of truth for English strings. Edit this to change plurals and other phrases for the UI.'); + // t('_comment', 'The code is the source of truth for English phrases. They should be updated in the components directly, and additional plurals specified in this file.'); const options: InitOptions = { // We don't bundle any translations, we load them async @@ -54,6 +39,8 @@ export function initializeI18n(language: string): Promise<{ language: string | u .use(initReactI18next) // passes i18n down to react-i18next .init(options); + tFunc = i18n.t; + return loadPromise.then(() => { return { language: i18nInstance.resolvedLanguage, @@ -70,10 +57,22 @@ export const Trans: typeof I18NextTrans = (props) => { return ; }; -// Reassign t() so i18next-parser doesn't warn on dynamic key, and we can have 'failOnWarnings' enabled -const tFunc = i18n.t; - +// Wrap t() to provide default namespaces and enforce a consistent API export const t = (id: string, defaultMessage: string, values?: Record) => { + if (!tFunc) { + if (process.env.NODE_ENV !== 'test') { + console.warn( + 't() was called before i18n was initialized. This is probably caused by calling t() in the root module scope, instead of lazily on render' + ); + } + + if (process.env.NODE_ENV === 'development') { + throw new Error('t() was called before i18n was initialized'); + } + + tFunc = i18n.t; + } + return tFunc(id, defaultMessage, values); }; diff --git a/public/app/core/internationalization/loadTranslations.ts b/public/app/core/internationalization/loadTranslations.ts new file mode 100644 index 00000000000..3e618027ee4 --- /dev/null +++ b/public/app/core/internationalization/loadTranslations.ts @@ -0,0 +1,21 @@ +import { BackendModule } from 'i18next'; + +import { LANGUAGES } from './constants'; + +const getLanguagePartFromCode = (code: string) => code.split('-')[0].toLowerCase(); + +export const loadTranslations: BackendModule = { + type: 'backend', + init() {}, + async read(language, namespace, callback) { + let localeDef = LANGUAGES.find((v) => v.code === language); + if (!localeDef) { + localeDef = LANGUAGES.find((v) => getLanguagePartFromCode(v.code) === getLanguagePartFromCode(language)); + } + if (!localeDef) { + return callback(new Error('No message loader available for ' + language), null); + } + const messages = await localeDef.loader(); + callback(null, messages); + }, +}; diff --git a/public/app/core/services/__mocks__/backend_srv.ts b/public/app/core/services/__mocks__/backend_srv.ts index 92bd8b24d77..9659824f263 100644 --- a/public/app/core/services/__mocks__/backend_srv.ts +++ b/public/app/core/services/__mocks__/backend_srv.ts @@ -1,5 +1,3 @@ -import 'whatwg-fetch'; // fetch polyfill needed for Headers - import { of } from 'rxjs'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; diff --git a/public/app/core/specs/backend_srv.test.ts b/public/app/core/specs/backend_srv.test.ts index 075ec6f0095..ac96cfc2572 100644 --- a/public/app/core/specs/backend_srv.test.ts +++ b/public/app/core/specs/backend_srv.test.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering import { Observable, of, lastValueFrom } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { delay } from 'rxjs/operators'; diff --git a/public/app/core/utils/fetch.test.ts b/public/app/core/utils/fetch.test.ts index de7130d48cf..556c3b40d0b 100644 --- a/public/app/core/utils/fetch.test.ts +++ b/public/app/core/utils/fetch.test.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering import { isContentTypeApplicationJson, parseBody, diff --git a/public/app/features/admin/Users/UsersTable.tsx b/public/app/features/admin/Users/UsersTable.tsx index 8043c6e061c..1c23998d5c5 100644 --- a/public/app/features/admin/Users/UsersTable.tsx +++ b/public/app/features/admin/Users/UsersTable.tsx @@ -55,7 +55,7 @@ export const UsersTable = ({ header: 'Login', cell: ({ row: { original } }: Cell<'login'>) => { return ( - + {original.login} ); diff --git a/public/app/features/alerting/unified/AlertGroups.test.tsx b/public/app/features/alerting/unified/AlertGroups.test.tsx index d6237e418f5..5771a296785 100644 --- a/public/app/features/alerting/unified/AlertGroups.test.tsx +++ b/public/app/features/alerting/unified/AlertGroups.test.tsx @@ -1,4 +1,4 @@ -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; @@ -56,6 +56,7 @@ const ui = { groupByContainer: byTestId('group-by-container'), groupByInput: byRole('combobox', { name: /group by label keys/i }), clearButton: byRole('button', { name: 'Clear filters' }), + loadingIndicator: byText('Loading notifications'), }; describe('AlertGroups', () => { @@ -66,20 +67,24 @@ describe('AlertGroups', () => { AccessControlAction.AlertingInstancesExternalRead, AccessControlAction.AlertingRuleRead, ]); - - mocks.api.fetchAlertGroups.mockImplementation(() => { - return Promise.resolve([ - mockAlertGroup({ labels: {}, alerts: [mockAlertmanagerAlert({ labels: { foo: 'bar' } })] }), - mockAlertGroup(), - ]); - }); }); beforeEach(() => { setDataSourceSrv(new MockDataSourceSrv(dataSources)); }); + afterEach(() => { + mocks.api.fetchAlertGroups.mockClear(); + }); + it('loads and shows groups', async () => { + mocks.api.fetchAlertGroups.mockImplementation(() => { + return Promise.resolve([ + mockAlertGroup({ labels: {}, alerts: [mockAlertmanagerAlert({ labels: { foo: 'bar' } })] }), + mockAlertGroup(), + ]); + }); + renderAmNotifications(); await waitFor(() => expect(mocks.api.fetchAlertGroups).toHaveBeenCalled()); @@ -105,9 +110,12 @@ describe('AlertGroups', () => { mockAlertGroup({ labels: { region }, alerts: [ - mockAlertmanagerAlert({ labels: { region, appName: 'billing', env: 'production' } }), - mockAlertmanagerAlert({ labels: { region, appName: 'auth', env: 'staging', uniqueLabel: 'true' } }), - mockAlertmanagerAlert({ labels: { region, appName: 'frontend', env: 'production' } }), + mockAlertmanagerAlert({ fingerprint: '1', labels: { region, appName: 'billing', env: 'production' } }), + mockAlertmanagerAlert({ + fingerprint: '2', + labels: { region, appName: 'auth', env: 'staging', uniqueLabel: 'true' }, + }), + mockAlertmanagerAlert({ fingerprint: '3', labels: { region, appName: 'frontend', env: 'production' } }), ], }) ); @@ -161,6 +169,33 @@ describe('AlertGroups', () => { expect(groups[1]).toHaveTextContent('uniqueLabeltrue'); }); + it('should split custom grouping groups with the same label by receiver', async () => { + // The same alert is repeated in two groups with different receivers + const alert = mockAlertmanagerAlert({ + fingerprint: '1', + labels: { region: 'NASA', appName: 'billing' }, + receivers: [{ name: 'slack' }, { name: 'email' }], + }); + const amGroups = [ + mockAlertGroup({ receiver: { name: 'slack' }, labels: { region: 'NASA' }, alerts: [alert] }), + mockAlertGroup({ receiver: { name: 'email' }, labels: { region: 'NASA' }, alerts: [alert] }), + ]; + mocks.api.fetchAlertGroups.mockResolvedValue(amGroups); + + const user = userEvent.setup(); + + renderAmNotifications(); + await waitForElementToBeRemoved(ui.loadingIndicator.query()); + + await user.type(ui.groupByInput.get(), 'appName{enter}'); + + const groups = await ui.group.findAll(); + + expect(groups).toHaveLength(2); + expect(groups[0]).toHaveTextContent('appNamebillingDelivered to slack'); + expect(groups[1]).toHaveTextContent('appNamebillingDelivered to email'); + }); + it('should combine multiple ungrouped groups', async () => { mocks.api.fetchAlertGroups.mockImplementation(() => { const groups = [ diff --git a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx index 747f66b4eb1..c2f77b60a46 100644 --- a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react'; import { setupServer } from 'msw/node'; import React from 'react'; diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx index a1df6a1be3e..9c70f018da8 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx @@ -4,14 +4,13 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { byTestId } from 'testing-library-selector'; import { DataSourceApi } from '@grafana/data'; +import { PromOptions, PrometheusDatasource } from '@grafana/prometheus'; import { setDataSourceSrv } from '@grafana/runtime'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { toggleOption } from 'app/features/variables/pickers/OptionsPicker/reducer'; import { toKeyedAction } from 'app/features/variables/state/keyedVariablesReducer'; -import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource'; -import { PromOptions } from 'app/plugins/datasource/prometheus/types'; import { configureStore } from 'app/store/configureStore'; import { AccessControlAction } from 'app/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index 096c594ae83..cdd52a78046 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -7,6 +7,11 @@ import { byRole } from 'testing-library-selector'; import { setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; +import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { + defaultAlertmanagerChoiceResponse, + mockAlertmanagerChoiceResponse, +} from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; import { AccessControlAction } from 'app/types'; import { GrafanaAlertStateDecision, PromApplication } from 'app/types/unified-alerting-dto'; @@ -61,9 +66,13 @@ const mocks = { }, }; +const server = setupMswServer(); + const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); describe('RuleEditor grafana managed rules', () => { beforeEach(() => { + mockApi(server).eval({ results: {} }); + mockAlertmanagerChoiceResponse(server, defaultAlertmanagerChoiceResponse); jest.clearAllMocks(); contextSrv.isEditor = true; contextSrv.hasEditPermissionInFolders = true; diff --git a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx index 8b9455201f4..63f6ea53268 100644 --- a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx @@ -7,6 +7,7 @@ import { byText } from 'testing-library-selector'; import { setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; +import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import { AccessControlAction } from 'app/types'; import { PromApplication } from 'app/types/unified-alerting-dto'; @@ -93,8 +94,11 @@ const mocks = { const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); +const server = setupMswServer(); + describe('RuleEditor recording rules', () => { beforeEach(() => { + mockApi(server).eval({ results: {} }); jest.clearAllMocks(); contextSrv.isEditor = true; contextSrv.hasEditPermissionInFolders = true; diff --git a/public/app/features/alerting/unified/components/DynamicTable.tsx b/public/app/features/alerting/unified/components/DynamicTable.tsx index 8253423dae6..1665ee8e6fe 100644 --- a/public/app/features/alerting/unified/components/DynamicTable.tsx +++ b/public/app/features/alerting/unified/components/DynamicTable.tsx @@ -54,7 +54,7 @@ export interface DynamicTableProps { items: Array> ) => ReactNode; - footerRow?: JSX.Element; + footerRow?: React.ReactNode; } export const DynamicTable = ({ diff --git a/public/app/features/alerting/unified/components/Expression.tsx b/public/app/features/alerting/unified/components/Expression.tsx index dce352a0f92..ec11e41a58f 100644 --- a/public/app/features/alerting/unified/components/Expression.tsx +++ b/public/app/features/alerting/unified/components/Expression.tsx @@ -4,9 +4,9 @@ import React, { FC, useMemo } from 'react'; import { Editor } from 'slate-react'; import { GrafanaTheme2 } from '@grafana/data'; +import { promqlGrammar } from '@grafana/prometheus'; import { makeValue, SlatePrism, useStyles2 } from '@grafana/ui'; import LogqlSyntax from 'app/plugins/datasource/loki/syntax'; -import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; import { RulesSource } from 'app/types/unified-alerting'; import { DataSourceType, isCloudRulesSource } from '../utils/datasource'; @@ -26,7 +26,7 @@ export const HighlightedQuery: FC<{ language: 'promql' | 'logql'; expr: string } onlyIn: (node) => 'type' in node && node.type === 'code_block', getSyntax: () => language, }, - { ...prismLanguages, [language]: language === 'logql' ? LogqlSyntax : PromqlSyntax } + { ...prismLanguages, [language]: language === 'logql' ? LogqlSyntax : promqlGrammar } ), ], [language] diff --git a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx b/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx index 92583016959..8eec117d321 100644 --- a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx +++ b/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render, screen, waitFor } from '@testing-library/react'; import { setupServer } from 'msw/node'; import React from 'react'; diff --git a/public/app/features/alerting/unified/components/PluginBridge.mock.ts b/public/app/features/alerting/unified/components/PluginBridge.mock.ts index d71d70c313d..1fcae26b01c 100644 --- a/public/app/features/alerting/unified/components/PluginBridge.mock.ts +++ b/public/app/features/alerting/unified/components/PluginBridge.mock.ts @@ -1,5 +1,3 @@ -// bit of setup to mock HTTP request responses -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts index 54666de1aa5..56edd4777f1 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts index adbed73cb48..3c7b17d13e6 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/lib/node'; diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts index 6c4943e28a2..5f31f5c0f7d 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/lib/node'; diff --git a/public/app/features/alerting/unified/components/receivers/PayloadEditor.test.tsx b/public/app/features/alerting/unified/components/receivers/PayloadEditor.test.tsx index aaced7dfded..c4ad73cf9c0 100644 --- a/public/app/features/alerting/unified/components/receivers/PayloadEditor.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/PayloadEditor.test.tsx @@ -6,8 +6,6 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { PayloadEditor, RESET_TO_DEFAULT } from './PayloadEditor'; -import 'whatwg-fetch'; - const DEFAULT_PAYLOAD = `[ { "annotations": { diff --git a/public/app/features/alerting/unified/components/receivers/TemplatePreview.test.tsx b/public/app/features/alerting/unified/components/receivers/TemplatePreview.test.tsx index fbe9b77212f..fce7249c1f9 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatePreview.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatePreview.test.tsx @@ -10,7 +10,6 @@ import { setBackendSrv } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; import { configureStore } from 'app/store/configureStore'; -import 'whatwg-fetch'; import { TemplatePreviewResponse } from '../../api/templateApi'; import { mockPreviewTemplateResponse, diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.test.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.test.tsx index 0d8e4dcfa72..5f380de38fd 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.test.tsx @@ -17,8 +17,6 @@ import { RuleFormValues } from '../../types/rule-form'; import { Annotation } from '../../utils/constants'; import { getDefaultFormValues } from '../../utils/rule-form'; -import 'whatwg-fetch'; - import AnnotationsStep from './AnnotationsStep'; // To get anything displayed inside the Autosize component we need to mock it diff --git a/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx index 4c2f5892908..58caf640fe4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx @@ -4,10 +4,10 @@ import React, { useCallback, useMemo } from 'react'; import { useAsync } from 'react-use'; import { CoreApp, DataQuery, DataSourcePluginContextProvider, GrafanaTheme2, LoadingState } from '@grafana/data'; +import { PromQuery } from '@grafana/prometheus'; import { getDataSourceSrv } from '@grafana/runtime'; import { Alert, Button, useStyles2 } from '@grafana/ui'; import { LokiQuery } from 'app/plugins/datasource/loki/types'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { CloudAlertPreview } from './CloudAlertPreview'; import { usePreview } from './PreviewRule'; diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index 901671761a1..e449a25f8ea 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -19,6 +19,7 @@ import { } from 'app/features/alerting/unified/api/ruler'; import * as useContactPoints from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import * as dsByPermission from 'app/features/alerting/unified/hooks/useAlertManagerSources'; +import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import { MockDataSourceSrv, grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { fetchRulerRulesIfNotFetchedYet } from 'app/features/alerting/unified/state/actions'; @@ -102,8 +103,11 @@ const mocks = { }, }; +const server = setupMswServer(); + describe('Can create a new grafana managed alert unsing simplified routing', () => { beforeEach(() => { + mockApi(server).eval({ results: {} }); jest.clearAllMocks(); contextSrv.isEditor = true; contextSrv.hasEditPermissionInFolders = true; diff --git a/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts b/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts index 83584bb4155..de4dcdcf261 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts +++ b/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer, setupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx index d701a62f0ee..fd32c83574f 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx @@ -15,7 +15,7 @@ interface Props { rule?: CombinedRule; instances: Alert[]; pagination?: PaginationProps; - footerRow?: JSX.Element; + footerRow?: React.ReactNode; } interface AlertWithCommonLabels extends Alert { 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 59e89009881..e5220ac4295 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render, screen, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx index fcc1a0ac7fb..70b67368ba9 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx @@ -1,10 +1,9 @@ import { css, cx } from '@emotion/css'; import { countBy, sum } from 'lodash'; import React, { useMemo, useState } from 'react'; -import { useHistory } from 'react-router-dom'; import { GrafanaTheme2 } from '@grafana/data'; -import { Button, useStyles2 } from '@grafana/ui'; +import { LinkButton, useStyles2 } from '@grafana/ui'; import { MatcherFilter } from 'app/features/alerting/unified/components/alert-groups/MatcherFilter'; import { AlertInstanceStateFilter, @@ -34,24 +33,28 @@ interface ShowMoreStats { visibleItemsCount: number; } -function ShowMoreInstances(props: { onClick: () => void; stats: ShowMoreStats }) { +interface ShowMoreInstancesProps { + stats: ShowMoreStats; + onClick?: React.ComponentProps['onClick']; + href?: React.ComponentProps['href']; +} + +function ShowMoreInstances({ stats, onClick, href }: ShowMoreInstancesProps) { const styles = useStyles2(getStyles); - const { onClick, stats } = props; return (
Showing {stats.visibleItemsCount} out of {stats.totalItemsCount} instances
- +
); } -export function RuleDetailsMatchingInstances(props: Props): JSX.Element | null { - const history = useHistory(); +export function RuleDetailsMatchingInstances(props: Props) { const { rule, itemsDisplayLimit = Number.POSITIVE_INFINITY, pagination, enableFiltering = false } = props; const { promRule, namespace, instanceTotals } = rule; @@ -97,16 +100,19 @@ export function RuleDetailsMatchingInstances(props: Props): JSX.Element | null { visibleItemsCount: visibleInstances.length, }; + // createViewLink returns a link containing the app subpath prefix hence cannot be used + // in locationService.push as it will result in a double prefix const ruleViewPageLink = createViewLink(namespace.rulesSource, props.rule, location.pathname + location.search); const statsComponents = getComponentsFromStats(instanceTotals); const resetFilter = () => setAlertState(undefined); - const navigateToDetailView = () => history.push(ruleViewPageLink); - - const onShowMoreInstances = enableFiltering ? resetFilter : navigateToDetailView; const footerRow = hiddenInstancesCount ? ( - + ) : undefined; return ( diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx index ea69d56047a..9d610940269 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx @@ -5,12 +5,14 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { byLabelText, byRole } from 'testing-library-selector'; import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { mockSearchApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import * as analytics from '../../Analytics'; import { MockDataSourceSrv } from '../../mocks'; import RulesFilter from './RulesFilter'; +const server = setupMswServer(); jest.spyOn(analytics, 'logInfo'); jest.mock('./MultipleDataSourcePicker', () => { @@ -39,6 +41,7 @@ const ui = { beforeEach(() => { locationService.replace({ search: '' }); + mockSearchApi(server).search([]); }); describe('RulesFilter', () => { diff --git a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.test.tsx b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.test.tsx index 3925cab0c31..44cc923b06b 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.test.tsx +++ b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/hooks/useAbilities.test.tsx b/public/app/features/alerting/unified/hooks/useAbilities.test.tsx index b0f12275dd4..83b076bc2c6 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.test.tsx +++ b/public/app/features/alerting/unified/hooks/useAbilities.test.tsx @@ -4,10 +4,16 @@ import React, { PropsWithChildren } from 'react'; import { Router } from 'react-router-dom'; import { TestProvider } from 'test/helpers/TestProvider'; +import { mockFolderApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { + defaultAlertmanagerChoiceResponse, + mockAlertmanagerChoiceResponse, +} from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; +import { RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; -import { getCloudRule, getGrafanaRule, grantUserPermissions, mockDataSource } from '../mocks'; +import { getCloudRule, getGrafanaRule, grantUserPermissions, mockDataSource, mockFolder } from '../mocks'; import { AlertmanagerProvider } from '../state/AlertmanagerContext'; import { setupDataSources } from '../testSetup/datasources'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; @@ -137,9 +143,19 @@ describe('alertmanager abilities', () => { }); describe('AlertRule abilities', () => { + const server = setupMswServer(); it('should report that all actions are supported for a Grafana Managed alert rule', async () => { const rule = getGrafanaRule(); + // TODO: Remove server mocking within test once server is run before all tests + mockFolderApi(server).folder( + (rule.rulerRule as RulerGrafanaRuleDTO).grafana_alert.namespace_uid, + mockFolder({ + accessControl: { [AccessControlAction.AlertingRuleUpdate]: false }, + }) + ); + mockAlertmanagerChoiceResponse(server, defaultAlertmanagerChoiceResponse); + const abilities = renderHook(() => useAllAlertRuleAbilities(rule), { wrapper: TestProvider }); await waitFor(() => { diff --git a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx index 1e15071190b..d4d892c0ed2 100644 --- a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx +++ b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { renderHook, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { SetupServer, setupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts b/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts index 4a0897e45ce..2c18c077f90 100644 --- a/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts +++ b/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts @@ -27,31 +27,45 @@ export const useGroupedAlerts = (groups: AlertmanagerGroup[], groupBy: string[]) return groups; } } - const alerts = groups.flatMap(({ alerts }) => alerts); + + // api/v2/alerts/groups returns alerts grouped by labels AND receiver. + // It means that the same alert can be in multiple groups if it has multiple receivers. + // Hence, to get the list of unique alerts we need to get unique alerts by fingerprint. + const alerts = uniqBy( + groups.flatMap(({ alerts }) => alerts), + (alert) => alert.fingerprint + ); return alerts.reduce((groupings, alert) => { const alertContainsGroupings = groupBy.every((groupByLabel) => Object.keys(alert.labels).includes(groupByLabel)); if (alertContainsGroupings) { - const existingGrouping = groupings.find((group) => { - return groupBy.every((groupKey) => { - return group.labels[groupKey] === alert.labels[groupKey]; - }); - }); - if (!existingGrouping) { - const labels = groupBy.reduce((acc, key) => { + // We need to create a group for each receiver. This is how Alertmanager groups alerts. + // Alertmanager not only does grouping by labels but also by receiver. + const receiverAlertGroups = alert.receivers.map((receiver) => ({ + alerts: [alert], + labels: groupBy.reduce((acc, key) => { acc = { ...acc, [key]: alert.labels[key] }; return acc; - }, {}); - groupings.push({ - alerts: [alert], - labels, - receiver: { - name: 'NONE', - }, + }, {}), + receiver, + })); + + // Merge the same groupings - groupings are the same if they have the same labels and receiver + receiverAlertGroups.forEach((receiverAlertGroup) => { + const existingGroup = groupings.find((grouping) => { + return ( + Object.keys(receiverAlertGroup.labels).every( + (key) => grouping.labels[key] === receiverAlertGroup.labels[key] + ) && grouping.receiver.name === receiverAlertGroup.receiver.name + ); }); - } else { - existingGrouping.alerts.push(alert); - } + + if (existingGroup) { + existingGroup.alerts.push(alert); + } else { + groupings.push(receiverAlertGroup); + } + }); } else { const noGroupingGroup = groupings.find((group) => Object.keys(group.labels).length === 0); if (!noGroupingGroup) { diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index 7aabf1c3184..fe4403d3f97 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { uniqueId } from 'lodash'; import { http, HttpResponse } from 'msw'; import { setupServer, SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/mocks/alertRuleApi.ts b/public/app/features/alerting/unified/mocks/alertRuleApi.ts index d748fb8973a..6f8d7858bd6 100644 --- a/public/app/features/alerting/unified/mocks/alertRuleApi.ts +++ b/public/app/features/alerting/unified/mocks/alertRuleApi.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts index 1b7e98806dc..1db4862f414 100644 --- a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/mocks/plugins.ts b/public/app/features/alerting/unified/mocks/plugins.ts index df657222de9..48c98edb33b 100644 --- a/public/app/features/alerting/unified/mocks/plugins.ts +++ b/public/app/features/alerting/unified/mocks/plugins.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/lib/node'; diff --git a/public/app/features/alerting/unified/mocks/rulerApi.ts b/public/app/features/alerting/unified/mocks/rulerApi.ts index d689d02382f..ec9bccf9ab9 100644 --- a/public/app/features/alerting/unified/mocks/rulerApi.ts +++ b/public/app/features/alerting/unified/mocks/rulerApi.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/mocks/templatesApi.ts b/public/app/features/alerting/unified/mocks/templatesApi.ts index 71461f216e9..49a1fd652b5 100644 --- a/public/app/features/alerting/unified/mocks/templatesApi.ts +++ b/public/app/features/alerting/unified/mocks/templatesApi.ts @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; diff --git a/public/app/features/alerting/unified/utils/query.ts b/public/app/features/alerting/unified/utils/query.ts index 950c7340956..49ec1464a93 100644 --- a/public/app/features/alerting/unified/utils/query.ts +++ b/public/app/features/alerting/unified/utils/query.ts @@ -1,9 +1,9 @@ import { produce } from 'immer'; import { DataSourceInstanceSettings } from '@grafana/data'; +import { PromQuery } from '@grafana/prometheus'; import { DataQuery } from '@grafana/schema'; import { LokiQuery } from 'app/plugins/datasource/loki/types'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { CombinedRule } from 'app/types/unified-alerting'; import { AlertQuery } from 'app/types/unified-alerting-dto'; diff --git a/public/app/features/alerting/unified/utils/rule-form.test.ts b/public/app/features/alerting/unified/utils/rule-form.test.ts index 735a8f7087b..164720b2f4a 100644 --- a/public/app/features/alerting/unified/utils/rule-form.test.ts +++ b/public/app/features/alerting/unified/utils/rule-form.test.ts @@ -1,5 +1,5 @@ +import { PromQuery } from '@grafana/prometheus'; import { config } from '@grafana/runtime'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto'; import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form'; diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index 1b664dfbfb4..8efbb441727 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -11,6 +11,7 @@ import { ScopedVars, TimeRange, } from '@grafana/data'; +import { PromQuery } from '@grafana/prometheus'; import { config, getDataSourceSrv } from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { sceneGraph, VizPanel } from '@grafana/scenes'; @@ -24,7 +25,6 @@ import { } from 'app/features/dashboard-scene/utils/utils'; import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; import { LokiQuery } from 'app/plugins/datasource/loki/types'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; import { AlertDataQuery, diff --git a/public/app/features/apiserver/server.ts b/public/app/features/apiserver/server.ts index 6b75ef795c8..77fba2a006e 100644 --- a/public/app/features/apiserver/server.ts +++ b/public/app/features/apiserver/server.ts @@ -2,6 +2,7 @@ import { config, getBackendSrv } from '@grafana/runtime'; import { ListOptions, + ListOptionsFieldSelector, ListOptionsLabelSelector, MetaStatus, Resource, @@ -33,9 +34,10 @@ export class ScopedResourceServer implements ResourceSer return getBackendSrv().get>(`${this.url}/${name}`); } - public async list(opts?: ListOptions | undefined): Promise> { + public async list(opts?: ListOptions | undefined): Promise> { const finalOpts = opts || {}; - finalOpts.labelSelector = this.parseLabelSelector(finalOpts?.labelSelector); + finalOpts.labelSelector = this.parseListOptionsSelector(finalOpts?.labelSelector); + finalOpts.fieldSelector = this.parseListOptionsSelector(finalOpts?.fieldSelector); return getBackendSrv().get>(this.url, opts); } @@ -48,12 +50,14 @@ export class ScopedResourceServer implements ResourceSer return getBackendSrv().delete(`${this.url}/${name}`); } - private parseLabelSelector(labelSelector: ListOptionsLabelSelector | undefined): string | undefined { - if (!Array.isArray(labelSelector)) { - return labelSelector; + private parseListOptionsSelector( + selector: ListOptionsLabelSelector | ListOptionsFieldSelector | undefined + ): string | undefined { + if (!Array.isArray(selector)) { + return selector; } - return labelSelector + return selector .map((label) => { const key = String(label.key); const operator = label.operator; diff --git a/public/app/features/apiserver/types.ts b/public/app/features/apiserver/types.ts index 41a8cbba08a..56fbbf5ac7b 100644 --- a/public/app/features/apiserver/types.ts +++ b/public/app/features/apiserver/types.ts @@ -78,32 +78,44 @@ export interface ResourceList extends TypeMeta { items: Array>; } -export type ListOptionsLabelSelector = +export type ListOptionsLabelSelector = | string | Array< | { - key: keyof T; + key: string; operator: '=' | '!='; value: string; } | { - key: keyof T; + key: string; operator: 'in' | 'notin'; value: string[]; } | { - key: keyof T; + key: string; operator: '' | '!'; } >; -export interface ListOptions { +export type ListOptionsFieldSelector = + | string + | Array<{ + key: string; + operator: '=' | '!='; + value: string; + }>; + +export interface ListOptions { // continue the list at a given batch continue?: string; // Query by labels // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - labelSelector?: ListOptionsLabelSelector; + labelSelector?: ListOptionsLabelSelector; + + // Query by fields + // https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/ + fieldSelector?: ListOptionsFieldSelector; // Limit the response count limit?: number; @@ -129,7 +141,7 @@ export interface MetaStatus { export interface ResourceServer { create(obj: ResourceForCreate): Promise; get(name: string): Promise>; - list(opts?: ListOptions): Promise>; + list(opts?: ListOptions): Promise>; update(obj: ResourceForCreate): Promise>; delete(name: string): Promise; } diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx index ab005ac3d48..6b269840ab7 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HttpResponse, http } from 'msw'; diff --git a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx index 9c101f9bb54..1ca5b3d960e 100644 --- a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render as rtlRender, screen } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { SetupServer, setupServer } from 'msw/node'; diff --git a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx index 1c6632d580b..9afb6ff3849 100644 --- a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx @@ -1,4 +1,3 @@ -import 'whatwg-fetch'; import { render as rtlRender, screen } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { SetupServer, setupServer } from 'msw/node'; diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index a3653d5ab92..371cc46ec43 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -61,6 +61,8 @@ export interface CanvasConnection { vertices?: ConnectionCoordinates[]; radius?: ScaleDimensionConfig; direction?: ConnectionDirection; + sourceOriginal?: ConnectionCoordinates; + targetOriginal?: ConnectionCoordinates; // See https://github.com/anseki/leader-line#options for more examples of more properties } diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx index f70dc029256..28f9def3165 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx @@ -72,6 +72,11 @@ export function DashboardScenePage({ match, route, queryParams, history }: Props ); } + // Do not render anything when transitioning from one dashboard to another + if (dashboard.state.uid && dashboard.state.uid !== match.params.uid) { + return null; + } + return ( <> diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 6326a7f8981..2d67ff875e9 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -1,6 +1,6 @@ import { advanceBy } from 'jest-date-mock'; -import { locationService } from '@grafana/runtime'; +import { BackendSrv, locationService, setBackendSrv } from '@grafana/runtime'; import { getUrlSyncManager } from '@grafana/scenes'; import store from 'app/core/store'; import { DASHBOARD_FROM_LS_KEY } from 'app/features/dashboard/state/initDashboard'; @@ -41,6 +41,18 @@ describe('DashboardScenePageStateManager', () => { expect(loader.state.loadError).toBe('Error: Dashboard not found'); }); + it('should handle home dashboard redirect', async () => { + setBackendSrv({ + get: () => Promise.resolve({ redirectUri: '/d/asd' }), + } as unknown as BackendSrv); + + const loader = new DashboardScenePageStateManager({}); + await loader.loadDashboard({ uid: '', route: DashboardRoutes.Home }); + + expect(loader.state.dashboard).toBeUndefined(); + expect(loader.state.loadError).toBeUndefined(); + }); + it('shoud fetch dashboard from local storage and remove it after if it exists', async () => { const loader = new DashboardScenePageStateManager({}); const localStorageDashboard = { uid: 'fake-dash' }; diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 6fa5474fce2..e878af98383 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -90,11 +90,8 @@ export class DashboardScenePageStateManager extends StateManagerBase { + private async loadScene(options: LoadDashboardOptions): Promise { const comingFromExplore = Boolean( localStorageStore.getObject(DASHBOARD_FROM_LS_KEY) && options.keepDashboardFromExploreInLocalStorage === false ); + this.setState({ isLoading: true }); + const rsp = await this.fetchDashboard(options); const fromCache = this.cache[options.uid]; @@ -198,8 +201,6 @@ export class DashboardScenePageStateManager extends StateManagerBase { AccessControlAction.AlertingRuleExternalRead, AccessControlAction.AlertingRuleExternalWrite, ]); + + jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue(mockFolder()); + mocks.getAllDataSources.mockReturnValue(Object.values(dataSources)); const dsService = new MockDataSourceSrv(dataSources); dsService.datasources[dataSources.prometheus.uid] = new PrometheusDatasource( diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx index 29263390f67..edc3677e1b1 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx @@ -1,5 +1,5 @@ import debounce from 'debounce-promise'; -import React from 'react'; +import React, { ChangeEvent } from 'react'; import { UseFormSetValue, useForm } from 'react-hook-form'; import { Dashboard } from '@grafana/schema'; @@ -28,7 +28,7 @@ export interface Props { export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { const { changedSaveModel } = changeInfo; - const { register, handleSubmit, setValue, formState, getValues, watch, trigger } = useForm({ + const { register, handleSubmit, setValue, formState, getValues, watch } = useForm({ mode: 'onBlur', defaultValues: { title: changeInfo.isNew ? changedSaveModel.title! : `${changedSaveModel.title} Copy`, @@ -97,10 +97,9 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { { - trigger('title'); + onChange={debounce(async (e: ChangeEvent) => { + setValue('title', e.target.value, { shouldValidate: true }); }, 400)} - autoFocus /> +