diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 43b5886447c..1be44601a3c 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -86,6 +86,10 @@ module.exports = [ importNames: ['Layout', 'HorizontalGroup', 'VerticalGroup'], message: 'Use Stack component instead.', }, + { + group: ['@grafana/ui/src/*', '@grafana/runtime/src/*', '@grafana/data/src/*'], + message: 'Import from the public export instead.', + }, ], }, ], diff --git a/.betterer.results b/.betterer.results index ee0179c4229..039459ba420 100644 --- a/.betterer.results +++ b/.betterer.results @@ -978,6 +978,11 @@ exports[`better eslint`] = { "packages/grafana-ui/src/utils/useAsyncDependency.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/app.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/components/PanelDataErrorView\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/components/PanelRenderer\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], "public/app/core/TableModel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -1022,6 +1027,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], + "public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/components/Breadcrumbs/Breadcrumbs.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -1050,12 +1058,25 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/core/components/GraphNG/GraphNG.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, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/Plot\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotConfigBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "Do not use any type assertions.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"], + [0, 0, 0, "Do not use any type assertions.", "8"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "10"] + ], + "public/app/core/components/GraphNG/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinDataFrames\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullToUndefThreshold\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], + "public/app/core/components/Indent/Indent.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Layout/utils/responsiveness\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/core/components/Layers/LayerDragDropList.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -1076,12 +1097,26 @@ exports[`better eslint`] = { "public/app/core/components/NestedFolderPicker/FolderRepo.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/core/components/NestedFolderPicker/NestedFolderList.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/core/components/NestedFolderPicker/Trigger.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/components/OptionsUI/color.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/ColorPicker/ColorSwatch\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/core/components/OptionsUI/fieldColor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/core/components/OptionsUI/registry.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/overrides/processors\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/core/components/OptionsUI/slider.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Slider/styles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/components/OptionsUI/units.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -1092,6 +1127,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], + "public/app/core/components/PasswordField/PasswordField.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/components/PluginHelp/PluginHelp.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -1099,16 +1137,23 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/core/components/RolePicker/RoleMenuGroupOption.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Select/getSelectStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/core/components/RolePicker/RoleMenuOption.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Select/getSelectStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] + ], + "public/app/core/components/RolePicker/RolePickerInput.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/core/components/RolePicker/RolePickerMenu.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Select/getSelectStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/core/components/RolePicker/RolePickerSubMenu.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Select/getSelectStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/core/components/RolePickerDrawer/RolePickerBadges.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -1158,15 +1203,39 @@ exports[`better eslint`] = { "public/app/core/components/TagFilter/TagOption.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/core/components/TimePicker/TimePickerWithHistory.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/core/components/TimeSeries/TimeSeries.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/PlotLegend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotConfigBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/themes/ThemeContext\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], "public/app/core/components/TimeSeries/utils.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotConfigBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/gradientFills\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/internal\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + ], + "public/app/core/components/TimelineChart/timeline.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/core/components/TimelineChart/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinDataFrames\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullToValue\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "3"] ], "public/app/core/config.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`Settings\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`config\`)", "1"] ], + "public/app/core/context/ModalsContextProvider.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Modal/ModalsContext\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/core.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`JsonExplorer\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`TimeSeries\`)", "1"], @@ -1205,6 +1274,12 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], + "public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/config\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/core/services/theme.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/themes/registry\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/specs/backend_srv.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1244,6 +1319,9 @@ exports[`better eslint`] = { "public/app/core/utils/deferred.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/core/utils/explore.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/url\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/core/utils/fetch.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -1264,38 +1342,60 @@ exports[`better eslint`] = { [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"] + [0, 0, 0, "\'@grafana/data/src/utils/url\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not re-export imported variable (\`RichHistorySearchFilters\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`RichHistorySettings\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`SortOrder\`)", "3"] ], "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"] ], "public/app/features/actions/ActionEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "\'@grafana/ui/src/components/Forms/Field\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Forms/InlineField\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/Forms/InlineFieldRow\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/JSONFormatter/JSONFormatter\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "10"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "11"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "12"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "13"] + ], + "public/app/features/actions/ActionEditorModalContent.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Modal/Modal\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] ], "public/app/features/actions/ActionsInlineEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Modal/Modal\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/actions/ActionsListItem.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] - ], - "public/app/features/actions/ParamsEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/Icon/Icon\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/IconButton/IconButton\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] ], + "public/app/features/actions/ParamsEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/IconButton/IconButton\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/Layout/Stack/Stack\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/Select/Select\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"] + ], "public/app/features/admin/AdminEditOrgPage.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] @@ -1307,8 +1407,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/admin/AdminOrgsTable.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] + ], + "public/app/features/admin/AdminSettingsTable.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/admin/OrgRolePicker.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -1478,8 +1582,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], @@ -1492,7 +1596,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], [0, 0, 0, "No untranslated strings. Wrap text with ", "13"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "14"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "14"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "15"] ], "public/app/features/alerting/unified/NotificationPoliciesPage.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -1531,6 +1636,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], + "public/app/features/alerting/unified/Templates.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/AlertLabel.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -1942,6 +2050,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], [0, 0, 0, "No untranslated strings. Wrap text with ", "12"] ], + "public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/receivers/TemplateForm.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -2060,6 +2171,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], + "public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -2140,6 +2254,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], + "public/app/features/alerting/unified/components/rule-editor/GrafanaAlertStatePicker.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Select/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -2193,10 +2310,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/alerting/unified/components/rule-editor/QueryOptions.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -2262,17 +2380,18 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Select/MultiValue\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "10"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "10"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "11"] ], "public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -2328,13 +2447,14 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] ], "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -2350,6 +2470,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] ], + "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/services/__mocks__/dataSourceSrv\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] @@ -2365,6 +2488,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/features/alerting/unified/components/rule-editor/util.test.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -2385,11 +2511,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -2411,6 +2538,12 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rule-viewer/tabs/Routing.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/features/alerting/unified/components/rules/ActionButton.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/alerting/unified/components/rules/ActionIcon.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -2490,6 +2623,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], + "public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -2597,6 +2733,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "25"], [0, 0, 0, "No untranslated strings. Wrap text with ", "26"] ], + "public/app/features/alerting/unified/components/rules/central-state-history/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/fieldComparers\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], @@ -2621,6 +2760,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], + "public/app/features/alerting/unified/components/rules/state-history/useRuleHistoryRecords.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/fieldComparers\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -2757,6 +2899,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/home/PluginIntegrations.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/features/alerting/unified/hooks/useAlertQueriesStatus.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/hooks/useAlertmanagerConfig.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -2793,19 +2938,12 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"] ], + "public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Icon/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/rule-list/FilterView.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], - "public/app/features/alerting/unified/rule-list/GroupedView.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"] - ], "public/app/features/alerting/unified/rule-list/RuleList.v1.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -2827,6 +2965,12 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/alerting/unified/state/actions.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/logging\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/types/receiver-form.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -2836,6 +2980,9 @@ 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/features/alerting/unified/utils/misc.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/config\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/utils/receiver-form.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -2852,12 +2999,21 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], + "public/app/features/alerting/unified/utils/routeTree.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/arrayUtils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/alerting/unified/utils/rule-form.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/alerting/unified/utils/rules.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, "Unexpected any. Specify a different type.", "3"] ], + "public/app/features/alerting/unified/utils/time.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/annotations/components/AnnotationResultMapper.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], @@ -2929,10 +3085,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] ], "public/app/features/auth-config/AuthProvidersListPage.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "\'@grafana/data/src/types/config\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/auth-config/ProviderConfigForm.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -2981,13 +3138,21 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/browse-dashboards/components/NameCell.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Icon/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "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, "\'@grafana/data/src/panel/PanelPlugin\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/canvas/elements/metricValue.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], "public/app/features/canvas/elements/notFound.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -3284,6 +3449,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], + "public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/text/sanitize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard-scene/scene/NavToolbarActions.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -3598,14 +3766,25 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/ConfigEmailSharing.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/EmailListConfiguration.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareConfiguration.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Switch/Switch\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/ShareSnapshot.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/UpsertSnapshot.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] + ], + "public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawerConfirmAction.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/ConfirmModal/ConfirmContent\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] @@ -3807,9 +3986,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.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, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], @@ -3830,26 +4010,30 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx:5381": [ - [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' 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"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "\'@grafana/ui/src/components/Layout/Layout\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Forms/Field\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"] ], "public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "\'@grafana/data/src/panel/PanelPlugin\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/features/dashboard/components/PanelEditor/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -3954,12 +4138,14 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"] + [0, 0, 0, "\'@grafana/ui/src/components/Layout/Layout\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "2"] ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx:5381": [ - [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "1"] + [0, 0, 0, "\'@grafana/ui/src/components/Layout/Layout\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "2"] ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -3971,13 +4157,24 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/query\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/PanelChrome/LoadingIndicator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/text/sanitize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "\'@grafana/data/src/text/sanitize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], "public/app/features/dashboard/components/SubMenu/SubMenu.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -4067,6 +4264,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], + "public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotice.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard/dashgrid/PanelLinks.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -4078,6 +4278,9 @@ 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/features/dashboard/services/TimeSrv.ts:5381": [ + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dashboard/state/DashboardMigrator.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -4093,13 +4296,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "11"] ], "public/app/features/dashboard/state/DashboardMigrator.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, "\'@grafana/data/src/transformations/transformers/labelsToFields\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/merge\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/Table/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], [0, 0, 0, "Do not use any type assertions.", "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, "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, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"], @@ -4120,7 +4323,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "24"], [0, 0, 0, "Unexpected any. Specify a different type.", "25"], [0, 0, 0, "Unexpected any. Specify a different type.", "26"], - [0, 0, 0, "Unexpected any. Specify a different type.", "27"] + [0, 0, 0, "Unexpected any. Specify a different type.", "27"], + [0, 0, 0, "Unexpected any. Specify a different type.", "28"], + [0, 0, 0, "Unexpected any. Specify a different type.", "29"], + [0, 0, 0, "Unexpected any. Specify a different type.", "30"] ], "public/app/features/dashboard/state/DashboardModel.repeat.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -4233,13 +4439,14 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/datasources/components/CloudInfoBox.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/types/config\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], "public/app/features/datasources/components/DashboardsTable.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -4317,6 +4524,9 @@ 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/dimensions/editors/ColorDimensionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/dimensions/editors/FileUploader.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] @@ -4326,15 +4536,17 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/dimensions/editors/ResourceDimensionEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/dimensions/editors/ResourcePicker.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "\'@grafana/ui/src/utils/closePopover\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/dimensions/editors/ResourcePickerPopover.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -4343,21 +4555,24 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/dimensions/editors/ScalarDimensionEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] - ], - "public/app/features/dimensions/editors/ScaleDimensionEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], - "public/app/features/dimensions/editors/TextDimensionEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], + "public/app/features/dimensions/editors/ScaleDimensionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + ], + "public/app/features/dimensions/editors/TextDimensionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] ], "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -4422,7 +4637,8 @@ exports[`better eslint`] = { [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, "\'@grafana/data/src/field/scale\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/features/dimensions/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -4431,7 +4647,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/explore/CorrelationEditorModeBar.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -4470,7 +4687,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/explore/Explore.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Table/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], @@ -4478,14 +4695,16 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"] ], "public/app/features/explore/ExplorePage.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/features/explore/ExploreRunQueryButton.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], "public/app/features/explore/ExploreToolbar.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -4506,8 +4725,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], + "public/app/features/explore/Logs/Logs.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/organize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/Logs/Logs.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizLegend/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], @@ -4516,8 +4738,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "10"], + [0, 0, 0, "Unexpected any. Specify a different type.", "11"] ], "public/app/features/explore/Logs/LogsColumnSearch.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -4529,6 +4752,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], + "public/app/features/explore/Logs/LogsMetaRow.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/organize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/Logs/LogsMetaRow.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -4547,6 +4773,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], + "public/app/features/explore/Logs/LogsTable.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/organize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/explore/Logs/LogsTable.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Table/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/Logs/LogsTableAvailableFields.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -4563,6 +4795,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], + "public/app/features/explore/Logs/LogsTableWrap.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/organize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/Logs/LogsTableWrap.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -4610,6 +4845,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], + "public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -4642,24 +4880,26 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "10"] ], "public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] ], "public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], @@ -4863,9 +5103,15 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], + "public/app/features/explore/state/main.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/url\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/state/time.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/explore/state/time.ts:5381": [ + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/explore/state/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -4875,8 +5121,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], "public/app/features/expressions/ExpressionDatasource.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, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/expressions/ExpressionQueryEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] @@ -4933,7 +5180,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] ], "public/app/features/expressions/guards.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/features/geo/editor/GazetteerPathEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5034,7 +5282,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5062,11 +5311,19 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/features/live/centrifuge/LiveDataStream.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/data/src/dataframe/StreamingDataFrame\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/services/live\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/runtime/src/utils/toDataQueryError\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"] ], "public/app/features/live/centrifuge/channel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/live/centrifuge/service.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/services/backendSrv\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/services/live\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/runtime/src/utils/queryResponse\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], "public/app/features/live/centrifuge/serviceWorkerProxy.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -5076,8 +5333,15 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], + "public/app/features/live/live.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/logs/components/InfiniteScroll.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/logs/components/InfiniteScroll.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/logs/components/LogDetails.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -5127,6 +5391,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], + "public/app/features/logs/logsModel.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/valueFormats/symbolFormatters\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/logs/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -5134,7 +5401,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], @@ -5142,9 +5409,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "10"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "10"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "11"] ], "public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -5161,6 +5429,9 @@ exports[`better eslint`] = { "public/app/features/manage-dashboards/components/SnapshotListTable.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], + "public/app/features/manage-dashboards/components/SnapshotListTableRow.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/manage-dashboards/state/actions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -5183,9 +5454,13 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] ], "public/app/features/migrate-to-cloud/onprem/NameCell.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Icon/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + ], + "public/app/features/migrate-to-cloud/shared/AlertWithTraceID.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Alert/Alert\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/notifications/StoredNotifications.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -5244,9 +5519,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"] ], "public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5261,10 +5537,16 @@ exports[`better eslint`] = { "public/app/features/panel/panellinks/link_srv.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/playlist/PlaylistCard.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/playlist/PlaylistForm.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] ], + "public/app/features/playlist/PlaylistPageList.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/playlist/PlaylistTableRows.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], @@ -5369,10 +5651,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/plugins/admin/components/PluginDetailsDeprecatedWarning.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -5402,14 +5685,18 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], + "public/app/features/plugins/admin/components/PluginDetailsPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/plugins/admin/components/PluginListItem.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5482,25 +5769,39 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/plugins/datasource_srv.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "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.", "4"], + [0, 0, 0, "Do not use any type assertions.", "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/features/plugins/extensions/logs/LogViewFilters.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], + "public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/pluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/plugins/extensions/usePluginComponents.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/runtime/src/services/pluginExtensions/getPluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/features/plugins/extensions/usePluginLinks.tsx:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/services/pluginExtensions/getPluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/plugins/extensions/validators.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/pluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/plugins/loader/sharedDependencies.ts: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/features/plugins/pluginPreloader.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/pluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/plugins/sandbox/distortion_map.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -5510,9 +5811,10 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/plugins/tests/datasource_srv.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "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.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/features/plugins/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -5622,7 +5924,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] ], "public/app/features/provisioning/RepositorySelect.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Combobox/Combobox\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/provisioning/RepositoryStatusPage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -5667,6 +5970,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/query-library/api/query.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/services/backendSrv\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/query/components/QueryEditorRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -5766,14 +6072,19 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/features/query/state/PanelQueryRunner.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, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] + ], + "public/app/features/query/state/runRequest.test.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/query/state/runRequest.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/features/query/state/updateQueries.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "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"], @@ -5781,7 +6092,14 @@ exports[`better eslint`] = { [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"] + [0, 0, 0, "Unexpected any. Specify a different type.", "8"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"] + ], + "public/app/features/query/state/updateQueries.ts:5381": [ + [0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/sandbox/BenchmarksPage.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/ThemeDemos/EmotionPerfTest\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/sandbox/TestStuffPage.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5797,8 +6115,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/search/page/components/SearchResultsTable.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "\'@grafana/ui/src/components/Table/TableCell\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Table/styles\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/search/page/components/columns.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -5827,6 +6147,9 @@ exports[`better eslint`] = { "public/app/features/search/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/serviceaccounts/ServiceAccountPage.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -5894,13 +6217,14 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], "public/app/features/serviceaccounts/components/ServiceAccountsListItem.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], "public/app/features/serviceaccounts/state/reducers.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -6029,10 +6353,15 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/trails/DataTrailSettings.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/trails/DataTrailsHistory.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/trails/DataTrailsHome.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/trails/MetricScene.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6065,16 +6394,21 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByValue\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + ], + "public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.test.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByValue\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByValue\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6113,11 +6447,16 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/transformers/calculateHeatmap/editor/helper.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + [0, 0, 0, "\'@grafana/ui/src/options/builder\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + ], + "public/app/features/transformers/calculateHeatmap/heatmap.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/dataframe/processDataFrame\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/features/transformers/calculateHeatmap/heatmap.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, "\'@grafana/data/src/transformations/transformers/joinDataFrames\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6125,60 +6464,68 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], "public/app/features/transformers/editors/CalculateFieldTransformerEditor/BinaryOperationOptionsEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + ], + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] + ], + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/IndexOptionsEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], - "public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] - ], - "public/app/features/transformers/editors/CalculateFieldTransformerEditor/IndexOptionsEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] - ], "public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.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, "Do not use any type assertions.", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] ], "public/app/features/transformers/editors/CalculateFieldTransformerEditor/UnaryOperationEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/calculateField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"] ], "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/ConcatenateTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] - ], - "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/concat\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + ], + "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/convertFieldType\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldTypeMatcherEditor\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], @@ -6191,13 +6538,17 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "13"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "14"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "15"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "16"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "16"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "17"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "18"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "19"] ], "public/app/features/transformers/editors/EnumMappingEditor.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"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/convertFieldType\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/transformers/editors/EnumMappingRow.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], @@ -6205,46 +6556,55 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] ], "public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByName\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/runtime/src/services\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"] ], "public/app/features/transformers/editors/FilterByRefIdTransformerEditor.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, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByRefId\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/transformers/editors/FormatStringTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] - ], - "public/app/features/transformers/editors/FormatTimeTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] - ], - "public/app/features/transformers/editors/GroupByTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] - ], - "public/app/features/transformers/editors/GroupToNestedTableTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/formatString\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] ], + "public/app/features/transformers/editors/FormatTimeTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/formatTime\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] + ], + "public/app/features/transformers/editors/GroupByTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/groupBy\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] + ], + "public/app/features/transformers/editors/GroupToNestedTableTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/groupBy\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/groupToNestedTable\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] + ], "public/app/features/transformers/editors/GroupingToMatrixTransformerEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], @@ -6252,70 +6612,85 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/transformers/editors/HistogramTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] ], "public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] - ], - "public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinByField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] ], - "public/app/features/transformers/editors/LimitTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] - ], - "public/app/features/transformers/editors/MergeTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], - "public/app/features/transformers/editors/ReduceTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + "public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/labelsToFields\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] ], + "public/app/features/transformers/editors/LimitTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/limit\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + ], + "public/app/features/transformers/editors/MergeTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/merge\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/order\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/organize\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + ], + "public/app/features/transformers/editors/ReduceTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/reduce\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] + ], "public/app/features/transformers/editors/RenameByRegexTransformer.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] - ], - "public/app/features/transformers/editors/SortByTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] - ], - "public/app/features/transformers/editors/TransposeTransformerEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/renameByRegex\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] ], - "public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + "public/app/features/transformers/editors/SeriesToRowsTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/seriesToRows\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/features/transformers/editors/SortByTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/sortBy\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"] + ], + "public/app/features/transformers/editors/TransposeTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/transpose\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + ], + "public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], @@ -6324,8 +6699,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "10"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "12"], + [0, 0, 0, "Unexpected any. Specify a different type.", "13"], + [0, 0, 0, "Unexpected any. Specify a different type.", "14"] ], "public/app/features/transformers/extractFields/components/JSONPathEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6337,6 +6713,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], + "public/app/features/transformers/extractFields/extractFields.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/dataframe/processDataFrame\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/sortBy\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/data/src/utils/tests/mockTransformationsRegistry\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], "public/app/features/transformers/extractFields/extractFields.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -6375,22 +6756,32 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "9"] ], "public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"] + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + ], + "public/app/features/transformers/lookupGazetteer/fieldLookup.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/dataframe/processDataFrame\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/ids\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] ], "public/app/features/transformers/lookupGazetteer/fieldLookup.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/transformers/partitionByValues/PartitionByValuesEditor.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, "No untranslated strings in text props. Wrap text with or use t()", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] + ], + "public/app/features/transformers/partitionByValues/partitionByValues.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/filterByName\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/noop\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] ], "public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6418,26 +6809,35 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/transformers/regression/regressionEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] ], "public/app/features/transformers/spatial/optionsHelper.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, "\'@grafana/data/src/panel/PanelPlugin\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], [0, 0, 0, "Do not use any type assertions.", "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, "Do not use any type assertions.", "3"], + [0, 0, 0, "Do not use any type assertions.", "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"] + ], + "public/app/features/transformers/spatial/spatialTransformer.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/dataframe/processDataFrame\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/ids\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] ], "public/app/features/transformers/suggestionsInput/SuggestionsInput.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, "\'@grafana/ui/src/components/DataLinks/DataLinkSuggestions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "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.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"] ], "public/app/features/transformers/timeSeriesTable/TimeSeriesTableTransformEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6446,12 +6846,13 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/users/TokenRevokedModal.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/Modal/getModalStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] ], "public/app/features/users/UsersActionBar.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6585,6 +6986,9 @@ exports[`better eslint`] = { "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, "\'@grafana/ui/src/components/PanelChrome/LoadingIndicator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/features/variables/pickers/shared/VariableOptions.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], @@ -6710,6 +7114,9 @@ exports[`better eslint`] = { "public/app/features/visualization/data-hover/DataHoverView.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], + "public/app/features/visualization/data-hover/ExemplarHoverView.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipRow\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/datasource/alertmanager/DataSource.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -6717,6 +7124,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/datasource/azuremonitor/__mocks__/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/data\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "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"] @@ -6757,6 +7167,9 @@ exports[`better eslint`] = { "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/components/shared/Field.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Forms/InlineField\' import is restricted from being used by a pattern. Import from the public export 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"], @@ -6861,6 +7274,12 @@ exports[`better eslint`] = { [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, "\'@grafana/ui/src/slate-plugins/slate-prism\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/PPLQueryEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "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"] ], @@ -6870,6 +7289,9 @@ exports[`better eslint`] = { "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/LegacyLogGroupSelector.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Select/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./MetricStatEditor\`)", "0"] ], @@ -6887,6 +7309,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], + "public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/datetime/moment_wrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/datasource/cloudwatch/types.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -6909,9 +7334,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/plugins/datasource/elasticsearch/ElasticResponse.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/convertFieldType\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Do not use any type assertions.", "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"], @@ -6940,7 +7365,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "28"], [0, 0, 0, "Unexpected any. Specify a different type.", "29"], [0, 0, 0, "Unexpected any. Specify a different type.", "30"], - [0, 0, 0, "Unexpected any. Specify a different type.", "31"] + [0, 0, 0, "Unexpected any. Specify a different type.", "31"], + [0, 0, 0, "Unexpected any. Specify a different type.", "32"] ], "public/app/plugins/datasource/elasticsearch/LanguageProvider.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -7047,6 +7473,14 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], + "public/app/plugins/datasource/grafana/components/TimePickerInput.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Forms/commonStyles\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOffset\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneTitle\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], "public/app/plugins/datasource/grafana/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -7219,6 +7653,15 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`Datasource\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`MixedDatasource\`)", "1"] ], + "public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataSourceSettings/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/datasource/mssql/types.ts:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataSourceSettings/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/datasource/opentsdb/datasource.d.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -7245,6 +7688,12 @@ exports[`better eslint`] = { "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/AzureAuthSettings.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataSourceSettings/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/datasource/prometheus/configuration/DataSourceHttpSettingsOverhaulPackage.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataSourceSettings/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "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"] ], @@ -7310,20 +7759,50 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] ], "public/app/plugins/panel/annolist/AnnoListPanel.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/List/AbstractList\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/plugins/panel/barchart/BarChartLegend.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/barchart/BarChartPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/panel/barchart/TickSpacingEditor.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/panel/barchart/bars.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"] + ], + "public/app/plugins/panel/barchart/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/options/builder/tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/panel/barchart/quadtree.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/barchart/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/fieldState\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], + "public/app/plugins/panel/bargauge/BarGaugeLegend.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/bargauge/BarGaugePanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataLinks/DataLinksContextMenu\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/candlestick/CandlestickPanel.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"] + ], + "public/app/plugins/panel/candlestick/fields.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinDataFrames\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/panel/candlestick/types.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`CandleStyle\`)", "0"], @@ -7335,6 +7814,34 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`VizDisplayMode\`)", "6"], [0, 0, 0, "Do not re-export imported variable (\`defaultCandlestickColors\`)", "7"] ], + "public/app/plugins/panel/canvas/components/CanvasTooltip.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/types/action\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/CloseButton\' import is restricted from being used by a pattern. Import from the public export instead.", "5"] + ], + "public/app/plugins/panel/canvas/editor/connectionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/canvas/editor/element/elementEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/panel/PanelPlugin\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/canvas/editor/layer/layerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/canvas/editor/options.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/panel/PanelPlugin\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/datagrid/components/DatagridContextMenu.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/convertFieldType\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/Menu/MenuDivider\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], "public/app/plugins/panel/debug/CursorView.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -7346,10 +7853,17 @@ exports[`better eslint`] = { "public/app/plugins/panel/gauge/GaugeMigrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/gauge/GaugePanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/DataLinks/DataLinksContextMenu\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/geomap/components/MarkersLegend.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "\'@grafana/data/src/field/scale\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + ], + "public/app/plugins/panel/geomap/editor/FrameSelectionEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -7373,7 +7887,11 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "12"] ], "public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldValueMatcher\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/plugins/panel/geomap/editor/layerEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/utils/OptionsUIBuilders\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/panel/geomap/layers/basemaps/esri.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -7382,7 +7900,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/panel/geomap/layers/data/routeLayer.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/plugins/panel/geomap/layers/registry.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -7395,6 +7914,9 @@ exports[`better eslint`] = { "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/checkFeatureMatchesStyleRule.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/matchers/compareValues\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/geomap/utils/layers.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -7402,12 +7924,23 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/panel/heatmap/HeatmapPanel.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + ], + "public/app/plugins/panel/heatmap/HeatmapTooltip.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipWrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "4"] ], "public/app/plugins/panel/heatmap/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/heatmap/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/options/builder\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/heatmap/palettes.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -7435,15 +7968,44 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "15"], [0, 0, 0, "Do not use any type assertions.", "16"] ], + "public/app/plugins/panel/histogram/Histogram.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/histogram/HistogramPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/histogram/HistogramTooltip.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipWrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "5"] + ], + "public/app/plugins/panel/histogram/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/options/builder\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/histogram/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/live/LivePanel.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/panel/logs/LogsPanel.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/panel/logs/LogsPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "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/news/component/News.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/nodeGraph/Edge.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -7458,6 +8020,9 @@ exports[`better eslint`] = { [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], + "public/app/plugins/panel/nodeGraph/editor/ArcOptionsEditor.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/nodeGraph/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./NodeGraph\`)", "0"] ], @@ -7467,25 +8032,55 @@ exports[`better eslint`] = { "public/app/plugins/panel/nodeGraph/types.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] ], + "public/app/plugins/panel/piechart/PieChart.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/mixins\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/utils/useComponetInstanceId\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], "public/app/plugins/panel/piechart/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "public/app/plugins/panel/piechart/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/options/builder/tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/stat/StatMigrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/stat/StatPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/fieldOverrides\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/DataLinks/DataLinksContextMenu\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipWrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "5"] + ], "public/app/plugins/panel/state-timeline/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "public/app/plugins/panel/status-history/StatusHistoryPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/table/TablePanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/Table/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] + [0, 0, 0, "\'@grafana/ui/src/components/Table/SparklineCell\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], "public/app/plugins/panel/table/migrations.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/reduce\' import is restricted from being used by a pattern. Import from the public export instead.", "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.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], "public/app/plugins/panel/text/textPanelMigrationHandler.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -7499,6 +8094,17 @@ exports[`better eslint`] = { "public/app/plugins/panel/timeseries/SpanNullsEditor.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/panel/timeseries/TimeSeriesPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], + "public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipWrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "5"] + ], "public/app/plugins/panel/timeseries/migrations.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -7511,9 +8117,15 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"] ], + "public/app/plugins/panel/timeseries/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/options/builder/tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin2.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/internal\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -7524,18 +8136,45 @@ 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, "Unexpected any. Specify a different type.", "1"] ], + "public/app/plugins/panel/timeseries/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/convertFieldType\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/nulls/nullToValue\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/internal\' import is restricted from being used by a pattern. Import from the public export instead.", "3"] + ], + "public/app/plugins/panel/trend/TrendPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinDataFrames\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] + ], + "public/app/plugins/panel/trend/module.tsx:5381": [ + [0, 0, 0, "\'@grafana/ui/src/options/builder/tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + ], "public/app/plugins/panel/xychart/SeriesEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/MatchersUI/FieldNamePicker\' import is restricted from being used by a pattern. Import from the public export instead.", "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.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"] + ], + "public/app/plugins/panel/xychart/XYChartPanel.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/utils\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] + ], + "public/app/plugins/panel/xychart/XYChartTooltip.tsx:5381": [ + [0, 0, 0, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipContent\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipFooter\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipHeader\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/VizTooltipWrapper\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], + [0, 0, 0, "\'@grafana/ui/src/components/VizTooltip/types\' import is restricted from being used by a pattern. Import from the public export instead.", "5"] ], "public/app/plugins/panel/xychart/migrations.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/panel/xychart/scatter.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, "\'@grafana/data/src/themes/colorManipulator\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/components/uPlot/types\' import is restricted from being used by a pattern. Import from the public export instead.", "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"], @@ -7548,10 +8187,15 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "11"], [0, 0, 0, "Do not use any type assertions.", "12"], [0, 0, 0, "Do not use any type assertions.", "13"], - [0, 0, 0, "Unexpected any. Specify a different type.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"], + [0, 0, 0, "Do not use any type assertions.", "14"], + [0, 0, 0, "Do not use any type assertions.", "15"], [0, 0, 0, "Unexpected any. Specify a different type.", "16"], - [0, 0, 0, "Unexpected any. Specify a different type.", "17"] + [0, 0, 0, "Unexpected any. Specify a different type.", "17"], + [0, 0, 0, "Unexpected any. Specify a different type.", "18"], + [0, 0, 0, "Unexpected any. Specify a different type.", "19"] + ], + "public/app/plugins/panel/xychart/utils.ts:5381": [ + [0, 0, 0, "\'@grafana/data/src/field/fieldState\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], "public/app/plugins/sdk.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`loadPluginCss\`)", "0"] @@ -7684,6 +8328,11 @@ exports[`better eslint`] = { [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"] + ], + "scripts/cli/generateSassVariableFiles.ts:5381": [ + [0, 0, 0, "\'@grafana/ui/src/themes/_variables.dark.scss.tmpl\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "\'@grafana/ui/src/themes/_variables.light.scss.tmpl\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], + [0, 0, 0, "\'@grafana/ui/src/themes/_variables.scss.tmpl\' import is restricted from being used by a pattern. Import from the public export instead.", "2"] ] }` }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d46ac756da4..57f7405ccff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -32,7 +32,7 @@ /devenv/README.md @grafana/docs-grafana # START Technical documentation -/.vale.ini +/.vale.ini @grafana/docs-tooling # `make docs` procedure and related workflows are owned @grafana/docs-tooling. Slack #docs. /docs/ @grafana/docs-tooling @@ -786,10 +786,10 @@ embed.go @grafana/grafana-as-code /.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad /.github/workflows/update-changelog.yml @grafana/grafana-developer-enablement-squad /.github/workflows/update-make-docs.yml @grafana/docs-tooling -/.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-cat -/.github/workflows/publish-kinds-next.yml @grafana/platform-cat -/.github/workflows/publish-kinds-release.yml @grafana/platform-cat -/.github/workflows/verify-kinds.yml @grafana/platform-cat +/.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring +/.github/workflows/publish-kinds-next.yml @grafana/platform-monitoring +/.github/workflows/publish-kinds-release.yml @grafana/platform-monitoring +/.github/workflows/verify-kinds.yml @grafana/platform-monitoring /.github/workflows/dashboards-issue-add-label.yml @grafana/dashboards-squad /.github/workflows/ephemeral-instances-pr-comment.yml @grafana/grafana-backend-services-squad /.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-developer-enablement-squad diff --git a/.github/workflows/documentation-ci.yml b/.github/workflows/documentation-ci.yml index 80ad62a573e..061aad71099 100644 --- a/.github/workflows/documentation-ci.yml +++ b/.github/workflows/documentation-ci.yml @@ -12,8 +12,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: grafana/writers-toolkit/vale-action@4b1248585248751e3b12fd020cf7ac91540ca09c # vale-action/v1.0.0 + - uses: grafana/writers-toolkit/vale-action@13205961f20ad13843505a9b84fdf032f911a3f4 # vale-action/v1.1.0 with: - filter: | - '.Name in ["Grafana.WordList", "Grafana.Spelling", "Grafana.ProductPossessives"]' + filter: '.Name in ["Grafana.WordList", "Grafana.Spelling", "Grafana.ProductPossessives"]' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vale.ini b/.vale.ini index 3f5c12597c8..cf4c71392dc 100644 --- a/.vale.ini +++ b/.vale.ini @@ -2,3 +2,4 @@ MinAlertLevel = warning [*] BasedOnStyles = Grafana +TokenIgnores = (+?), \*\*[^\n]+\*\* diff --git a/docs/sources/cli.md b/docs/sources/cli.md index 3352c8e85ba..3a0df07cbd2 100644 --- a/docs/sources/cli.md +++ b/docs/sources/cli.md @@ -17,7 +17,9 @@ weight: 400 # Grafana CLI -Grafana CLI is a small executable that is bundled with Grafana server. It can be executed on the same machine Grafana server is running on. Grafana CLI has `plugins` and `admin` commands, as well as global options. +Grafana CLI is a small executable that's bundled with Grafana server. +You can run it on the same machine Grafana server is running on. +Grafana CLI has `plugins` and `admin` commands, as well as global options. To list all commands and options: @@ -25,13 +27,17 @@ To list all commands and options: grafana cli -h ``` -## Invoking Grafana CLI +## Run Grafana CLI -To invoke Grafana CLI, add the path to the grafana binaries in your `PATH` environment variable. Alternately, if your current directory is the `bin` directory, use `./grafana cli`. Otherwise, you can specify full path to the CLI. For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and invoke it with `grafana cli`. +To run Grafana CLI, add the path to the Grafana binaries in your `PATH` environment variable. +Alternately, if your current directory is the `bin` directory, run `./grafana cli`. +Otherwise, you can specify full path to the binary. +For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and run it with `grafana cli`. -{{% admonition type="note" %}} -Some commands, such as installing or removing plugins, require `sudo` on Linux. If you are on Windows, run Windows PowerShell as Administrator. -{{% /admonition %}} +{{< admonition type="note" >}} +Some commands, such as installing or removing plugins, require `sudo` on Linux. +If you're on Windows, run Windows PowerShell as Administrator. +{{< /admonition >}} ## Grafana CLI command syntax diff --git a/docs/sources/tutorials/alerting-get-started-pt2/index.md b/docs/sources/tutorials/alerting-get-started-pt2/index.md index a26d3b8fd57..bcc77632336 100644 --- a/docs/sources/tutorials/alerting-get-started-pt2/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt2/index.md @@ -10,10 +10,10 @@ labels: - cloud tags: - beginner -title: Get started with Grafana Alerting - Part 2 of 3 +title: Get started with Grafana Alerting - Part 2 of 4 weight: 50 killercoda: - title: Get started with Grafana Alerting - Part 2 of 3 + title: Get started with Grafana Alerting - Part 2 of 4 description: Learn to use alert instances and route notifications by labels to contacts, building on your alerting skills in Grafana for more advanced workflows — Part 2. backend: imageid: ubuntu @@ -21,7 +21,7 @@ killercoda: -# Get started with Grafana Alerting - Part 2 of 3 +# Get started with Grafana Alerting - Part 2 of 4 The Get started with Grafana Alerting tutorial Part 2 is a continuation of [Get started with Grafana Alerting tutorial Part 1](http://www.grafana.com/tutorials/alerting-get-started/). @@ -222,7 +222,7 @@ The alert rule that you are about to create is meant to monitor web traffic page ### Create an alert rule -1. Navigate to **Alerting > Alert rules**. +1. Navigate to **Alerts & IRM > Alerting > Alert rules**. 1. Click **New alert rule**. ### Enter an alert rule name @@ -260,18 +260,21 @@ It should return two series.`desktop` in Firing state, and `mobile` in Normal st +### Add folders and labels + +1. In **Folder**, click **+ New folder** and enter a name. For example: `web-traffic-alerts` . This folder contains our alert rules. + ### Set evaluation behavior In the [life cycle](http://grafana.com/docs/grafana/next/alerting/fundamentals/alert-rule-evaluation/) of alert instances, when an alert condition (threshold) is not met, the alert instance state is **Normal**. Similarly, when the condition is breached (for longer than the pending period, which in this tutorial will be 0), the alert instance state switches back to **Alerting**, which means that the alert rule state is **Firing**, and a notification is sent. To set up evaluation behavior: -1. In **Folder**, click **+ New folder** and enter a name. For example: `web-traffic-alerts`. This folder contains our alert rules. -1. In **Evaluation group**, repeat the above step to create a new evaluation group. Name it `1m` (referring to “1 minute”). -1. Choose an Evaluation interval (how often the alert will be evaluated). Choose `1m`. -1. Set the pending period to `0s` (zero seconds), so the alert rule fires the moment the condition is met. +1. In **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `1m` (referring to “1 minute”). +1. Choose an **Evaluation interval** (how often the alert will be evaluated). Choose `1m`. +1. Set the **pending period** to `0s` (zero seconds), so the alert rule fires the moment the condition is met. -### Configure labels and notifications +### Configure notifications In this section, you can select how you want to route your alert instances. Since we want to route by notification policy, we need to ensure that the labels match the alert instance. diff --git a/docs/sources/tutorials/alerting-get-started-pt3/index.md b/docs/sources/tutorials/alerting-get-started-pt3/index.md index ceef5ad8867..7445758c22b 100644 --- a/docs/sources/tutorials/alerting-get-started-pt3/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt3/index.md @@ -10,10 +10,10 @@ labels: - cloud tags: - intermediate -title: Get started with Grafana Alerting - Part 3 +title: Get started with Grafana Alerting - Part 3 of 4 weight: 60 killercoda: - title: Get started with Grafana Alerting - Part 3 + title: Get started with Grafana Alerting - Part 3 of 4 description: Learn how to group alert notifications effectively to reduce noise and streamline communication in Grafana Alerting — Part 3. backend: imageid: ubuntu @@ -37,7 +37,7 @@ refs: -# Get started with Grafana Alerting - Part 3 +# Get started with Grafana Alerting - Part 3 of 4 The Get started with Grafana Alerting tutorial Part 3 is a continuation of [Get started with Grafana Alerting tutorial Part 2](http://www.grafana.com/tutorials/alerting-get-started-pt2/). @@ -338,7 +338,7 @@ Following the above example, [notification policies](ref:notification-policies) In this section we configure an alert rule based on our application monitoring example. -1. Navigate to **Alerting > Alert rules**. +1. Navigate to **Alerts & IRM > Alerting > Alert rules**. 2. Click **New alert rule**. ### Enter an alert rule name @@ -383,16 +383,19 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da {{< figure src="/media/docs/alerting/regions-alert-instance-preview.png" max-width="750px" alt="Preview of a query returning alert instances." >}} +### Add folders and labels + +1. In **Folder**, click **+ New folder** and enter a name. For example: `Multi-region alerts` . This folder contains our alert rules. + ### Set evaluation behavior Every alert rule is assigned to an evaluation group. You can assign the alert rule to an existing evaluation group or create a new one. -1. In **Folder**, click **+ New folder** and enter a name. For example: `Multi-region alerts`. This folder contains our alert rules. -1. In the **Evaluation group**, repeat the above step to create a new evaluation group. Name it `Multi-region group`. +1. In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `Multi-region group`. 1. Choose an **Evaluation interval** (how often the alert are evaluated). Choose `1m`. -1. Set the pending period to `0s` (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration). +1. Set the **pending period** to `0s` (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration). -### Configure labels and notifications +### Configure notifications Select who should receive a notification when an alert rule fires. @@ -508,4 +511,24 @@ _Detail of memory alert instances grouped into a separate notification for us-ea By configuring **notification policies** and using **labels** (such as _region_), you can group alert notifications based on specific criteria and route them to the appropriate teams. Fine-tuning **timing options**—including group wait, group interval, and repeat interval—further can reduce noise and ensures notifications remain actionable without overwhelming on-call engineers. +## Learn more in [Grafana Alerting Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/) + + + +{{< admonition type="tip" >}} + +In [Get started with Grafana Alerting - Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/) you learn how to use templates to create customized and concise notifications. + +{{< /admonition >}} + + + +{{< docs/ignore >}} + +In [Get started with Grafana Alerting - Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/) you learn how to use templates to create customized and concise notifications. + +{{< /docs/ignore >}} + + + diff --git a/docs/sources/tutorials/alerting-get-started-pt4/index.md b/docs/sources/tutorials/alerting-get-started-pt4/index.md new file mode 100644 index 00000000000..9bca211b882 --- /dev/null +++ b/docs/sources/tutorials/alerting-get-started-pt4/index.md @@ -0,0 +1,421 @@ +--- +Feedback Link: https://github.com/grafana/tutorials/issues/new +categories: + - alerting +description: Learn how to use templates to create customized and concise notifications — Part 4 +labels: + products: + - enterprise + - oss + - cloud +tags: + - intermediate +title: Get started with Grafana Alerting - Part 4 +weight: 60 +killercoda: + title: Get started with Grafana Alerting - Part 4 + description: Learn how to use templates to create customized and concise notifications — Part 4. + backend: + imageid: ubuntu +refs: + alert-labels: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rules/annotation-label/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rules/annotation-label/ + template-labels-annotations: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/ + template-labels-annotations-ref: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/reference/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/ + template-labels-annotations-ref-labels-variable: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/reference/#labels + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/#labels + template-labels-annotations-ref-values-variable: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/reference/#values + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/#values + template-labels-annotations-lang: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/language/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/language/ + template-notifications: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/template-notifications/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/ + template-notifications-ref: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/template-notifications/reference/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/reference/ + template-notifications-lang: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/template-notifications/language/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/language/ + templates: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/templates/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/templates/ +--- + + + +# Get started with Grafana Alerting - Part 4 + +The Get started with Grafana Alerting tutorial Part 4 is a continuation of [Get started with Grafana Alerting tutorial Part 3](http://www.grafana.com/tutorials/alerting-get-started-pt3/). + +In this tutorial, you will learn: + +- The two types of templates in Grafana Alerting: labels and annotations and notification templates. +- How to configure alert rules with summary and description annotations. +- How to create a notification template that integrates with alert rule annotations. +- How to use a built-in notification template to group and format multiple alert instances. +- How to preview alert notifications by leveraging alert instances in the notification template payload. + + + + + + +{{< docs/ignore >}} + +## Set up the Grafana stack + +{{< /docs/ignore >}} + +## Before you begin + +There are different ways you can follow along with this tutorial. + +- **Grafana Cloud** + + - As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user). + + Continue to [how templating works](#how-templating-works). + +- **Interactive learning environment** + + - Alternatively, you can try out this example in our interactive learning environment: [Get started with Grafana Alerting - Part 4](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt4/). It's a fully configured environment with all the dependencies already installed. + +- **Grafana OSS** + + - If you opt to run a Grafana stack locally, ensure you have the following applications installed: + + - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) + - [Git](https://git-scm.com/) + +### Set up the Grafana stack (OSS users) + + + +To demonstrate the observation of data using the Grafana stack, download and run the following files. + +1. Clone the [tutorial environment repository](https://www.github.com/grafana/tutorial-environment). + + + + ``` + git clone https://github.com/grafana/tutorial-environment.git + ``` + + + +1. Change to the directory where you cloned the repository: + + + + ``` + cd tutorial-environment + ``` + + + +1. Run the Grafana stack: + + + + ``` + docker compose up -d + ``` + + + + {{< docs/ignore >}} + + + + ```bash + docker-compose up -d + ``` + + + + {{< /docs/ignore >}} + + The first time you run `docker compose up -d`, Docker downloads all the necessary resources for the tutorial. This might take a few minutes, depending on your internet connection. + + + + {{< admonition type="note" >}} + If you already have Grafana, Loki, or Prometheus running on your system, you might see errors, because the Docker image is trying to use ports that your local installations are already using. If this is the case, stop the services, then run the command again. + {{< /admonition >}} + + + + {{< docs/ignore >}} + + NOTE: + + If you already have Grafana, Loki, or Prometheus running on your system, you might see errors, because the Docker image is trying to use ports that your local installations are already using. If this is the case, stop the services, then run the command again. + + {{< /docs/ignore >}} + + + + +## How templating works + +In Grafana, you can use [templates](https://grafana.com/docs/grafana/latest/alerting/fundamentals/templates/) to dynamically pull in specific data about the alert rule. This results in more flexible and informative alert notification messages. You can template either alert rule labels and annotations, or notification templates. Both use the Go template language. + +{{< figure src="/media/docs/alerting/how-notification-templates-works.png" max-width="1200px" caption="How templating works" >}} + +### Templating alert rule labels and annotations + +[Labels and annotations](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/) are key fields where templates are applied. One of the main advantages of using templating in annotations is the ability to incorporate dynamic data from queries, allowing alerts to reflect real-time information relevant to the triggered condition. By using templating in annotations, you can customize the content of each alert instance, such as including instance names and metric values, so the notification becomes more informative. + +### Notification templates + +The real power of templating lies in how it helps you format notifications with dynamic alert data. [Notification templates](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/) let you pull in details from annotations to create clear and consistent messages. They also make it simple to reuse the same format across different contact points, saving time and effort. + +Notification templates allow you to customize how information is presented in each notification. For example, you can use templates to organize and format details about firing or resolved alerts, making it easier for recipients to understand the status of each alert at a glance—all within a single notification. + +This particular notification template pulls in summary and description annotations for each alert instance and organizes them into separate sections, such as "firing" and "resolved." This way, instead of getting a long list of individual alert notifications, users can receive one well-structured message with all the relevant details grouped together. + +This approach is helpful when you want to reduce notification noise, especially in situations where multiple instances of an alert are firing at the same time (e.g., high CPU usage across several instances). You can leverage templates to create a unified, easy-to-read notification that includes all the pertinent details. + + + + +## Step 1: Template labels and annotations + +Now that we've introduced how templating works, let’s move on to the next step. We guide you through creating an alert rule with a summary and description annotation. In doing so, we incorporate CPU usage and instance names, which we later use in our notification template. + +### Create an alert rule + +1. Sign in to Grafana: + + - **Grafana Cloud** users: Log in via Grafana Cloud. + - **OSS users**: Go to [http://localhost:3000](http://localhost:3000). + +1. Create an alert rule that includes a summary and description annotation: + - Navigate to **Alerts & IRM > Alerting > Alert rules**. + - Click **+ New alert rule**. + - Enter an **alert rule name**. Name it `High CPU usage` +1. **Define query an alert condition** section: + + - Select TestData data source from the drop-down menu. + + [TestData](https://grafana.com/docs/grafana/latest/datasources/testdata/) is included in the demo environment. If you’re working in Grafana Cloud or your own local Grafana instance, you can add the data source through the Connections menu. + + - From **Scenario** select **CSV Content**. + - Copy in the following CSV data: + + ``` + region,cpu-usage,service,instance + us-west,88,web-server-1,server-01 + us-west,81,web-server-1,server-02 + us-east,79,web-server-2,server-03 + us-east,52,web-server-2,server-04 + ``` + + This dataset simulates a data source returning multiple time series, with each time series generating a separate alert instance. + +1. **Alert condition** section: + + - Keep Last as the value for the reducer function (`WHEN`), and `75` as the threshold value, representing CPU usage above 75% .This is the value above which the alert rule should trigger. + - Click **Preview alert rule condition** to run the queries. + + It should return 3 series in Firing state, and 1 in Normal state. + + {{< figure src="/media/docs/alerting/part-4-firing-instances-preview.png" max-width="1200px" caption="Preview of a query returning alert instances" >}} + +1. Add folders and labels section: + + - In **Folder**, click **+ New folder** and enter a name. For example: `System metrics` . This folder contains our alert rules. + + Note: while it's possible to template labels here, in this tutorial, we focus on templating the summary and annotations fields instead. + +1. **Set evaluation behaviour** section: + - In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `High usage`. + - Choose an **Evaluation interval** (how often the alert will be evaluated). Choose `1m`. + - Set the **pending period** to 0s (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration.). +1. **Configure notifications** section: + + Select who should receive a notification when an alert rule fires. + + - Select a **Contact point**. If you don’t have any contact points, click _View or create contact points_. + +1. **Configure notification message** section: + + In this step, you’ll configure the **summary** and **description** annotations to make your alert notifications informative and easy to understand. These annotations use templates to dynamically include key information about the alert. + + - **Summary** annotation: Enter the following code as the value for the annotation.: + + ```go + {{- "\n" -}} + Instance: {{ index $labels "instance" }} + {{- "\t" -}} Usage: {{ index $values "A"}}%{{- "\n" -}} + ``` + + This template automatically adds the instance name (from the [$labels](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/#labels) data) and its current CPU usage (from [$values["A"]](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/#values)) into the alert summary. `\t`: Adds a tab space between the instance name and the value. And, `\n`: Inserts a new line after the value. + + Output example: + + ``` + server-01 88 + ``` + + This output helps you quickly see which instance is affected and its usage level. + +1. Optional: Add a description to help the on-call engineer to better understand what the alert rule is about. Eg. This alert monitors CPU usage across instances and triggers if any instance exceeds a usage threshold of 75%. +1. Click **Save rule and exit**. + +Now that we’ve configured an alert rule with dynamic templates for the **summary** annotation, the next step is to customize the alert notifications themselves. While the default notification message includes the summary annotation and works well, it can often be too verbose. + +{{< figure src="/media/docs/alerting/templated-annotation-alert.png" max-width="1200px" caption="Default email alert notification with templated annotation" >}} + +To make our alert notifications more concise and tailored to our needs, we’ll create a custom **notification template** that references the summary annotation we just set up. Notification templates are especially useful because they can be reused across multiple contact points, ensuring consistent alert messages. + + + + +## Step 2: Template notifications + +In this step, we use a built-in notification template to format alert notifications in a clear and organized way. Notification templates allow us to customize the structure of alert messages, making them easier to read and more relevant. + +Without a notification template, the alert messages would include the default Grafana formatting (`default.message`, see image above). + +### Adding a notification template: + +1. Navigate to **Alerts & IRM** > **Alerting** > **Contact point**s. +1. Select the **Notification Templates** tab. +1. Click **+ Add notification template group**. +1. Enter a name. E.g `instance-cpu-summary`. +1. From the **Add example** dropdown menu, choose `Print firing and resolved alerts`. + +This template prints out alert instances into two sections: **firing alerts** and **resolved alerts**, and includes only the key details for each. In addition, it adds our summary and description annotations. + +``` +{{- /* Example displaying firing and resolved alerts separately in the notification. */ -}} +{{- /* Edit the template name and template content as needed. */ -}} +{{ define "custom.firing_and_resolved_alerts" -}} +{{ len .Alerts.Resolved }} resolved alert(s) +{{ range .Alerts.Resolved -}} + {{ template "alert.summary_and_description" . -}} +{{ end }} +{{ len .Alerts.Firing }} firing alert(s) +{{ range .Alerts.Firing -}} + {{ template "alert.summary_and_description" . -}} +{{ end -}} +{{ end -}} +{{ define "alert.summary_and_description" }} + Summary: {{.Annotations.summary}} + Status: {{ .Status }} + Description: {{.Annotations.description}} +{{ end -}} +``` + +{{< docs/ignore >}} +Note: Your notification template name (`{{define ""}}`) must be unique. You cannot have two templates with the same name in the same notification template group or in different notification template groups. +{{< /docs/ignore >}} + + + +{{< admonition type="note" >}} +Your notification template name (`{{define ""}}`) must be unique. You cannot have two templates with the same name in the same notification template group or in different notification template groups. +{{< /admonition >}} + + + +Here’s a breakdown of the template: + +- `{{ define "custom.firing_and_resolved_alerts" -}}` section: Displays the number of resolved alerts and their summaries, using the `alert.summary_and_description` template to include the summary, status, and description for each alert. +- `.Alerts.Firing` section: Similarly lists the number of firing alert instances and their details. +- `alert.summary_and_description`: This sub-template pulls the summary annotation you configured earlier. + +In the **Preview** area, you can see a sample of how the notification would look. Since we’ve already created our alert rule, you can take it a step further by previewing how an actual alert instance from your rule would appear in the notification. + +1. Click **Edit payload**. +1. Click **Use existing alert instance**. + + You should see our alert rule listed on the left. + +1. Click the alert rule. +1. Select an instance. +1. Click **Add alert data to payload**. + + The alert instance is added to the bottom of the preview. + + {{< figure src="/media/docs/alerting/alert-instance-preview-in-template.png" max-width="1200px" caption="Preview of an alert instance in a notification template" >}} + +1. Click **Save**. + +With the notification template ready, the next step is to apply it to your contact point to see it in action. + + + + +### Apply the template to your contact point + +1. Apply the template to your contact point. + - Navigate to **Alerts & IRM** > **Alerting** > **Contact points**. + - Edit your contact point. +1. **Optional** [email] **settings** section: + - Click **Edit Message**. + - Under **Select notification template**, search `custom.firing_and_resolved_alerts`. + - Click **Save**. +1. Save your contact point. + + + + +### Receiving notifications + +Now that the template has been applied to the contact point, you should receive notifications in the specified contact point. + +Note: you might need to pause the alert rule evaluation and resume it to trigger the notification. + +{{< figure src="/media/docs/alerting/templated-notification-cpu.png" max-width="1200px" caption="Templated email notification for CPU and memory usage" >}} + +In the screen capture, you can see how the notification template groups the alert instances into two sections: **firing alerts** and **resolved alerts**. Each section includes only the key details for each alert, ensuring the message remains concise and focused. Additionally, the summary and description annotations we created earlier are included, providing affected instance and CPU usage. + + + + +## Conclusion + +In this tutorial, we learned how to use templating in Grafana Alerting to create dynamic and actionable alert notifications. We explored how to configure alert rules with annotations, design custom notification templates, and apply them to contact points to enhance the clarity and efficiency of alert messages. By organizing alert instances into concise notifications, you can reduce noise and ensure on-call engineers quickly understand and address critical issues. + +To deepen your understanding of Grafana’s templating, explore the following resources: + +- **Overview of the functions and operators used in templates**: + + - [Notification template language](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/language/) + - [Alert rule template language](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/language/) + +- [**Notification template reference**](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/reference/): Lists the data available for use in notification templates and explores specific functions. +- [**Alert rule template reference**](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/): Covers the specifics of creating dynamic labels and annotations for alert rules using elements such as variables and functions. + + diff --git a/docs/sources/tutorials/alerting-get-started/index.md b/docs/sources/tutorials/alerting-get-started/index.md index b26e8221c0a..c77a4b0693f 100644 --- a/docs/sources/tutorials/alerting-get-started/index.md +++ b/docs/sources/tutorials/alerting-get-started/index.md @@ -11,10 +11,10 @@ labels: - cloud tags: - beginner -title: Get started with Grafana Alerting - Part 1 of 3 +title: Get started with Grafana Alerting - Part 1 of 4 weight: 50 killercoda: - title: Get started with Grafana Alerting - Part 1 of 3 + title: Get started with Grafana Alerting - Part 1 of 4 description: Get started with Grafana Alerting by creating your first alert rule, sending notifications to a webhook, and generating data to test it live — Part 1. backend: imageid: ubuntu @@ -22,7 +22,7 @@ killercoda: -# Get started with Grafana Alerting - Part 1 of 3 +# Get started with Grafana Alerting - Part 1 of 4 In this guide, we walk you through the process of setting up your first alert in just a few minutes. You'll witness your alert in action with real-time data, as well as sending alert notifications. @@ -181,7 +181,7 @@ We have created a dummy Webhook endpoint and created a new Alerting contact poin Next, we establish an [alert rule](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule/) within Grafana Alerting to notify us whenever alert rules are triggered and resolved. -1. In Grafana, **navigate to Alerting** > **Alert rules**. Click on **New alert rule**. +1. In Grafana, navigate to **Alerts & IRM > Alerting > Alert rules**. Click on **New alert rule**. 1. Enter alert rule name for your alert rule. Make it short and descriptive as this appears in your alert notification. For instance, **database-metrics** @@ -203,6 +203,10 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da {{< figure src="/media/docs/alerting/random-walk-firing-alert-rule.png" max-width="1200px" caption="A preview of a firing alert" >}} +### Add folders and labels + +1. In **Folder**, click **+ New folder** and enter a name. For example: `metric-alerts` . This folder contains our alert rules. + ### Set evaluation behavior The [alert rule evaluation](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/rule-evaluation/) defines the conditions under which an alert rule triggers, based on the following settings: @@ -213,13 +217,12 @@ The [alert rule evaluation](https://grafana.com/docs/grafana/latest/alerting/fun To set up the evaluation: -1. In **Folder**, click **+ New folder** and enter a name. For example: _metric-alerts_. This folder contains our alerts. -1. In the **Evaluation group**, repeat the above step to create a new evaluation group. Name it _1m-evaluation_. +1. In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it _1m-evaluation_. 1. Choose an **Evaluation interval** (how often the alert are evaluated). For example, every `1m` (1 minute). -1. Set the pending period to, `0s` (zero seconds), so the alert rule fires the moment the condition is met. +1. Set the **pending period** to, `0s` (zero seconds), so the alert rule fires the moment the condition is met. -### Configure labels and notifications +### Configure notifications Choose the contact point where you want to receive your alert notifications. diff --git a/go.mod b/go.mod index 0fc4158c148..8f4ade38a1a 100644 --- a/go.mod +++ b/go.mod @@ -372,6 +372,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/karlseguin/ccache/v3 v3.0.5 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect diff --git a/go.sum b/go.sum index b05443e57cb..0030ca3cbf2 100644 --- a/go.sum +++ b/go.sum @@ -1766,8 +1766,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= diff --git a/go.work.sum b/go.work.sum index 92ad551ad18..5c7206dd59f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -440,6 +440,7 @@ cloud.google.com/go/functions v1.15.4/go.mod h1:CAsTc3VlRMVvx+XqXxKqVevguqJpnVip cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k= cloud.google.com/go/functions v1.19.1 h1:eWjTZohtJX/9rckZYXaYVViGi06JkNJRKvm0aO+ce+g= cloud.google.com/go/functions v1.19.1/go.mod h1:18RszySpwRg6aH5UTTVsRfdCwDooSf/5mvSnU7NAk4A= +cloud.google.com/go/gaming v1.9.0 h1:7vEhFnZmd931Mo7sZ6pJy7uQPDxF7m7v8xtBheG08tc= cloud.google.com/go/gaming v1.10.1 h1:5qZmZEWzMf8GEFgm9NeC3bjFRpt7x4S6U7oLbxaf7N8= cloud.google.com/go/gaming v1.10.1/go.mod h1:XQQvtfP8Rb9Rxnxm5wFVpAp9zCQkJi2bLIb7iHGwB3s= cloud.google.com/go/gkebackup v1.3.0/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= @@ -473,6 +474,7 @@ cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfj cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= cloud.google.com/go/gkemulticloud v1.4.0 h1:t2HXXYrICui+rZXScietjU1YdrQDLXpfqqrTo7zWSYQ= cloud.google.com/go/gkemulticloud v1.4.0/go.mod h1:rg8YOQdRKEtMimsiNCzZUP74bOwImhLRv9wQ0FwBUP4= +cloud.google.com/go/grafeas v0.2.0 h1:CYjC+xzdPvbV65gi6Dr4YowKcmLo045pm18L0DhdELM= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= 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= @@ -955,6 +957,7 @@ cloud.google.com/go/workflows v1.13.1 h1:DkxrZ4HyXvjQLZWsYAUOV1w7d2a43XscM9dmkIG cloud.google.com/go/workflows v1.13.1/go.mod h1:xNdYtD6Sjoug+khNCAtBMK/rdh8qkjyL6aBas2XlkNc= contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec h1:CSNP8nIEQt4sZEo2sGUiWSmVJ9c5QdyIQvwzZAsn+8Y= contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60= @@ -1038,6 +1041,8 @@ github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1CPDhLlA3Z37E= github.com/KimMachineGun/automemlimit v0.6.0/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= +github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8= +github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= @@ -1047,6 +1052,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0= github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPrQV1xCoSjc= github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s= @@ -1169,12 +1176,14 @@ github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgf github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= @@ -1257,6 +1266,7 @@ github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWE github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= +github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b h1:Yqiad0+sloMPdd/0Fg22actpFx0dekpzt1xJmVNVkU0= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/digitalocean/godo v1.113.0/go.mod h1:Z2mTP848Vi3IXXl5YbPekUgr4j4tOePomA+OE1Ag98w= github.com/digitalocean/godo v1.118.0 h1:lkzGFQmACrVCp7UqH1sAi4JK/PWwlc5aaxubgorKmC4= @@ -1275,6 +1285,7 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 h1:IMfrF5LCzP2Vhw7j4IIH3HxPsCLuZYjDqFAM/C88ulg= github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= @@ -1295,6 +1306,7 @@ github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= @@ -1335,6 +1347,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= @@ -1433,6 +1446,7 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksP github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -1476,6 +1490,7 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -1506,6 +1521,7 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= +github.com/influxdata/influxdb v1.7.7 h1:UvNzAPfBrKMENVbQ4mr4ccA9sW+W1Ihl0Yh1s0BiVAg= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b h1:i44CesU68ZBRvtCjBi3QSosCIKrjmMbYlQMFAwVLds4= @@ -1582,6 +1598,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kshvakov/clickhouse v1.3.5 h1:PDTYk9VYgbjPAWry3AoDREeMgOVUFij6bh6IjlloHL0= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= @@ -1633,6 +1650,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0 github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= @@ -1769,6 +1788,7 @@ github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/En github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= @@ -1816,6 +1836,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= +github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75 h1:cA+Ubq9qEVIQhIWvP2kNuSZ2CmnfBJFSRq+kO1pu2cc= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 h1:F+GIVtGqCFxPxO46ujf8cEOP574MBoRm3gNbPXECbxs= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= @@ -2003,6 +2024,8 @@ go.opentelemetry.io/collector/receiver/otlpreceiver v0.102.1/go.mod h1:0hmxfFSSq go.opentelemetry.io/collector/semconv v0.98.0/go.mod h1:8ElcRZ8Cdw5JnvhTOQOdYizkJaQ10Z2fS+R6djOnj6A= go.opentelemetry.io/collector/semconv v0.105.0 h1:8p6dZ3JfxFTjbY38d8xlQGB1TQ3nPUvs+D0RERniZ1g= go.opentelemetry.io/collector/semconv v0.105.0/go.mod h1:yMVUCNoQPZVq/IPfrHrnntZTWsLf5YGZ7qwKulIl5hw= +go.opentelemetry.io/collector/semconv v0.116.0 h1:63xCZomsKJAWmKGWD3lnORiE3WKW6AO4LjnzcHzGx3Y= +go.opentelemetry.io/collector/semconv v0.116.0/go.mod h1:N6XE8Q0JKgBN2fAhkUQtqK9LT7rEGR6+Wu/Rtbal1iI= go.opentelemetry.io/collector/service v0.102.1 h1:Lg7qrC4Zctd/OAlkpdsaZaUY+jLEGLLnOigfBLP2GW8= go.opentelemetry.io/collector/service v0.102.1/go.mod h1:L5Sh3461B1Zij7vpMMbi6M/SZicgrLB3UgbG0oUK0pA= go.opentelemetry.io/contrib/bridges/prometheus v0.53.0 h1:BdkKDtcrHThgjcEia1737OUuFdP6xzBKAMx2sNZCkvE= @@ -2063,6 +2086,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= @@ -2316,6 +2341,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= @@ -2334,6 +2360,7 @@ k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQI k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= k8s.io/code-generator v0.32.0 h1:s0lNN8VSWny8LBz5t5iy7MCdgwdOhdg7vAGVxvS+VWU= k8s.io/code-generator v0.32.0/go.mod h1:b7Q7KMZkvsYFy72A79QYjiv4aTz3GvW0f1T3UfhFq4s= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= 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/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= @@ -2350,14 +2377,17 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdz k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= +modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= @@ -2376,10 +2406,12 @@ modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= +modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0= modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= @@ -2387,6 +2419,7 @@ rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/packages/grafana-runtime/src/services/backendSrv.ts b/packages/grafana-runtime/src/services/backendSrv.ts index 896e4cd3b63..6d636c8a60b 100644 --- a/packages/grafana-runtime/src/services/backendSrv.ts +++ b/packages/grafana-runtime/src/services/backendSrv.ts @@ -70,6 +70,12 @@ export type BackendSrvRequest = { */ responseType?: 'json' | 'text' | 'arraybuffer' | 'blob' | 'stream'; + /** + * Used to cancel an open connection + * https://developer.mozilla.org/en-US/docs/Web/API/AbortController + */ + abortSignal?: AbortSignal; + /** * The credentials read-only property of the Request interface indicates whether the user agent should send cookies from the other domain in the case of cross-origin requests. */ @@ -173,6 +179,14 @@ export interface BackendSrv { * Observable http request interface */ fetch(options: BackendSrvRequest): Observable>; + + /** + * Observe each raw chunk in the response. This is useful when reading values from + * a long living HTTP connection like the kubernetes WATCH command. + * + * Each chunk includes the full response headers and the `data` property is filled with the chunk. + */ + chunked(options: BackendSrvRequest): Observable>; } let singletonInstance: BackendSrv; diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 0ab74fbcbf5..025383983bf 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -7,11 +7,6 @@ go 1.23.1 // github.com/docker/docker@v0.0.0-00010101000000-000000000000: invalid version: unknown revision 000000000000 replace github.com/docker/docker => github.com/moby/moby v26.0.0+incompatible -// Override Prometheus version because Prometheus v2.X is tagged as v0.X for Go modules purposes and Go assumes -// that v1.Y is higher than v0.X, so when we resolve dependencies if any dependency imports v1.Y we'd -// import that instead of v0.X even though v0.X is newer. -replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.52.0 - require ( cloud.google.com/go/storage v1.43.0 // @grafana/grafana-backend-group github.com/Masterminds/semver/v3 v3.3.0 // @grafana/grafana-developer-enablement-squad diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index e09d84a9721..bba6f52db98 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -90,6 +90,7 @@ type pluginInstallOpts struct { repoURL string pluginURL string pluginDir string + gcomToken string } func newInstallPluginOpts(c utils.CommandLine) pluginInstallOpts { @@ -98,6 +99,7 @@ func newInstallPluginOpts(c utils.CommandLine) pluginInstallOpts { repoURL: c.PluginRepoURL(), pluginURL: c.PluginURL(), pluginDir: c.PluginDirectory(), + gcomToken: c.GcomToken(), } } @@ -132,9 +134,10 @@ func doInstallPlugin(ctx context.Context, pluginID, version string, o pluginInst } repository := repo.NewManager(repo.ManagerCfg{ - SkipTLSVerify: o.insecure, - BaseURL: o.repoURL, - Logger: services.Logger, + SkipTLSVerify: o.insecure, + BaseURL: o.repoURL, + Logger: services.Logger, + GrafanaComAPIToken: o.gcomToken, }) // FIXME: Re-enable grafanaVersion. This check was broken in 10.2 so disabling it for the moment. diff --git a/pkg/cmd/grafana-cli/commands/install_command_test.go b/pkg/cmd/grafana-cli/commands/install_command_test.go index bfb286f58d5..9fcf045d70b 100644 --- a/pkg/cmd/grafana-cli/commands/install_command_test.go +++ b/pkg/cmd/grafana-cli/commands/install_command_test.go @@ -106,7 +106,8 @@ func TestValidatePluginRepoConfig(t *testing.T) { t.Skip("skipping integration test") } grafDir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - GrafanaComAPIURL: "https://grafana-dev.com", + GrafanaComAPIURL: "https://grafana-dev.com", + GrafanaComSSOAPIToken: "token3", }) c, err := commandstest.NewCliContext(map[string]string{ @@ -116,6 +117,9 @@ func TestValidatePluginRepoConfig(t *testing.T) { require.NoError(t, err) repoURL := c.PluginRepoURL() require.Equal(t, "https://grafana-dev.com/plugins", repoURL) + + token := c.GcomToken() + require.Equal(t, "token3", token) }) t.Run("Should use config overrides parameter if it is set alongside config parameter", func(t *testing.T) { diff --git a/pkg/cmd/grafana-cli/utils/command_line.go b/pkg/cmd/grafana-cli/utils/command_line.go index e394ab03769..6ce00ad60a9 100644 --- a/pkg/cmd/grafana-cli/utils/command_line.go +++ b/pkg/cmd/grafana-cli/utils/command_line.go @@ -26,6 +26,7 @@ type CommandLine interface { PluginDirectory() string PluginRepoURL() string PluginURL() string + GcomToken() string } type ApiClient interface { @@ -75,12 +76,7 @@ func (c *ContextCommandLine) PluginRepoURL() string { // if --config flag is set, try to get the GrafanaComAPIURL setting if c.ConfigFile() != "" { - configOptions := strings.Split(c.String("configOverrides"), " ") - cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ - Config: c.ConfigFile(), - HomePath: c.HomePath(), - Args: append(configOptions, c.Args().Slice()...), - }) + cfg, err := c.Config() if err != nil { logger.Debug("Could not parse config file", err) @@ -92,6 +88,25 @@ func (c *ContextCommandLine) PluginRepoURL() string { return c.String("repo") } +func (c *ContextCommandLine) Config() (*setting.Cfg, error) { + configOptions := strings.Split(c.String("configOverrides"), " ") + return setting.NewCfgFromArgs(setting.CommandLineArgs{ + Config: c.ConfigFile(), + HomePath: c.HomePath(), + Args: append(configOptions, c.Args().Slice()...), + }) +} + +func (c *ContextCommandLine) GcomToken() string { + cfg, err := c.Config() + + if err != nil { + logger.Debug("Could not parse config file", err) + return "" + } + return cfg.GrafanaComSSOAPIToken +} + func (c *ContextCommandLine) PluginURL() string { return c.String("pluginUrl") } diff --git a/pkg/cmd/grafana-cli/utils/command_line_mock.go b/pkg/cmd/grafana-cli/utils/command_line_mock.go index 09cf896bc93..45195a2de0a 100644 --- a/pkg/cmd/grafana-cli/utils/command_line_mock.go +++ b/pkg/cmd/grafana-cli/utils/command_line_mock.go @@ -146,6 +146,19 @@ func (_m *MockCommandLine) PluginURL() string { return r0 } +func (_m *MockCommandLine) GcomToken() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // ShowHelp provides a mock function with given fields: func (_m *MockCommandLine) ShowHelp() error { ret := _m.Called() diff --git a/pkg/login/social/connectors/azuread_oauth.go b/pkg/login/social/connectors/azuread_oauth.go index cd2e81367b5..66bc53db788 100644 --- a/pkg/login/social/connectors/azuread_oauth.go +++ b/pkg/login/social/connectors/azuread_oauth.go @@ -199,7 +199,7 @@ func (s *SocialAzureAD) Exchange(ctx context.Context, code string, authOptions . case social.ClientSecretPost: // Default behavior for ClientSecretPost, no additional setup needed default: - s.log.Debug("ClientAuthentication is not set. Using default client authentication method") + s.log.Debug("ClientAuthentication is not set. Using default client authentication method: none") } // Default token exchange @@ -379,6 +379,9 @@ func validateClientAuthentication(info *social.OAuthInfo, requester identity.Req } return nil + case social.None: + return nil + default: return ssosettings.ErrInvalidOAuthConfig("Invalid client authentication method.") } diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 172c8de58a0..741d02098a9 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -16,6 +16,7 @@ const ( // Values for ClientAuthentication under OAuthInfo (based on oidc spec) ClientSecretPost = "client_secret_post" + None = "none" // Azure AD ManagedIdentity = "managed_identity" // Other providers... diff --git a/pkg/plugins/repo/client.go b/pkg/plugins/repo/client.go index 29293537a66..1c4c6496ece 100644 --- a/pkg/plugins/repo/client.go +++ b/pkg/plugins/repo/client.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "os" + "strings" "time" "github.com/grafana/grafana/pkg/plugins/log" @@ -24,16 +25,18 @@ type Client struct { httpClientNoTimeout http.Client retryCount int grafanaComAPIToken string + grafanaComAPIURL string log log.PrettyLogger } -func NewClient(skipTLSVerify bool, grafanaComAPIToken string, logger log.PrettyLogger) *Client { +func NewClient(skipTLSVerify bool, grafanaComAPIToken, grafanaComAPIURL string, logger log.PrettyLogger) *Client { return &Client{ httpClient: MakeHttpClient(skipTLSVerify, 10*time.Second), httpClientNoTimeout: MakeHttpClient(skipTLSVerify, 0), log: logger, grafanaComAPIToken: grafanaComAPIToken, + grafanaComAPIURL: grafanaComAPIURL, } } @@ -233,7 +236,7 @@ func (c *Client) createReq(ctx context.Context, url *url.URL, compatOpts CompatO req.Header.Set("grafana-origin", orig.(string)) } - if c.grafanaComAPIToken != "" { + if strings.HasPrefix(url.String(), c.grafanaComAPIURL) && c.grafanaComAPIToken != "" { req.Header.Set("Authorization", "Bearer "+c.grafanaComAPIToken) } diff --git a/pkg/plugins/repo/client_test.go b/pkg/plugins/repo/client_test.go index 6ffdc76c2a7..ccc526386aa 100644 --- a/pkg/plugins/repo/client_test.go +++ b/pkg/plugins/repo/client_test.go @@ -70,7 +70,7 @@ func Test_Download(t *testing.T) { require.Equal(t, 2, count, "should retry on error") }) - t.Run("it should use gcom token when it's available", func(t *testing.T) { + t.Run("it should use gcom token when the token is available and request is to GCOM", func(t *testing.T) { expectedToken := "token-test" var gcomCalled bool fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -82,9 +82,29 @@ func Test_Download(t *testing.T) { })) defer fakeServer.Close() cli := fakeServer.Client() - repo := Client{httpClient: *cli, httpClientNoTimeout: *cli, log: log.NewPrettyLogger("test"), grafanaComAPIToken: expectedToken} - _, err := repo.Download(context.Background(), fakeServer.URL, "", CompatOpts{}) + pluginURL := fakeServer.URL + "/api/plugins/test-datasource" + gcomAPIURL := fakeServer.URL + "/api/plugins" + repo := Client{httpClient: *cli, httpClientNoTimeout: *cli, log: log.NewPrettyLogger("test"), grafanaComAPIToken: expectedToken, grafanaComAPIURL: gcomAPIURL} + _, err := repo.Download(context.Background(), pluginURL, "", CompatOpts{}) require.NoError(t, err) require.True(t, gcomCalled) }) + + t.Run("it should not set gcom token when the token is available but request is NOT to GCOM", func(t *testing.T) { + expectedToken := "token-test" + var serverCalled bool + fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + require.Empty(t, token, "token should not be set") + err := writeFakeZip(w) + require.NoError(t, err) + serverCalled = true + })) + defer fakeServer.Close() + cli := fakeServer.Client() + repo := Client{httpClient: *cli, httpClientNoTimeout: *cli, log: log.NewPrettyLogger("test"), grafanaComAPIToken: expectedToken, grafanaComAPIURL: "https://grafana.com/api/plugins"} + _, err := repo.Download(context.Background(), fakeServer.URL, "", CompatOpts{}) + require.NoError(t, err) + require.True(t, serverCalled) + }) } diff --git a/pkg/plugins/repo/service.go b/pkg/plugins/repo/service.go index 8007a03d609..a7a1a7f8660 100644 --- a/pkg/plugins/repo/service.go +++ b/pkg/plugins/repo/service.go @@ -14,8 +14,7 @@ import ( ) type Manager struct { - client *Client - baseURL string + client *Client log log.PrettyLogger } @@ -43,9 +42,8 @@ type ManagerCfg struct { func NewManager(cfg ManagerCfg) *Manager { return &Manager{ - baseURL: cfg.BaseURL, - client: NewClient(cfg.SkipTLSVerify, cfg.GrafanaComAPIToken, cfg.Logger), - log: cfg.Logger, + client: NewClient(cfg.SkipTLSVerify, cfg.GrafanaComAPIToken, cfg.BaseURL, cfg.Logger), + log: cfg.Logger, } } @@ -101,12 +99,12 @@ func (m *Manager) PluginVersion(ctx context.Context, pluginID, version string, c } func (m *Manager) downloadURL(pluginID, version string) string { - return fmt.Sprintf("%s/%s/versions/%s/download", m.baseURL, pluginID, version) + return fmt.Sprintf("%s/%s/versions/%s/download", m.client.grafanaComAPIURL, pluginID, version) } // grafanaCompatiblePluginVersions will get version info from /api/plugins/$pluginID/versions func (m *Manager) grafanaCompatiblePluginVersions(ctx context.Context, pluginID string, compatOpts CompatOpts) ([]Version, error) { - u, err := url.Parse(m.baseURL) + u, err := url.Parse(m.client.grafanaComAPIURL) if err != nil { return nil, err } diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 7a3c8fa0c45..58416fa8992 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -109,9 +109,9 @@ require ( golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + google.golang.org/api v0.216.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect google.golang.org/grpc v1.69.2 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 771629a5c73..68b0cb9656d 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -111,8 +111,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= -github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -384,8 +384,8 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= -google.golang.org/api v0.213.0 h1:KmF6KaDyFqB417T68tMPbVmmwtIXs2VB60OJKIHB0xQ= -google.golang.org/api v0.213.0/go.mod h1:V0T5ZhNUUNpYAlL306gFZPFt5F5D/IeyLoktduYYnvQ= +google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY= +google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 h1:ChAdCYNQFDk5fYvFZMywKLIijG7TC2m1C2CMEu11G3o= google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484/go.mod h1:KRUmxRI4JmbpAm8gcZM4Jsffi859fo5LQjILwuqj9z8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= diff --git a/pkg/registry/apis/dashboard/authorizer.go b/pkg/registry/apis/dashboard/authorizer.go index 34598438015..8f7a509ec1d 100644 --- a/pkg/registry/apis/dashboard/authorizer.go +++ b/pkg/registry/apis/dashboard/authorizer.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/apis/dashboard" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" @@ -27,12 +26,6 @@ func GetAuthorizer(dashboardService dashboards.DashboardService, l log.Logger) a } if attr.GetName() == "" { - // Discourage use of the "list" command for non super admin users - if attr.GetVerb() == "list" && attr.GetResource() == dashboard.DashboardResourceInfo.GroupResource().Resource { - if !user.GetIsGrafanaAdmin() { - return authorizer.DecisionDeny, "list summary objects (or connect as GrafanaAdmin)", err - } - } return authorizer.DecisionNoOpinion, "", nil } diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 22a9c840f5f..f7d33bad1d7 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -4,9 +4,11 @@ import ( "encoding/json" "net/http" "net/url" + "slices" "strconv" "strings" + "github.com/grafana/grafana/pkg/storage/unified/search" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -226,12 +228,17 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { Limit: int64(limit), Offset: int64(offset), Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false", - Fields: []string{ - "title", - "folder", - "tags", - }, } + fields := []string{"title", "folder", "tags"} + if queryParams.Has("field") { + // add fields to search and exclude duplicates + for _, f := range queryParams["field"] { + if f != "" && !slices.Contains(fields, f) { + fields = append(fields, f) + } + } + } + searchRequest.Fields = fields // Add the folder constraint. Note this does not do recursive search folder := queryParams.Get("folder") @@ -277,6 +284,9 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { // Add sorting if queryParams.Has("sort") { for _, sort := range queryParams["sort"] { + if slices.Contains(search.DashboardFields(), sort) { + sort = "fields." + sort + } s := &resource.ResourceSearchRequest_Sort{Field: sort} if strings.HasPrefix(sort, "-") { s.Desc = true diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go new file mode 100644 index 00000000000..1feafbf88d0 --- /dev/null +++ b/pkg/registry/apis/dashboard/search_test.go @@ -0,0 +1,100 @@ +package dashboard + +import ( + "context" + "fmt" + "net/http/httptest" + "testing" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "google.golang.org/grpc" +) + +func TestSearchHandlerFields(t *testing.T) { + // Create a mock client + mockClient := &MockClient{} + + // Initialize the search handler with the mock client + searchHandler := SearchHandler{ + log: log.New("test", "test"), + client: mockClient, + tracer: tracing.NewNoopTracerService(), + } + + t.Run("Multiple comma separated fields will be appended to default dashboard search fields", func(t *testing.T) { + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/search?field=field1&field=field2&field=field3", nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Search to be called, but it was not") + } + expectedFields := []string{"title", "folder", "tags", "field1", "field2", "field3"} + if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) { + t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields) + } + }) + + t.Run("Single field will be appended to default dashboard search fields", func(t *testing.T) { + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/search?field=field1", nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Search to be called, but it was not") + } + expectedFields := []string{"title", "folder", "tags", "field1"} + if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) { + t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields) + } + }) + + t.Run("Passing no fields will search using default dashboard fields", func(t *testing.T) { + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/search", nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Search to be called, but it was not") + } + expectedFields := []string{"title", "folder", "tags"} + if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) { + t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields) + } + }) +} + +// MockClient implements the ResourceIndexClient interface for testing +type MockClient struct { + resource.ResourceIndexClient + + // Capture the last SearchRequest for assertions + LastSearchRequest *resource.ResourceSearchRequest +} + +func (m *MockClient) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) { + m.LastSearchRequest = in + + return &resource.ResourceSearchResponse{}, nil +} + +func (m *MockClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) { + return nil, nil +} + +func (m *MockClient) History(ctx context.Context, in *resource.HistoryRequest, opts ...grpc.CallOption) (*resource.HistoryResponse, error) { + return nil, nil +} diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index 3a42a83ead7..8435b86fede 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -15,7 +15,6 @@ import ( grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn/authnimpl" - "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cloudmigration" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" @@ -73,7 +72,7 @@ func ProvideBackgroundServiceRegistry( _ dashboardsnapshots.Service, _ serviceaccounts.Service, _ *guardian.Provider, _ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider, - _ *grpcserver.HealthService, _ authz.Client, _ *grpcserver.ReflectionService, + _ *grpcserver.HealthService, _ *grpcserver.ReflectionService, _ *ldapapi.Service, _ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI, _ ssosettings.Service, _ cloudmigration.Service, _ authnimpl.Registration, ) *BackgroundServiceRegistry { diff --git a/pkg/services/authz/client.go b/pkg/services/authz/client.go index ccb88d4a399..c3399811a00 100644 --- a/pkg/services/authz/client.go +++ b/pkg/services/authz/client.go @@ -2,6 +2,7 @@ package authz import ( "context" + "errors" "github.com/fullstorydev/grpchan" "github.com/fullstorydev/grpchan/inprocgrpc" @@ -27,56 +28,40 @@ import ( // `authzService` is hardcoded in authz-service const authzServiceAudience = "authzService" -type Client interface { - authzlib.AccessClient -} - // ProvideAuthZClient provides an AuthZ client and creates the AuthZ service. func ProvideAuthZClient( cfg *setting.Cfg, features featuremgmt.FeatureToggles, grpcServer grpcserver.Provider, tracer tracing.Tracer, db db.DB, -) (Client, error) { - if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) { - return nil, nil - } - +) (authzlib.AccessClient, error) { authCfg, err := ReadCfg(cfg) if err != nil { return nil, err } - var client Client + isRemoteServer := authCfg.mode == ModeCloud || authCfg.mode == ModeGRPC + if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) && isRemoteServer { + return nil, errors.New("authZGRPCServer feature toggle is required for cloud and grpc mode") + } // Register the server sql := legacysql.NewDatabaseProvider(db) server := rbac.NewService(sql, legacy.NewLegacySQLStores(sql), log.New("authz-grpc-server"), tracer) switch authCfg.mode { - case ModeInProc: - client, err = newInProcLegacyClient(server, tracer) - if err != nil { - return nil, err - } case ModeGRPC: - client, err = newGrpcLegacyClient(authCfg, tracer) - if err != nil { - return nil, err - } + return newGrpcLegacyClient(authCfg, tracer) case ModeCloud: - client, err = newCloudLegacyClient(authCfg, tracer) - if err != nil { - return nil, err - } + return newCloudLegacyClient(authCfg, tracer) + default: + return newInProcLegacyClient(server, tracer) } - - return client, err } // ProvideStandaloneAuthZClient provides a standalone AuthZ client, without registering the AuthZ service. // You need to provide a remote address in the configuration func ProvideStandaloneAuthZClient( cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer, -) (Client, error) { +) (authzlib.AccessClient, error) { if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) { return nil, nil } diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 8847aafde85..329efe73e0b 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -198,7 +198,6 @@ func (_m *FakeDashboardService) GetAllDashboards(ctx context.Context) ([]*Dashbo return r0, r1 } - func (_m *FakeDashboardService) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) { ret := _m.Called(ctx, orgID) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 99d064982e8..e9fa15309de 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -12,6 +12,7 @@ import ( "time" "github.com/google/uuid" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "golang.org/x/exp/slices" @@ -71,6 +72,16 @@ var ( tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards/service") ) +var ( + excludedFields = map[string]string{ + resource.SEARCH_FIELD_EXPLAIN: "", + resource.SEARCH_FIELD_SCORE: "", + resource.SEARCH_FIELD_TITLE: "", + resource.SEARCH_FIELD_FOLDER: "", + resource.SEARCH_FIELD_TAGS: "", + } +) + type DashboardServiceImpl struct { cfg *setting.Cfg log log.Logger @@ -1949,11 +1960,11 @@ func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alp explainIDX = i case resource.SEARCH_FIELD_SCORE: scoreIDX = i - case "title": + case resource.SEARCH_FIELD_TITLE: titleIDX = i - case "folder": + case resource.SEARCH_FIELD_FOLDER: folderIDX = i - case "tags": + case resource.SEARCH_FIELD_TAGS: tagsIDX = i } } @@ -1967,11 +1978,28 @@ func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alp } for i, row := range result.Results.Rows { + fields := &common.Unstructured{} + for colIndex, col := range result.Results.Columns { + if _, ok := excludedFields[col.Name]; ok { + val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex]) + if err != nil { + return nil, err + } + // Some of the dashboard fields come in as int32, but we need to convert them to int64 or else fields.Set() will panic + int32Val, ok := val.(int32) + if ok { + val = int64(int32Val) + } + fields.Set(col.Name, val) + } + } + hit := &v0alpha1.DashboardHit{ Resource: row.Key.Resource, // folders | dashboards Name: row.Key.Name, // The Grafana UID Title: string(row.Cells[titleIDX]), Folder: string(row.Cells[folderIDX]), + Field: fields, } if tagsIDX > 0 && row.Cells[tagsIDX] != nil { _ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags) diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 62e70c00cbe..a8768008f2b 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/storage/unified/search" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -417,9 +418,11 @@ func TestGetDashboard(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -636,9 +639,11 @@ func TestGetProvisionedDashboardData(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -729,9 +734,11 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -810,9 +817,11 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -917,9 +926,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -946,9 +957,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1039,9 +1052,11 @@ func TestUnprovisionDashboard(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1095,9 +1110,11 @@ func TestGetDashboardsByPluginID(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1303,9 +1320,11 @@ func TestDeleteDashboard(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1422,12 +1441,15 @@ func TestSearchDashboards(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "tags", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1521,9 +1543,11 @@ func TestGetDashboards(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1599,9 +1623,11 @@ func TestGetDashboardUIDByID(t *testing.T) { Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ @@ -1877,3 +1903,47 @@ func TestToUID(t *testing.T) { assert.Equal(t, "", result) }) } + +// regression test - parsing int32 values from search results was causing a panic +func TestParseResults(t *testing.T) { + resSearchResp := &resource.ResourceSearchResponse{ + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + { + Name: "title", + Type: resource.ResourceTableColumnDefinition_STRING, + }, + { + Name: "folder", + Type: resource.ResourceTableColumnDefinition_STRING, + }, + { + Name: search.DASHBOARD_ERRORS_LAST_1_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + }, + { + Name: search.DASHBOARD_LINK_COUNT, + Type: resource.ResourceTableColumnDefinition_INT32, + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "uid", + Resource: "dashboard", + }, + Cells: [][]byte{ + []byte("Dashboard 1"), + []byte("folder1"), + []byte("100"), + []byte("25"), + }, + }, + }, + }, + TotalHits: 1, + } + + _, err := ParseResults(resSearchResp, 0) + require.NoError(t, err) +} diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index 3aa863aaa45..d1f7eba8e3f 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -29,7 +29,7 @@ import ( const ( selectLibraryElementDTOWithMeta = ` SELECT DISTINCT - le.name, le.id, le.org_id, le.folder_id, le.uid, le.kind, le.type, le.description, le.model, le.created, le.created_by, le.updated, le.updated_by, le.version + le.name, le.id, le.org_id, le.folder_id, le.folder_uid, le.uid, le.kind, le.type, le.description, le.model, le.created, le.created_by, le.updated, le.updated_by, le.version , u1.login AS created_by_name , u1.email AS created_by_email , u2.login AS updated_by_name @@ -93,10 +93,7 @@ func syncFieldsWithModel(libraryElement *model.LibraryElement) error { func GetLibraryElement(dialect migrator.Dialect, session *db.Session, uid string, orgID int64) (model.LibraryElementWithMeta, error) { elements := make([]model.LibraryElementWithMeta, 0) sql := selectLibraryElementDTOWithMeta + - ", coalesce(dashboard.title, 'General') AS folder_name" + - ", coalesce(dashboard.uid, '') AS folder_uid" + getFromLibraryElementDTOWithMeta(dialect) + - " LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" + " WHERE le.uid=? AND le.org_id=?" sess := session.SQL(sql, uid, orgID) err := sess.Find(&elements) @@ -110,6 +107,15 @@ func GetLibraryElement(dialect migrator.Dialect, session *db.Session, uid string return model.LibraryElementWithMeta{}, fmt.Errorf("found %d elements, while expecting at most one", len(elements)) } + for _, el := range elements { + if el.FolderName == "" { + el.FolderName = dashboards.RootFolderName + } + if el.FolderUID == "" { + el.FolderUID = ac.GeneralFolderUID + } + } + return elements[0], nil } @@ -678,12 +684,12 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser i if err != nil { return err } + var libraryElementConnections []model.LibraryElementConnectionWithMeta builder := db.NewSqlBuilder(l.Cfg, l.features, l.SQLStore.GetDialect(), recursiveQueriesAreSupported) - builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email, dashboard.uid AS connection_uid") + builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email") builder.Write(" FROM " + model.LibraryElementConnectionTableName + " AS lec") builder.Write(" LEFT JOIN " + l.SQLStore.GetDialect().Quote("user") + " AS u1 ON lec.created_by = u1.id") - builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id") builder.Write(` WHERE lec.element_id=?`, element.ID) if signedInUser.GetOrgRole() != org.RoleAdmin { builder.WriteDashboardPermissionFilter(signedInUser, dashboardaccess.PERMISSION_VIEW, "") @@ -694,12 +700,11 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser i for _, connection := range libraryElementConnections { connections = append(connections, model.LibraryElementConnectionDTO{ - ID: connection.ID, - Kind: connection.Kind, - ElementID: connection.ElementID, - ConnectionID: connection.ConnectionID, - ConnectionUID: connection.ConnectionUID, - Created: connection.Created, + ID: connection.ID, + Kind: connection.Kind, + ElementID: connection.ElementID, + ConnectionID: connection.ConnectionID, + Created: connection.Created, CreatedBy: librarypanel.LibraryElementDTOMetaUser{ Id: connection.CreatedBy, Name: connection.CreatedByName, diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go index a07092269ad..721b9472599 100644 --- a/pkg/services/libraryelements/libraryelements_permissions_test.go +++ b/pkg/services/libraryelements/libraryelements_permissions_test.go @@ -116,11 +116,13 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) + result.Result.FolderUID = "general" result.Result.Meta.CreatedBy.Name = userInDbName result.Result.Meta.CreatedBy.AvatarUrl = userInDbAvatar result.Result.Meta.UpdatedBy.Name = userInDbName result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar result.Result.Meta.FolderName = "General" + result.Result.Meta.FolderUID = "general" sc.reqContext.SignedInUser.OrgRole = testCase.role resp = sc.service.getAllHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 51054a18a1b..c2f58a2029c 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -171,12 +171,11 @@ func TestGetLibraryPanelConnections(t *testing.T) { return model.LibraryElementConnectionsResponse{ Result: []model.LibraryElementConnectionDTO{ { - ID: sc.initialResult.Result.ID, - Kind: sc.initialResult.Result.Kind, - ElementID: 1, - ConnectionID: dashInDB.ID, - ConnectionUID: dashInDB.UID, - Created: res.Result[0].Created, + ID: sc.initialResult.Result.ID, + Kind: sc.initialResult.Result.Kind, + ElementID: 1, + ConnectionID: dashInDB.ID, + Created: res.Result[0].Created, CreatedBy: librarypanel.LibraryElementDTOMetaUser{ Id: 1, Name: userInDbName, diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index e1253821d5b..a71f67819d7 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -489,7 +489,8 @@ func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusRe newGroup := &apimodels.RuleGroup{ Name: groupKey.RuleGroup, // file is what Prometheus uses for provisioning, we replace it with namespace which is the folder in Grafana. - File: folderFullPath, + File: folderFullPath, + FolderUID: groupKey.NamespaceUID, } rulesTotals := make(map[string]int64, len(rules)) @@ -514,7 +515,9 @@ func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusRe } newRule := apimodels.Rule{ + UID: rule.UID, Name: rule.Title, + FolderUID: rule.NamespaceUID, Labels: apimodels.LabelsFromMap(rule.GetLabels(labelOptions...)), Health: status.Health, LastError: errorOrEmpty(status.LastError), diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 6f699f087e8..120755d2252 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -314,9 +314,12 @@ func TestRouteGetRuleStatuses(t *testing.T) { "groups": [{ "name": "rule-group", "file": "%s", + "folderUid": "namespaceUID", "rules": [{ "state": "inactive", "name": "AlwaysFiring", + "folderUid": "namespaceUID", + "uid": "RuleUID", "query": "vector(1)", "alerts": [{ "labels": { @@ -377,10 +380,13 @@ func TestRouteGetRuleStatuses(t *testing.T) { "groups": [{ "name": "rule-group", "file": "%s", + "folderUid": "namespaceUID", "rules": [{ "state": "inactive", "name": "AlwaysFiring", "query": "vector(1)", + "folderUid": "namespaceUID", + "uid": "RuleUID", "alerts": [{ "labels": { "job": "prometheus", @@ -439,10 +445,13 @@ func TestRouteGetRuleStatuses(t *testing.T) { "groups": [{ "name": "rule-group", "file": "%s", + "folderUid": "namespaceUID", "rules": [{ "state": "inactive", "name": "AlwaysFiring", "query": "vector(1) | vector(1)", + "folderUid": "namespaceUID", + "uid": "RuleUID", "alerts": [{ "labels": { "job": "prometheus" diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index cdbe810597b..935cf1bc8d3 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -171,7 +171,7 @@ func (srv *ProvisioningSrv) RoutePostContactPoint(c *contextmodel.ReqContext, cp return ErrResp(http.StatusBadRequest, err, "") } if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "", err) } return response.JSON(http.StatusAccepted, contactPoint) } diff --git a/pkg/services/ngalert/api/tooling/definitions/prom.go b/pkg/services/ngalert/api/tooling/definitions/prom.go index abb61eddbc7..dc70ed29d56 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom.go @@ -89,6 +89,8 @@ type RuleGroup struct { Name string `json:"name"` // required: true File string `json:"file"` + // required: true + FolderUID string `json:"folderUid"` // In order to preserve rule ordering, while exposing type (alerting or recording) // specific properties, both alerting and recording rules are exposed in the // same array. @@ -165,9 +167,13 @@ type AlertingRule struct { // adapted from cortex // swagger:model type Rule struct { + // required: true + UID string `json:"uid"` // required: true Name string `json:"name"` // required: true + FolderUID string `json:"folderUid"` + // required: true Query string `json:"query"` Labels promlabels.Labels `json:"labels,omitempty"` // required: true diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 9bde1c86b17..d5fe345ca3d 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -198,10 +198,7 @@ func (ecp *ContactPointService) CreateContactPoint( // check if uid is already used in receiver for _, rec := range receiver.PostableGrafanaReceivers.GrafanaManagedReceivers { if grafanaReceiver.UID == rec.UID { - return apimodels.EmbeddedContactPoint{}, fmt.Errorf( - "receiver configuration with UID '%s' already exist in contact point '%s'. Please use unique identifiers for receivers across all contact points", - rec.UID, - rec.Name) + return apimodels.EmbeddedContactPoint{}, MakeErrContactPointUidExists(rec.UID, rec.Name) } } if receiver.Name == contactPoint.Name { diff --git a/pkg/services/ngalert/provisioning/errors.go b/pkg/services/ngalert/provisioning/errors.go index f13d2315df5..3d8b4c85608 100644 --- a/pkg/services/ngalert/provisioning/errors.go +++ b/pkg/services/ngalert/provisioning/errors.go @@ -28,6 +28,10 @@ var ( ErrContactPointReferenced = errutil.Conflict("alerting.notifications.contact-points.referenced", errutil.WithPublicMessage("Contact point is currently referenced by a notification policy.")) ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules.")) + contactPointUidExists = "Receiver configuration with UID '{{ .Public.UID }}' already exists in contact point '{{ .Public.Name }}'. Please use unique identifiers for receivers across all contact points." + ErrContactPointUidExists = errutil.Conflict("alerting.notifications.contact-points.uidInUse").MustTemplate( + contactPointUidExists, errutil.WithPublic(contactPointUidExists), + ) ErrRouteInvalidFormat = errutil.BadRequest("alerting.notifications.routes.invalidFormat").MustTemplate( "Invalid format of the submitted route.", @@ -104,3 +108,12 @@ func MakeErrRouteInvalidFormat(err error) error { Error: err, }) } + +func MakeErrContactPointUidExists(uid, name string) error { + return ErrContactPointUidExists.Build(errutil.TemplateData{ + Public: map[string]any{ + "UID": uid, + "Name": name, + }, + }) +} diff --git a/pkg/services/ngalert/schedule/alert_rule.go b/pkg/services/ngalert/schedule/alert_rule.go index 2eb46a1e089..2fe691ab30f 100644 --- a/pkg/services/ngalert/schedule/alert_rule.go +++ b/pkg/services/ngalert/schedule/alert_rule.go @@ -196,8 +196,7 @@ func (a *alertRule) Status() ngmodels.RuleStatus { func (a *alertRule) Eval(eval *Evaluation) (bool, *Evaluation) { if a.key.AlertRuleKey != eval.rule.GetKey() { // Make sure that rule has the same key. This should not happen - a.logger.Error("Invalid rule sent for evaluating. Skipping", "ruleKeyToEvaluate", eval.rule.GetKey().String()) - return false, eval + panic(fmt.Sprintf("Invalid rule sent for evaluating. Expected rule key %s, got %s", a.key.AlertRuleKey, eval.rule.GetKey())) } // read the channel in unblocking manner to make sure that there is no concurrent send operation. var droppedMsg *Evaluation diff --git a/pkg/services/ngalert/schedule/alert_rule_test.go b/pkg/services/ngalert/schedule/alert_rule_test.go index 7433d62ec2f..5b0187d3e8d 100644 --- a/pkg/services/ngalert/schedule/alert_rule_test.go +++ b/pkg/services/ngalert/schedule/alert_rule_test.go @@ -220,6 +220,9 @@ func TestAlertRule(t *testing.T) { } }() + rule := gen.GenerateRef() + rule.UID = r.key.UID + rule.OrgID = r.key.OrgID for i := 0; i < 10; i++ { wg.Add(1) go func() { @@ -234,7 +237,7 @@ func TestAlertRule(t *testing.T) { case 2: r.Eval(&Evaluation{ scheduledAt: time.Now(), - rule: gen.GenerateRef(), + rule: rule, folderTitle: util.GenerateShortUID(), }) case 3: diff --git a/pkg/services/ngalert/sender/sender.go b/pkg/services/ngalert/sender/sender.go index 8f4cd77df47..41f8cd2a2a7 100644 --- a/pkg/services/ngalert/sender/sender.go +++ b/pkg/services/ngalert/sender/sender.go @@ -114,7 +114,9 @@ func NewExternalAlertmanagerSender(l log.Logger, reg prometheus.Registerer, opts s.logger.Error("failed to register service discovery metrics", "error", err) return nil, err } - s.sdManager = discovery.NewManager(sdCtx, s.logger, prometheus.NewRegistry(), sdMetrics) + // Convert s.Logger to slog.Logger using the adapter + slogLogger := toSlogLogger(s.logger) + s.sdManager = discovery.NewManager(sdCtx, slogLogger, prometheus.NewRegistry(), sdMetrics) if s.sdManager == nil { return nil, errors.New("failed to create new discovery manager") diff --git a/pkg/services/ngalert/sender/slog_adapter.go b/pkg/services/ngalert/sender/slog_adapter.go new file mode 100644 index 00000000000..4263da8d32f --- /dev/null +++ b/pkg/services/ngalert/sender/slog_adapter.go @@ -0,0 +1,82 @@ +package sender + +import ( + "context" + "log/slog" + + "github.com/grafana/grafana/pkg/infra/log" +) + +// InfraLoggerHandler adapts infra's log.Logger to an slog.Handler +type InfraLoggerHandler struct { + infraLogger log.Logger +} + +type SlogAdapter struct { + logger *slog.Logger +} + +// Log implements the Log method to satisfy the log.Logger interface. +func (s *SlogAdapter) Log(keyvals ...interface{}) error { + msg := "" + args := []interface{}{} + + // Extract message and attributes (customize as needed). + if len(keyvals) > 0 { + msg, _ = keyvals[0].(string) + if len(keyvals) > 1 { + args = keyvals[1:] + } + } + + // Delegate to slog.Logger + s.logger.Info(msg, args...) + return nil +} + +// Enabled determines if the log level is enabled +func (h *InfraLoggerHandler) Enabled(_ context.Context, level slog.Level) bool { + // Assume all levels are enabled; customize this based on infra's log.Logger + return true +} + +// Handle processes a log record and routes it to infra's log.Logger +func (h *InfraLoggerHandler) Handle(_ context.Context, record slog.Record) error { + // Build the log message + msg := record.Message + args := []interface{}{} + record.Attrs(func(a slog.Attr) bool { + args = append(args, a.Key, a.Value.Any()) + return true + }) + + // Route the log message to Infra's logger + switch record.Level { + case slog.LevelDebug: + h.infraLogger.Debug(msg, args...) + case slog.LevelInfo: + h.infraLogger.Info(msg, args...) + case slog.LevelWarn: + h.infraLogger.Warn(msg, args...) + case slog.LevelError: + h.infraLogger.Error(msg, args...) + } + + return nil +} + +// WithAttrs adds attributes to the logger context +func (h *InfraLoggerHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return h // Context attributes are ignored; implement if needed +} + +// WithGroup groups log records; not used here +func (h *InfraLoggerHandler) WithGroup(name string) slog.Handler { + return h // Grouping is ignored; implement if needed +} + +// toSlogLogger is a function to create *slog.Logger from Infra's log.Logger +func toSlogLogger(infraLogger log.Logger) *slog.Logger { + handler := &InfraLoggerHandler{infraLogger: infraLogger} + return slog.New(handler) +} diff --git a/pkg/services/org/orgimpl/store.go b/pkg/services/org/orgimpl/store.go index d56f941e34c..5d247ffeb9b 100644 --- a/pkg/services/org/orgimpl/store.go +++ b/pkg/services/org/orgimpl/store.go @@ -221,6 +221,8 @@ func (ss *sqlStore) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error deletes := []string{ "DELETE FROM star WHERE org_id = ?", + "DELETE FROM playlist_item WHERE playlist_id IN (SELECT id FROM playlist WHERE org_id = ?)", + "DELETE FROM playlist WHERE org_id = ?", "DELETE FROM dashboard_tag WHERE org_id = ?", "DELETE FROM api_key WHERE org_id = ?", "DELETE FROM data_source WHERE org_id = ?", diff --git a/pkg/services/org/orgimpl/store_test.go b/pkg/services/org/orgimpl/store_test.go index a4f839d6842..abc4e217418 100644 --- a/pkg/services/org/orgimpl/store_test.go +++ b/pkg/services/org/orgimpl/store_test.go @@ -15,6 +15,8 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/playlist" + "github.com/grafana/grafana/pkg/services/playlist/playlistimpl" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "github.com/grafana/grafana/pkg/services/searchusers/sortopts" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -115,11 +117,32 @@ func TestIntegrationOrgDataAccess(t *testing.T) { t.Run("Removing org", func(t *testing.T) { // make sure ac2 has no org ac2 := &org.Org{ID: 22, Name: "ac2", Version: 1, Created: time.Now(), Updated: time.Now()} - _, err := orgStore.Insert(context.Background(), ac2) + orgId, err := orgStore.Insert(context.Background(), ac2) require.NoError(t, err) + + // Create some org-scoped items like playlists, so we can assert that they + // are cleaned up on delete. + plItems := []playlist.PlaylistItem{ + { + Type: "foo", + }, + } + plStore := playlistimpl.ProvideService(ss, tracing.InitializeTracerForTest()) + plCreateCommand := playlist.CreatePlaylistCommand{ + OrgId: orgId, + Name: "test", + Items: plItems, + } + pl, err := plStore.Create(context.Background(), &plCreateCommand) + require.NoError(t, err) + err = orgStore.Delete(context.Background(), &org.DeleteOrgCommand{ID: ac2.ID}) require.NoError(t, err) + plDTO, err := plStore.Get(context.Background(), &playlist.GetPlaylistByUidQuery{OrgId: pl.OrgId, UID: pl.UID}) + require.Error(t, err) + require.Nil(t, plDTO) + // TODO: this part of the test will be added when we move RemoveOrgUser to org store // "Removing user from org should delete user completely if in no other org" // // remove ac2 user from ac1 org diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index cf7455003b8..c512b9bfe00 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -115,6 +115,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlmiddlecote/sqlstats v1.0.2 // indirect + github.com/docker/docker v27.4.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v1.3.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index e9633ef8d2d..73a81896f45 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -300,8 +300,8 @@ github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxW github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= +github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go index 558a0b05187..dc4f8fa57c2 100644 --- a/pkg/storage/unified/client.go +++ b/pkg/storage/unified/client.go @@ -12,12 +12,12 @@ import ( "google.golang.org/grpc/credentials/insecure" authnlib "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/authz" infraDB "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/apiserver/options" "github.com/grafana/grafana/pkg/services/authn/grpcutils" - "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql" @@ -35,7 +35,7 @@ func ProvideUnifiedStorageClient( db infraDB.DB, tracer tracing.Tracer, reg prometheus.Registerer, - authzc authz.Client, + authzc authz.AccessClient, docs resource.DocumentBuilderSupplier, ) (resource.ResourceClient, error) { // See: apiserver.ApplyGrafanaConfig(cfg, features, o) @@ -62,7 +62,7 @@ func newClient(opts options.StorageOptions, db infraDB.DB, tracer tracing.Tracer, reg prometheus.Registerer, - authzc authz.Client, + authzc authz.AccessClient, docs resource.DocumentBuilderSupplier, ) (resource.ResourceClient, error) { ctx := context.Background() diff --git a/pkg/storage/unified/resource/bleve_index_metrics.go b/pkg/storage/unified/resource/bleve_index_metrics.go index c6a96e5dd6d..caa0499bcb8 100644 --- a/pkg/storage/unified/resource/bleve_index_metrics.go +++ b/pkg/storage/unified/resource/bleve_index_metrics.go @@ -72,7 +72,7 @@ func NewIndexMetrics(indexDir string, searchBackend SearchBackend) *BleveIndexMe Namespace: "index_server", Name: "index_tenants", Help: "Number of tenants in the index", - }, []string{"namespace", "index_storage"}), // index_storage is either "file" or "memory" + }, []string{"index_storage"}), // index_storage is either "file" or "memory" } }) diff --git a/pkg/storage/unified/resource/client.go b/pkg/storage/unified/resource/client.go index f7c4e4c770b..22f1325fb46 100644 --- a/pkg/storage/unified/resource/client.go +++ b/pkg/storage/unified/resource/client.go @@ -101,6 +101,7 @@ func NewGRPCResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn) (Resour return &resourceClient{ ResourceStoreClient: NewResourceStoreClient(cc), ResourceIndexClient: NewResourceIndexClient(cc), + BlobStoreClient: NewBlobStoreClient(cc), DiagnosticsClient: NewDiagnosticsClient(cc), }, nil } @@ -125,6 +126,7 @@ func NewCloudResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn, cfg au return &resourceClient{ ResourceStoreClient: NewResourceStoreClient(cc), ResourceIndexClient: NewResourceIndexClient(cc), + BlobStoreClient: NewBlobStoreClient(cc), DiagnosticsClient: NewDiagnosticsClient(cc), }, nil } diff --git a/pkg/storage/unified/resource/document.go b/pkg/storage/unified/resource/document.go index 32116bb207d..ce5ecdf0d16 100644 --- a/pkg/storage/unified/resource/document.go +++ b/pkg/storage/unified/resource/document.go @@ -237,6 +237,8 @@ func (x *searchableDocumentFields) Fields() []string { } func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefinition { + name = strings.TrimPrefix(name, "fields.") + f, ok := x.fields[name] if ok { return f.def diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 657df639931..76b314c46eb 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -519,8 +519,9 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index cc16bf64814..bfefdf3e2df 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -372,7 +372,7 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour } check := authz.CheckRequest{ - Verb: "create", + Verb: utils.VerbCreate, Group: key.Group, Resource: key.Resource, Namespace: key.Namespace, @@ -386,7 +386,7 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour if oldValue == nil { event.Type = WatchEvent_ADDED } else { - check.Verb = "update" + check.Verb = utils.VerbUpdate temp := &unstructured.Unstructured{} err = temp.UnmarshalJSON(oldValue) @@ -437,8 +437,12 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour return nil, err } + // We only set name for update checks + if check.Verb == utils.VerbUpdate { + check.Name = key.Name + } + check.Folder = obj.GetFolder() - check.Name = key.Name a, err := s.access.Check(ctx, user, check) if err != nil { return nil, AsErrorResult(err) diff --git a/pkg/storage/unified/resource/table.go b/pkg/storage/unified/resource/table.go index a58cf2dd13a..99c737ce3f7 100644 --- a/pkg/storage/unified/resource/table.go +++ b/pkg/storage/unified/resource/table.go @@ -182,6 +182,19 @@ type resourceTableColumn struct { OpenAPIFormat string } +// helper to decode a cell value +func DecodeCell(columnDef *ResourceTableColumnDefinition, index int, cellVal []byte) (any, error) { + col, err := newResourceTableColumn(columnDef, index) + if err != nil { + return nil, err + } + res, err := col.Decode(cellVal) + if err != nil { + return nil, err + } + return res, nil +} + // nolint:gocyclo func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*resourceTableColumn, error) { col := &resourceTableColumn{def: def, index: index} diff --git a/pkg/storage/unified/resource/table_test.go b/pkg/storage/unified/resource/table_test.go index fcefe136749..9e37a902d1c 100644 --- a/pkg/storage/unified/resource/table_test.go +++ b/pkg/storage/unified/resource/table_test.go @@ -1,6 +1,8 @@ package resource import ( + "bytes" + "encoding/binary" "fmt" "path/filepath" "strings" @@ -312,3 +314,15 @@ func TestColumnEncoding(t *testing.T) { require.Empty(t, missingArrays, "missing array tests for types") }) } + +func TestDecodeCell(t *testing.T) { + colDef := &ResourceTableColumnDefinition{Type: ResourceTableColumnDefinition_INT64} + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, int64(123)) + require.NoError(t, err) + + res, err := DecodeCell(colDef, 0, buf.Bytes()) + + require.NoError(t, err) + require.Equal(t, int64(123), res) +} diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index edbe93b7312..d0e0c376872 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -7,6 +7,7 @@ import ( "log/slog" "os" "path/filepath" + "slices" "strings" "sync" "time" @@ -18,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/selection" "github.com/grafana/authlib/authz" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -138,10 +140,10 @@ func (b *bleveBackend) BuildIndex(ctx context.Context, index, err = bleve.New(dir, mapper) } - resource.IndexMetrics.IndexTenants.WithLabelValues(key.Namespace, "file").Inc() + resource.IndexMetrics.IndexTenants.WithLabelValues("file").Inc() } else { index, err = bleve.NewMemOnly(mapper) - resource.IndexMetrics.IndexTenants.WithLabelValues(key.Namespace, "memory").Inc() + resource.IndexMetrics.IndexTenants.WithLabelValues("memory").Inc() } if err != nil { return nil, err @@ -488,8 +490,18 @@ func toBleveSearchRequest(req *resource.ResourceSearchRequest, access authz.Acce for _, f := range req.Facet { facets[f.Field] = bleve.NewFacetRequest(f.Field, int(f.Limit)) } + + // Convert resource-specific fields to bleve fields (just considers dashboard fields for now) + fields := make([]string, 0, len(req.Fields)) + for _, f := range req.Fields { + if slices.Contains(DashboardFields(), f) { + f = "fields." + f + } + fields = append(fields, f) + } + searchrequest := &bleve.SearchRequest{ - Fields: req.Fields, + Fields: fields, Size: int(req.Limit), From: int(req.Offset), Explain: req.Explain, @@ -719,7 +731,14 @@ func (b *bleveIndex) hitsToTable(selectFields []string, hits search.DocumentMatc row.Cells[i], err = json.Marshal(match.Expl) } default: - v := match.Fields[f.Name] + fieldName := f.Name + // since the bleve index fields mix common and resource-specific fields, it is possible a conflict can happen + // if a specific field is named the same as a common field + v := match.Fields[fieldName] + // fields that are specific to the resource get stored as fields., so we need to check for that + if v == nil { + v = match.Fields["fields."+fieldName] + } if v != nil { // Encode the value to protobuf row.Cells[i], err = encoders[i](v) diff --git a/pkg/storage/unified/search/bleve_mappings.go b/pkg/storage/unified/search/bleve_mappings.go index f9cdafc41d4..a1ec7d9fd6b 100644 --- a/pkg/storage/unified/search/bleve_mappings.go +++ b/pkg/storage/unified/search/bleve_mappings.go @@ -103,5 +103,8 @@ func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentM labelMapper := bleve.NewDocumentMapping() mapper.AddSubDocumentMapping(resource.SEARCH_FIELD_LABELS, labelMapper) + fieldMapper := bleve.NewDocumentMapping() + mapper.AddSubDocumentMapping("fields", fieldMapper) + return mapper } diff --git a/pkg/storage/unified/search/bleve_test.go b/pkg/storage/unified/search/bleve_test.go index c9b650e7668..8f8f3bb902d 100644 --- a/pkg/storage/unified/search/bleve_test.go +++ b/pkg/storage/unified/search/bleve_test.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" "time" @@ -76,9 +77,9 @@ func TestBleveBackend(t *testing.T) { TitleSort: "aaa (dash)", Folder: "xxx", Fields: map[string]any{ - DASHBOARD_LEGACY_ID: 12, - DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"}, - DASHBOARD_ERRORS_TODAY: 25, + DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"}, + DASHBOARD_ERRORS_TODAY: 25, + DASHBOARD_VIEWS_LAST_1_DAYS: 50, }, Labels: map[string]string{ utils.LabelKeyDeprecatedInternalID: "10", // nolint:staticcheck @@ -104,9 +105,9 @@ func TestBleveBackend(t *testing.T) { TitleSort: "bbb (dash)", Folder: "xxx", Fields: map[string]any{ - DASHBOARD_LEGACY_ID: 12, - DASHBOARD_PANEL_TYPES: []string{"timeseries"}, - DASHBOARD_ERRORS_TODAY: 40, + DASHBOARD_PANEL_TYPES: []string{"timeseries"}, + DASHBOARD_ERRORS_TODAY: 40, + DASHBOARD_VIEWS_LAST_1_DAYS: 100, }, Tags: []string{"aa"}, Labels: map[string]string{ @@ -136,10 +137,8 @@ func TestBleveBackend(t *testing.T) { Name: "repo2", Path: "path/in/repo2.yaml", }, - Fields: map[string]any{ - DASHBOARD_LEGACY_ID: 12, - }, - Tags: []string{"aa"}, + Fields: map[string]any{}, + Tags: []string{"aa"}, Labels: map[string]string{ "region": "west", }, @@ -217,6 +216,27 @@ func TestBleveBackend(t *testing.T) { rsp.Results.Rows[1].Key.Name, }) + // can get sprinkles fields + rsp, err = index.Search(ctx, nil, &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{ + Key: key, + }, + Limit: 100000, + Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"}, + SortBy: []*resource.ResourceSearchRequest_Sort{ + {Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true}, + }, + }, nil) + require.NoError(t, err) + require.Equal(t, 2, len(rsp.Results.Columns)) + require.Equal(t, DASHBOARD_ERRORS_TODAY, rsp.Results.Columns[0].Name) + require.Equal(t, DASHBOARD_VIEWS_LAST_1_DAYS, rsp.Results.Columns[1].Name) + + // sorted descending so should start with highest dashboard_views_last_1_days (100) + val, err := resource.DecodeCell(rsp.Results.Columns[1], 0, rsp.Results.Rows[0].Cells[1]) + require.NoError(t, err) + require.Equal(t, int64(100), val) + // Now look for repositories found, err := index.ListRepositoryObjects(ctx, &resource.ListRepositoryObjectsRequest{ Name: "repo-1", @@ -408,6 +428,29 @@ func TestBleveBackend(t *testing.T) { }) } +func TestToBleveSearchRequest(t *testing.T) { + t.Run("will prepend 'fields.' to all dashboard fields", func(t *testing.T) { + fields := []string{"title", "name", "folder"} + fields = append(fields, DashboardFields()...) + resReq := &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{}, + Fields: fields, + } + bleveReq, err := toBleveSearchRequest(resReq, nil) + if err != nil { + t.Fatalf("error creating bleve search request: %v", err) + } + + require.Equal(t, len(fields), len(bleveReq.Fields)) + for _, field := range DashboardFields() { + require.True(t, slices.Contains(bleveReq.Fields, "fields."+field)) + } + require.Contains(t, bleveReq.Fields, "title") + require.Contains(t, bleveReq.Fields, "name") + require.Contains(t, bleveReq.Fields, "folder") + }) +} + func asTimePointer(milli int64) *time.Time { if milli > 0 { t := time.UnixMilli(milli) diff --git a/pkg/storage/unified/search/dashboard.go b/pkg/storage/unified/search/dashboard.go index d4cd334ac04..b96cbf4ae0e 100644 --- a/pkg/storage/unified/search/dashboard.go +++ b/pkg/storage/unified/search/dashboard.go @@ -69,6 +69,126 @@ func DashboardBuilder(namespaced resource.NamespacedDocumentSupplier) (resource. Filterable: true, }, }, + { + Name: DASHBOARD_ERRORS_TODAY, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of errors that occurred today", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_ERRORS_LAST_1_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of errors that occurred in the last 1 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_ERRORS_LAST_7_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of errors that occurred in the last 7 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_ERRORS_LAST_30_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of errors that occurred in the last 30 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_ERRORS_TOTAL, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Total number of errors", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_QUERIES_TODAY, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of queries that occurred today", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_QUERIES_LAST_1_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of queries that occurred in the last 1 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_QUERIES_LAST_7_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of queries that occurred in the last 7 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_QUERIES_LAST_30_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of queries that occurred in the last 30 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_QUERIES_TOTAL, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Total number of queries", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_VIEWS_TODAY, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of views that occurred today", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_VIEWS_LAST_1_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of views that occurred in the last 1 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_VIEWS_LAST_7_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of views that occurred in the last 7 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_VIEWS_LAST_30_DAYS, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Number of views that occurred in the last 30 days", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + { + Name: DASHBOARD_VIEWS_TOTAL, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Total number of views", + Properties: &resource.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, }) if namespaced == nil { namespaced = func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { @@ -215,3 +335,36 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou return doc, nil } + +func DashboardFields() []string { + baseFields := []string{ + DASHBOARD_LEGACY_ID, + DASHBOARD_SCHEMA_VERSION, + DASHBOARD_LINK_COUNT, + DASHBOARD_PANEL_TYPES, + DASHBOARD_DS_TYPES, + DASHBOARD_TRANSFORMATIONS, + } + + return append(baseFields, UsageInsightsFields()...) +} + +func UsageInsightsFields() []string { + return []string{ + DASHBOARD_VIEWS_LAST_1_DAYS, + DASHBOARD_VIEWS_LAST_7_DAYS, + DASHBOARD_VIEWS_LAST_30_DAYS, + DASHBOARD_VIEWS_TODAY, + DASHBOARD_VIEWS_TOTAL, + DASHBOARD_QUERIES_LAST_1_DAYS, + DASHBOARD_QUERIES_LAST_7_DAYS, + DASHBOARD_QUERIES_LAST_30_DAYS, + DASHBOARD_QUERIES_TODAY, + DASHBOARD_QUERIES_TOTAL, + DASHBOARD_ERRORS_LAST_1_DAYS, + DASHBOARD_ERRORS_LAST_7_DAYS, + DASHBOARD_ERRORS_LAST_30_DAYS, + DASHBOARD_ERRORS_TODAY, + DASHBOARD_ERRORS_TOTAL, + } +} diff --git a/pkg/storage/unified/search/testdata/manual-dashboard.json b/pkg/storage/unified/search/testdata/manual-dashboard.json index 5df8ab5def2..c8375063dbc 100644 --- a/pkg/storage/unified/search/testdata/manual-dashboard.json +++ b/pkg/storage/unified/search/testdata/manual-dashboard.json @@ -63,6 +63,111 @@ "format": "", "description": "How many links appear on the page", "priority": 0 + }, + { + "name": "errors_today", + "type": "number", + "format": "int64", + "description": "Number of errors that occurred today", + "priority": 0 + }, + { + "name": "errors_last_1_days", + "type": "number", + "format": "int64", + "description": "Number of errors that occurred in the last 1 days", + "priority": 0 + }, + { + "name": "errors_last_7_days", + "type": "number", + "format": "int64", + "description": "Number of errors that occurred in the last 7 days", + "priority": 0 + }, + { + "name": "errors_last_30_days", + "type": "number", + "format": "int64", + "description": "Number of errors that occurred in the last 30 days", + "priority": 0 + }, + { + "name": "errors_total", + "type": "number", + "format": "int64", + "description": "Total number of errors", + "priority": 0 + }, + { + "name": "queries_today", + "type": "number", + "format": "int64", + "description": "Number of queries that occurred today", + "priority": 0 + }, + { + "name": "queries_last_1_days", + "type": "number", + "format": "int64", + "description": "Number of queries that occurred in the last 1 days", + "priority": 0 + }, + { + "name": "queries_last_7_days", + "type": "number", + "format": "int64", + "description": "Number of queries that occurred in the last 7 days", + "priority": 0 + }, + { + "name": "queries_last_30_days", + "type": "number", + "format": "int64", + "description": "Number of queries that occurred in the last 30 days", + "priority": 0 + }, + { + "name": "queries_total", + "type": "number", + "format": "int64", + "description": "Total number of queries", + "priority": 0 + }, + { + "name": "views_today", + "type": "number", + "format": "int64", + "description": "Number of views that occurred today", + "priority": 0 + }, + { + "name": "views_last_1_days", + "type": "number", + "format": "int64", + "description": "Number of views that occurred in the last 1 days", + "priority": 0 + }, + { + "name": "views_last_7_days", + "type": "number", + "format": "int64", + "description": "Number of views that occurred in the last 7 days", + "priority": 0 + }, + { + "name": "views_last_30_days", + "type": "number", + "format": "int64", + "description": "Number of views that occurred in the last 30 days", + "priority": 0 + }, + { + "name": "views_total", + "type": "number", + "format": "int64", + "description": "Total number of views", + "priority": 0 } ], "rows": [ @@ -78,6 +183,21 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, null ], "object": { @@ -102,6 +222,23 @@ null, null, null, + [ + "timeseries" + ], + 40, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 100, + null, + null, null ], "object": { @@ -127,6 +264,24 @@ null, null, null, + [ + "timeseries", + "table" + ], + 25, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 50, + null, + null, null ], "object": { diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 3e0a93d966f..0269e395961 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -7,13 +7,13 @@ import ( "path/filepath" "strings" + "github.com/grafana/authlib/authz" "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/storage/unified/search" infraDB "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -23,7 +23,7 @@ import ( // Creates a new ResourceServer func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, docs resource.DocumentBuilderSupplier, - tracer tracing.Tracer, reg prometheus.Registerer, ac authz.Client) (resource.ResourceServer, error) { + tracer tracing.Tracer, reg prometheus.Registerer, ac authz.AccessClient) (resource.ResourceServer, error) { apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver") opts := resource.ResourceServerOptions{ Tracer: tracer, diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go index bfae05b1538..468b42f6049 100644 --- a/pkg/tests/api/alerting/api_prometheus_test.go +++ b/pkg/tests/api/alerting/api_prometheus_test.go @@ -27,6 +27,9 @@ import ( "github.com/grafana/grafana/pkg/tests/testinfra" ) +// Declare respModel at the function level +var respModel apimodels.UpdateRuleGroupResponse + func TestIntegrationPrometheusRules(t *testing.T) { testinfra.SQLiteIntegrationTest(t) @@ -157,7 +160,6 @@ func TestIntegrationPrometheusRules(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusAccepted, resp.StatusCode) - var respModel apimodels.UpdateRuleGroupResponse require.NoError(t, json.Unmarshal(b, &respModel)) require.Len(t, respModel.Created, len(rules.Rules)) } @@ -235,18 +237,21 @@ func TestIntegrationPrometheusRules(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) - require.JSONEq(t, ` + require.JSONEq(t, fmt.Sprintf(` { "status": "success", "data": { "groups": [{ "name": "arulegroup", "file": "default", + "folderUid": "default", "rules": [{ "state": "inactive", "name": "AlwaysFiring", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "duration": 10, + "folderUid": "default", + "uid": "%s", "annotations": { "annotation1": "val1" }, @@ -261,6 +266,8 @@ func TestIntegrationPrometheusRules(t *testing.T) { "state": "inactive", "name": "AlwaysFiringButSilenced", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", + "folderUid": "default", + "uid": "%s", "health": "ok", "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", @@ -277,7 +284,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "inactive": 2 } } -}`, string(b)) +}`, respModel.Created[0], respModel.Created[1]), string(b)) } { @@ -293,18 +300,21 @@ func TestIntegrationPrometheusRules(t *testing.T) { b, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) - require.JSONEq(t, ` + require.JSONEq(t, fmt.Sprintf(` { "status": "success", "data": { "groups": [{ "name": "arulegroup", "file": "default", + "folderUid": "default", "rules": [{ "state": "inactive", "name": "AlwaysFiring", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "duration": 10, + "folderUid": "default", + "uid": "%s", "annotations": { "annotation1": "val1" }, @@ -319,6 +329,8 @@ func TestIntegrationPrometheusRules(t *testing.T) { "state": "inactive", "name": "AlwaysFiringButSilenced", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", + "folderUid": "default", + "uid": "%s", "health": "ok", "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", @@ -335,7 +347,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "inactive": 2 } } -}`, string(b)) +}`, respModel.Created[0], respModel.Created[1]), string(b)) return true }, 18*time.Second, 2*time.Second) } @@ -441,7 +453,6 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusAccepted, resp.StatusCode) - var respModel apimodels.UpdateRuleGroupResponse require.NoError(t, json.Unmarshal(b, &respModel)) require.Len(t, respModel.Created, len(rules.Rules)) } @@ -453,9 +464,12 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "groups": [{ "name": "anotherrulegroup", "file": "default", + "folderUid": "default", "rules": [{ "state": "inactive", "name": "AlwaysFiring", + "uid": "%s", + "folderUid": "default", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "duration": 10, "annotations": { @@ -469,6 +483,8 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { }, { "state": "inactive", "name": "AlwaysFiringButSilenced", + "uid": "%s", + "folderUid": "default", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "health": "ok", "type": "alerting", @@ -486,7 +502,7 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "inactive": 2 } } -}`, dashboardUID) +}`, respModel.Created[0], dashboardUID, respModel.Created[1]) expectedFilteredByJSON := fmt.Sprintf(` { "status": "success", @@ -494,9 +510,12 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "groups": [{ "name": "anotherrulegroup", "file": "default", + "folderUid": "default", "rules": [{ "state": "inactive", "name": "AlwaysFiring", + "uid": "%s", + "folderUid": "default", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "duration": 10, "annotations": { @@ -519,7 +538,7 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "inactive": 1 } } -}`, dashboardUID) +}`, respModel.Created[0], dashboardUID) expectedNoneJSON := ` { "status": "success", diff --git a/pkg/tests/apis/helper.go b/pkg/tests/apis/helper.go index a175744b6a2..2dfe8018e8a 100644 --- a/pkg/tests/apis/helper.go +++ b/pkg/tests/apis/helper.go @@ -487,7 +487,14 @@ func (c *K8sTestHelper) LoadYAMLOrJSON(body string) *unstructured.Unstructured { func (c *K8sTestHelper) createTestUsers(orgName string) OrgUsers { c.t.Helper() users := OrgUsers{ - Admin: c.CreateUser("admin", orgName, org.RoleAdmin, nil), + Admin: c.CreateUser("admin", orgName, org.RoleAdmin, []resourcepermissions.SetResourcePermissionCommand{ + { + Actions: []string{"dashboards:read", "dashboards:write", "dashboards:create", "dashboards:delete"}, + Resource: "dashboards", + ResourceAttribute: "uid", + ResourceID: "*", + }, + }), Editor: c.CreateUser("editor", orgName, org.RoleEditor, nil), Viewer: c.CreateUser("viewer", orgName, org.RoleViewer, nil), } diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 62300735167..e5d1646a67b 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -461,6 +461,13 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { _, err = grafanaComSection.NewKey("api_url", opts.GrafanaComAPIURL) require.NoError(t, err) } + if opts.GrafanaComSSOAPIToken != "" { + grafanaComSection, err := getOrCreateSection("grafana_com") + require.NoError(t, err) + _, err = grafanaComSection.NewKey("sso_api_token", opts.GrafanaComSSOAPIToken) + require.NoError(t, err) + } + if opts.UnifiedStorageConfig != nil { for k, v := range opts.UnifiedStorageConfig { section, err := getOrCreateSection(fmt.Sprintf("unified_storage.%s", k)) @@ -526,6 +533,7 @@ type GrafanaOpts struct { GrafanaComAPIURL string UnifiedStorageConfig map[string]setting.UnifiedStorageConfig PermittedProvisioningPaths string + GrafanaComSSOAPIToken string // When "unified-grpc" is selected it will also start the grpc server APIServerStorageType options.StorageType diff --git a/pkg/tsdb/grafana-testdata-datasource/resource_handler.go b/pkg/tsdb/grafana-testdata-datasource/resource_handler.go index 3c02b183792..31daa042e87 100644 --- a/pkg/tsdb/grafana-testdata-datasource/resource_handler.go +++ b/pkg/tsdb/grafana-testdata-datasource/resource_handler.go @@ -4,13 +4,13 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "net/http" "sort" "strconv" "time" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" ) @@ -78,34 +78,89 @@ func (s *Service) testStreamHandler(rw http.ResponseWriter, req *http.Request) { ctxLogger := s.logger.FromContext(req.Context()) ctxLogger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) + header := rw.Header() + header.Set("Cache-Control", "no-store") + header.Set("X-Content-Type-Options", "nosniff") + header.Set("Content-Type", "text/plain") + + writeError := func(code int, message string) { + rw.WriteHeader(code) + _, _ = rw.Write([]byte(message)) + } + if req.Method != http.MethodGet { + writeError(http.StatusMethodNotAllowed, "only supports get") return } + var err error + query := req.URL.Query() count := 10 - countstr := req.URL.Query().Get("count") - if countstr != "" { - if i, err := strconv.Atoi(countstr); err == nil { - count = i + if query.Has("count") { + count, err = strconv.Atoi(query.Get("count")) + if err != nil { + writeError(http.StatusBadRequest, "invalid count value") + return } } - sleep := req.URL.Query().Get("sleep") - sleepDuration, err := time.ParseDuration(sleep) - if err != nil { - sleepDuration = time.Millisecond + start := 1 + if query.Has("start") { + start, err = strconv.Atoi(query.Get("start")) + if err != nil { + writeError(http.StatusBadRequest, "invalid start value") + return + } } - rw.Header().Set("Content-Type", "text/plain") + flush := 100 // flush 100% of the time + if query.Has("flush") { + flush, err = strconv.Atoi(query.Get("flush")) + if err != nil { + writeError(http.StatusBadRequest, "invalid flush value") + return + } + if flush > 100 || flush < 0 { + writeError(http.StatusBadRequest, "expecting flush between 0-100") + return + } + } + + speed := time.Millisecond * 10 + if query.Has("speed") { + speed, err = time.ParseDuration(query.Get("speed")) + if err != nil { + writeError(http.StatusBadRequest, "invalid speed") + return + } + } + + line := func(i int) string { + return fmt.Sprintf("Message #%d", i) + } + switch query.Get("format") { + case "json": + line = func(i int) string { + return fmt.Sprintf(`{"message": %d, "value": %.3f, "time": %d}`, i, rand.Float64(), time.Now().UnixMilli()) + } + case "influx": + line = func(i int) string { + val := rand.Float64() + return fmt.Sprintf("measurement,tag1=value1,tag2=value2 message=%d,value=%.3f %d", i, val, time.Now().UnixMilli()) + } + } rw.WriteHeader(http.StatusOK) - for i := 1; i <= count; i++ { - if _, err := io.WriteString(rw, fmt.Sprintf("Message #%d", i)); err != nil { + for i := start; i <= count; i++ { + if _, err := io.WriteString(rw, line(i)+"\n"); err != nil { ctxLogger.Error("Failed to write response", "error", err) return } - rw.(http.Flusher).Flush() - time.Sleep(sleepDuration) + // This may send multiple lines in one chunk + if flush > rand.Intn(100) { + rw.(http.Flusher).Flush() + } + time.Sleep(speed) } } diff --git a/public/app/core/internationalization/index.tsx b/public/app/core/internationalization/index.tsx index 2d31fc1d708..e3dbfb28270 100644 --- a/public/app/core/internationalization/index.tsx +++ b/public/app/core/internationalization/index.tsx @@ -68,7 +68,14 @@ export const Trans = (props: TransProps): ReactElement => { return ; }; -// Wrap t() to provide default namespaces and enforce a consistent API +/** + * This is a simple wrapper over i18n.t() to provide default namespaces and enforce a consistent API. + * Note: Don't use this in the top level module scope. This wrapper needs initialization, which is done during Grafana + * startup, and it will throw if used before. + * @param id ID of the translation string + * @param defaultMessage Default message to use if the translation is missing + * @param values Values to be interpolated into the string + */ export const t = (id: string, defaultMessage: string, values?: Record) => { if (!tFunc) { if (process.env.NODE_ENV !== 'test') { diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 472f0fe221e..ce7dab8c838 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -142,6 +142,83 @@ export class BackendSrv implements BackendService { }); } + chunkRequestId = 1; + + chunked(options: BackendSrvRequest): Observable> { + const requestId = options.requestId ?? `chunked-${this.chunkRequestId++}`; + const controller = new AbortController(); + const url = parseUrlFromOptions(options); + const init = parseInitFromOptions({ + ...options, + requestId, + abortSignal: controller.signal, + }); + + return new Observable((observer) => { + let done = false; + // Calling fromFetch explicitly avoids the request queue + const sub = this.dependencies.fromFetch(url, init).subscribe({ + next: (response) => { + const rsp = { + status: response.status, + statusText: response.statusText, + ok: response.ok, + headers: response.headers, + url: response.url, + type: response.type, + redirected: response.redirected, + config: options, + traceId: response.headers.get(GRAFANA_TRACEID_HEADER) ?? undefined, + data: undefined, + }; + + if (!response.body) { + observer.next(rsp); + observer.complete(); + return; + } + + const reader = response.body.getReader(); + async function process() { + while (reader && !done) { + if (controller.signal.aborted) { + reader.cancel(controller.signal.reason); + console.log(requestId, 'signal.aborted'); + return; + } + const chunk = await reader.read(); + observer.next({ + ...rsp, + data: chunk.value, + }); + if (chunk.done) { + done = true; + console.log(requestId, 'done'); + } + } + } + process() + .catch((e) => { + console.log(requestId, 'catch', e); + }) // from abort + .then(() => { + console.log(requestId, 'complete'); + observer.complete(); + }); // runs in background + }, + error: (e) => { + observer.error(e); + }, + }); + + return function unsubscribe() { + console.log(requestId, 'unsubscribe'); + controller.abort('unsubscribe'); + sub.unsubscribe(); + }; + }); + } + private internalFetch(options: BackendSrvRequest): Observable> { if (options.requestId) { this.inFlightRequests.next(options.requestId); diff --git a/public/app/core/utils/fetch.ts b/public/app/core/utils/fetch.ts index 90a7ace0124..fb2564146d8 100644 --- a/public/app/core/utils/fetch.ts +++ b/public/app/core/utils/fetch.ts @@ -15,6 +15,7 @@ export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => headers, body, credentials, + signal: options.abortSignal, }; }; diff --git a/public/app/features/alerting/unified/api/prometheus.ts b/public/app/features/alerting/unified/api/prometheus.ts index faf60f28ca4..58c5a58a711 100644 --- a/public/app/features/alerting/unified/api/prometheus.ts +++ b/public/app/features/alerting/unified/api/prometheus.ts @@ -1,3 +1,4 @@ +import { produce } from 'immer'; import { lastValueFrom } from 'rxjs'; import { getBackendSrv } from '@grafana/runtime'; @@ -89,10 +90,9 @@ export function paramsWithMatcherAndState( return paramsResult; } -export const groupRulesByFileName = (groups: PromRuleGroupDTO[], dataSourceName: string) => { - const nsMap: { [key: string]: RuleNamespace } = {}; - groups.forEach((group) => { - group.rules.forEach((rule) => { +export function normalizeRuleGroup(group: PromRuleGroupDTO): PromRuleGroupDTO { + return produce(group, (draft) => { + draft.rules.forEach((rule) => { rule.query = rule.query || ''; if (rule.type === PromRuleType.Alerting) { // There's a possibility that a custom/unexpected datasource might response with @@ -100,11 +100,19 @@ export const groupRulesByFileName = (groups: PromRuleGroupDTO[], dataSourceName: // In this case, we fall back to `Inactive` state so that elsewhere in the UI we don't fail/have to handle the edge case // and log a message so we can identify how frequently this might be happening if (!rule.state) { - logInfo('prom rule with type=alerting is missing a state', { dataSourceName, ruleName: rule.name }); + logInfo('prom rule with type=alerting is missing a state', { ruleName: rule.name }); rule.state = PromAlertingRuleState.Inactive; } } }); + }); +} + +export const groupRulesByFileName = (groups: PromRuleGroupDTO[], dataSourceName: string) => { + const normalizedGroups = groups.map(normalizeRuleGroup); + + const nsMap: { [key: string]: RuleNamespace } = {}; + normalizedGroups.forEach((group) => { if (!nsMap[group.file]) { nsMap[group.file] = { dataSourceName, @@ -118,6 +126,7 @@ export const groupRulesByFileName = (groups: PromRuleGroupDTO[], dataSourceName: return Object.values(nsMap); }; + export const ungroupRulesByFileName = (namespaces: RuleNamespace[] = []): PromRuleGroupDTO[] => { return namespaces?.flatMap((namespace) => namespace.groups.flatMap((group) => ruleGroupToPromRuleGroupDTO(group, namespace.name)) diff --git a/public/app/features/alerting/unified/api/prometheusApi.ts b/public/app/features/alerting/unified/api/prometheusApi.ts index eb9bf877ae1..fd512216b81 100644 --- a/public/app/features/alerting/unified/api/prometheusApi.ts +++ b/public/app/features/alerting/unified/api/prometheusApi.ts @@ -1,11 +1,14 @@ -import { PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; +import { GrafanaPromRuleGroupDTO, PromRuleDTO, PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; + +import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { alertingApi } from './alertingApi'; +import { normalizeRuleGroup } from './prometheus'; -interface PromRulesResponse { +export interface PromRulesResponse { status: string; data: { - groups: PromRuleGroupDTO[]; + groups: TRuleGroup[]; groupNextToken?: string; }; errorType?: string; @@ -22,11 +25,37 @@ interface PromRulesOptions { groupNextToken?: string; } +type GrafanaPromRulesOptions = Omit & { + dashboardUid?: string; + panelId?: number; +}; + export const prometheusApi = alertingApi.injectEndpoints({ endpoints: (build) => ({ - groups: build.query({ - query: ({ ruleSource, namespace, groupName, ruleName, groupLimit, excludeAlerts, groupNextToken }) => ({ - url: `api/prometheus/${ruleSource.uid}/api/v1/rules`, + getGroups: build.query>, PromRulesOptions>({ + query: ({ ruleSource, namespace, groupName, ruleName, groupLimit, excludeAlerts, groupNextToken }) => { + if (ruleSource.uid === GRAFANA_RULES_SOURCE_NAME) { + throw new Error('Please use getGrafanaGroups endpoint for grafana rules'); + } + return { + url: `api/prometheus/${ruleSource.uid}/api/v1/rules`, + params: { + 'file[]': namespace, + 'group[]': groupName, + 'rule[]': ruleName, + exclude_alerts: excludeAlerts?.toString(), + group_limit: groupLimit?.toFixed(0), + group_next_token: groupNextToken, + }, + }; + }, + transformResponse: (response: PromRulesResponse>) => { + return { ...response, data: { ...response.data, groups: response.data.groups.map(normalizeRuleGroup) } }; + }, + }), + getGrafanaGroups: build.query, GrafanaPromRulesOptions>({ + query: ({ namespace, groupName, ruleName, groupLimit, excludeAlerts, groupNextToken }) => ({ + url: `api/prometheus/grafana/api/v1/rules`, params: { 'file[]': namespace, 'group[]': groupName, diff --git a/public/app/features/alerting/unified/rule-list/AlertRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx similarity index 95% rename from public/app/features/alerting/unified/rule-list/AlertRuleLoader.tsx rename to public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx index 2567f21f8be..234d241bcdc 100644 --- a/public/app/features/alerting/unified/rule-list/AlertRuleLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx @@ -14,12 +14,15 @@ import { ActionsLoader, RuleActionsButtons } from './components/RuleActionsButto const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi; const { useGetRuleGroupForNamespaceQuery } = alertRuleApi; -interface AlertRuleLoaderProps { +interface DataSourceRuleLoaderProps { rule: Rule; groupIdentifier: DataSourceRuleGroupIdentifier; } -export const AlertRuleLoader = memo(function AlertRuleLoader({ rule, groupIdentifier }: AlertRuleLoaderProps) { +export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({ + rule, + groupIdentifier, +}: DataSourceRuleLoaderProps) { const { rulesSource, namespace, groupName } = groupIdentifier; const ruleIdentifier = fromRule(rulesSource.name, namespace.name, groupName, rule); diff --git a/public/app/features/alerting/unified/rule-list/FilterView.tsx b/public/app/features/alerting/unified/rule-list/FilterView.tsx index 1744c77551b..41414d88461 100644 --- a/public/app/features/alerting/unified/rule-list/FilterView.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterView.tsx @@ -9,12 +9,19 @@ import { isLoading, useAsync } from '../hooks/useAsync'; import { RulesFilter } from '../search/rulesSearchParser'; import { hashRule } from '../utils/rule-id'; -import { AlertRuleLoader } from './AlertRuleLoader'; +import { DataSourceRuleLoader } from './DataSourceRuleLoader'; +import { GrafanaRuleLoader } from './GrafanaRuleLoader'; import LoadMoreHelper from './LoadMoreHelper'; +import { UnknownRuleListItem } from './components/AlertRuleListItem'; import { ListItem } from './components/ListItem'; import { ActionsLoader } from './components/RuleActionsButtons.V2'; import { RuleListIcon } from './components/RuleListIcon'; -import { RuleWithOrigin, useFilteredRulesIteratorProvider } from './hooks/useFilteredRulesIterator'; +import { + GrafanaRuleWithOrigin, + PromRuleWithOrigin, + RuleWithOrigin, + useFilteredRulesIteratorProvider, +} from './hooks/useFilteredRulesIterator'; interface FilterViewProps { filterState: RulesFilter; @@ -30,13 +37,13 @@ export function FilterView({ filterState }: FilterViewProps) { return ; } -interface KeyedRuleWithOrigin extends RuleWithOrigin { +type KeyedRuleWithOrigin = RuleWithOrigin & { /** * Artificial frontend-only identifier for the rule. * It's used as a key for the rule in the rule list to prevent key duplication */ key: string; -} +}; /** * Renders the list of rules that match the filter. @@ -107,9 +114,25 @@ function FilterViewResults({ filterState }: FilterViewProps) { return (
    - {rules.map(({ key, rule, groupIdentifier }) => ( - - ))} + {rules.map((ruleWithOrigin) => { + const { key, rule, groupIdentifier, origin } = ruleWithOrigin; + + switch (origin) { + case 'grafana': + return ( + + ); + case 'datasource': + return ; + default: + return ; + } + })} {loading && ( <> @@ -146,7 +169,22 @@ function onFinished(fn: () => void) { return tap(undefined, undefined, fn); } -function getRuleKey(ruleWithOrigin: RuleWithOrigin) { +function getRuleKey(ruleWithOrigin: RuleWithOrigin): string { + if (ruleWithOrigin.origin === 'grafana') { + return getGrafanaRuleKey(ruleWithOrigin); + } + return getDataSourceRuleKey(ruleWithOrigin); +} + +function getGrafanaRuleKey(ruleWithOrigin: GrafanaRuleWithOrigin) { + const { + groupIdentifier: { namespace, groupName }, + rule, + } = ruleWithOrigin; + return `grafana-${namespace.uid}-${groupName}-${rule.uid}}`; +} + +function getDataSourceRuleKey(ruleWithOrigin: PromRuleWithOrigin) { const { rule, groupIdentifier: { rulesSource, namespace, groupName }, diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx new file mode 100644 index 00000000000..a572e29148f --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx @@ -0,0 +1,56 @@ +import { GrafanaPromRuleDTO, PromRuleType } from 'app/types/unified-alerting-dto'; + +import { GrafanaRulesSource } from '../utils/datasource'; +import { createRelativeUrl } from '../utils/url'; + +import { AlertRuleListItem, RecordingRuleListItem, UnknownRuleListItem } from './components/AlertRuleListItem'; + +interface GrafanaRuleLoaderProps { + rule: GrafanaPromRuleDTO; + groupName: string; + namespaceName: string; +} + +export function GrafanaRuleLoader({ rule, groupName, namespaceName }: GrafanaRuleLoaderProps) { + const { folderUid } = rule; + + const commonProps = { + name: rule.name, + rulesSource: GrafanaRulesSource, + group: groupName, + namespace: namespaceName, + href: createRelativeUrl(`/alerting/grafana/${rule.uid}/view`), + health: rule.health, + error: rule.lastError, + labels: rule.labels, + }; + + if (rule.type === PromRuleType.Alerting) { + return ( + + ); + } + + if (rule.type === PromRuleType.Recording) { + return ; + } + + return ( + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/GroupedView.tsx b/public/app/features/alerting/unified/rule-list/GroupedView.tsx index 37a608901ee..da79a9e6d5a 100644 --- a/public/app/features/alerting/unified/rule-list/GroupedView.tsx +++ b/public/app/features/alerting/unified/rule-list/GroupedView.tsx @@ -1,53 +1,46 @@ -import { css } from '@emotion/css'; -import { PropsWithChildren, ReactNode, useMemo } from 'react'; +import { useMemo } from 'react'; import Skeleton from 'react-loading-skeleton'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, Dropdown, Icon, IconButton, LinkButton, Menu, Stack, Text, useStyles2 } from '@grafana/ui'; -import { Trans } from 'app/core/internationalization'; -import { DataSourceNamespaceIdentifier, DataSourceRuleGroupIdentifier, RuleGroup } from 'app/types/unified-alerting'; -import { RulesSourceApplication } from 'app/types/unified-alerting-dto'; +import { Stack } from '@grafana/ui'; +import { DataSourceRulesSourceIdentifier } from 'app/types/unified-alerting'; import { featureDiscoveryApi } from '../api/featureDiscoveryApi'; -import { Spacer } from '../components/Spacer'; -import { WithReturnButton } from '../components/WithReturnButton'; -import { getDatasourceAPIUid, getExternalRulesSources } from '../utils/datasource'; -import { hashRule } from '../utils/rule-id'; +import { GrafanaRulesSource, getExternalRulesSources } from '../utils/datasource'; -import { AlertRuleLoader } from './AlertRuleLoader'; -import { ListGroup } from './components/ListGroup'; -import { ListSection } from './components/ListSection'; -import { DataSourceIcon } from './components/Namespace'; -import { LoadingIndicator } from './components/RuleGroup'; -import { usePaginatedPrometheusRuleNamespaces } from './hooks/usePaginatedPrometheusRuleNamespaces'; +import { PaginatedDataSourceLoader } from './PaginatedDataSourceLoader'; +import { PaginatedGrafanaLoader } from './PaginatedGrafanaLoader'; +import { DataSourceErrorBoundary } from './components/DataSourceErrorBoundary'; +import { DataSourceSection } from './components/DataSourceSection'; const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi; -const GROUP_PAGE_SIZE = 40; export function GroupedView() { const externalRuleSources = useMemo(() => getExternalRulesSources(), []); return ( - + + + {externalRuleSources.map((ruleSource) => { - return ; + return ; })} ); } interface DataSourceLoaderProps { - name: string; - uid: string; + rulesSourceIdentifier: DataSourceRulesSourceIdentifier; } export function GrafanaDataSourceLoader() { return ; } -export function DataSourceLoader({ uid, name }: DataSourceLoaderProps) { - const { data: dataSourceInfo, isLoading } = useDiscoverDsFeaturesQuery({ uid }); +function DataSourceLoader({ rulesSourceIdentifier }: DataSourceLoaderProps) { + const { data: dataSourceInfo, isLoading } = useDiscoverDsFeaturesQuery({ uid: rulesSourceIdentifier.uid }); + + const { uid, name } = rulesSourceIdentifier; if (isLoading) { return } uid={uid} name={name} />; @@ -56,223 +49,15 @@ export function DataSourceLoader({ uid, name }: DataSourceLoaderProps) { // 2. grab prometheus rule groups with max_groups if supported if (dataSourceInfo) { return ( - + + + ); } return null; } - -// TODO Try to use a better rules source identifier -interface PaginatedDataSourceLoaderProps - extends Required> { - ruleSourceName: string; -} - -function PaginatedDataSourceLoader({ ruleSourceName, name, uid, application }: PaginatedDataSourceLoaderProps) { - const { - page: ruleNamespaces, - nextPage, - previousPage, - canMoveForward, - canMoveBackward, - isLoading, - } = usePaginatedPrometheusRuleNamespaces(ruleSourceName, GROUP_PAGE_SIZE); - - return ( - - - {ruleNamespaces.map((namespace) => ( - - {' '} - - {namespace.name} - - - } - > - {namespace.groups.map((group) => ( - - ))} - - ))} - - - - ); -} - -interface RuleGroupListItemProps { - group: RuleGroup; - ruleSourceName: string; - namespaceId: DataSourceNamespaceIdentifier; -} - -function RuleGroupListItem({ group, ruleSourceName, namespaceId }: RuleGroupListItemProps) { - const rulesWithGroupId = useMemo( - () => - group.rules.map((rule) => { - const groupIdentifier: DataSourceRuleGroupIdentifier = { - rulesSource: { uid: getDatasourceAPIUid(ruleSourceName), name: ruleSourceName }, - namespace: namespaceId, - groupName: group.name, - groupOrigin: 'datasource', - }; - return { rule, groupIdentifier }; - }), - [group, namespaceId, ruleSourceName] - ); - - return ( - - - - - - - - - } - > - - - - } - > - {rulesWithGroupId.map(({ rule, groupIdentifier }) => ( - - ))} - - ); -} - -interface DataSourceSectionProps extends PropsWithChildren { - uid: string; - name: string; - loader?: ReactNode; - application?: RulesSourceApplication; - isLoading?: boolean; - description?: ReactNode; -} - -const DataSourceSection = ({ - uid, - name, - application, - children, - loader, - isLoading = false, - description = null, -}: DataSourceSectionProps) => { - const styles = useStyles2(getStyles); - - return ( -
    - - - {isLoading && } -
    - {loader ?? ( - - {application && } - - {name} - - {description && ( - <> - {'·'} - {description} - - )} - - - Configure - - } - /> - - )} -
    -
    -
    {children}
    -
    -
    - ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - itemsWrapper: css({ - position: 'relative', - marginLeft: theme.spacing(1.5), - - '&:before': { - content: "''", - position: 'absolute', - height: '100%', - - marginLeft: `-${theme.spacing(1.5)}`, - borderLeft: `solid 1px ${theme.colors.border.weak}`, - }, - }), - dataSourceSectionTitle: css({ - background: theme.colors.background.secondary, - padding: `${theme.spacing(1)} ${theme.spacing(1.5)}`, - - border: `solid 1px ${theme.colors.border.weak}`, - borderRadius: theme.shape.radius.default, - }), -}); - -interface LazyPaginationProps { - canMoveForward: boolean; - canMoveBackward: boolean; - nextPage: () => void; - previousPage: () => void; -} - -function LazyPagination({ canMoveForward, canMoveBackward, nextPage, previousPage }: LazyPaginationProps) { - return ( - - - - - ); -} diff --git a/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx new file mode 100644 index 00000000000..04e5892bfe3 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx @@ -0,0 +1,108 @@ +import { groupBy } from 'lodash'; +import { useEffect, useMemo, useRef } from 'react'; + +import { Icon, Stack, Text } from '@grafana/ui'; +import { DataSourceRuleGroupIdentifier, DataSourceRulesSourceIdentifier, RuleGroup } from 'app/types/unified-alerting'; + +import { hashRule } from '../utils/rule-id'; + +import { DataSourceRuleLoader } from './DataSourceRuleLoader'; +import { DataSourceSection, DataSourceSectionProps } from './components/DataSourceSection'; +import { LazyPagination } from './components/LazyPagination'; +import { ListGroup } from './components/ListGroup'; +import { ListSection } from './components/ListSection'; +import { RuleGroupActionsMenu } from './components/RuleGroupActionsMenu'; +import { usePrometheusGroupsGenerator } from './hooks/prometheusGroupsGenerator'; +import { usePaginatedPrometheusGroups } from './hooks/usePaginatedPrometheusGroups'; + +const DATA_SOURCE_GROUP_PAGE_SIZE = 40; + +interface PaginatedDataSourceLoaderProps extends Required> { + rulesSourceIdentifier: DataSourceRulesSourceIdentifier; +} +export function PaginatedDataSourceLoader({ rulesSourceIdentifier, application }: PaginatedDataSourceLoaderProps) { + const { uid, name } = rulesSourceIdentifier; + const prometheusGroupsGenerator = usePrometheusGroupsGenerator(); + + const groupsGenerator = useRef(prometheusGroupsGenerator(rulesSourceIdentifier, DATA_SOURCE_GROUP_PAGE_SIZE)); + + useEffect(() => { + const currentGenerator = groupsGenerator.current; + return () => { + currentGenerator.return(); + }; + }, [groupsGenerator]); + + const { + page: groupsPage, + nextPage, + previousPage, + canMoveForward, + canMoveBackward, + isLoading, + } = usePaginatedPrometheusGroups(groupsGenerator.current, DATA_SOURCE_GROUP_PAGE_SIZE); + + const groupsByNamespace = useMemo(() => groupBy(groupsPage, 'file'), [groupsPage]); + + return ( + + + {Object.entries(groupsByNamespace).map(([namespace, groups]) => ( + + {' '} + + {namespace} + + + } + > + {groups.map((group) => ( + + ))} + + ))} + + + + ); +} + +interface RuleGroupListItemProps { + group: RuleGroup; + rulesSourceIdentifier: DataSourceRulesSourceIdentifier; + namespaceName: string; +} +function RuleGroupListItem({ rulesSourceIdentifier, group, namespaceName }: RuleGroupListItemProps) { + const rulesWithGroupId = useMemo(() => { + return group.rules.map((rule) => { + const groupIdentifier: DataSourceRuleGroupIdentifier = { + rulesSource: rulesSourceIdentifier, + namespace: { name: namespaceName }, + groupName: group.name, + groupOrigin: 'datasource', + }; + return { rule, groupIdentifier }; + }); + }, [group, namespaceName, rulesSourceIdentifier]); + + return ( + }> + {rulesWithGroupId.map(({ rule, groupIdentifier }) => ( + + ))} + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx new file mode 100644 index 00000000000..3f9cd0814cd --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx @@ -0,0 +1,93 @@ +import { groupBy } from 'lodash'; +import { useEffect, useMemo, useRef } from 'react'; + +import { Icon, Stack, Text } from '@grafana/ui'; +import { GrafanaRulesSourceSymbol } from 'app/types/unified-alerting'; +import { GrafanaPromRuleGroupDTO } from 'app/types/unified-alerting-dto'; + +import { GrafanaRuleLoader } from './GrafanaRuleLoader'; +import { DataSourceSection } from './components/DataSourceSection'; +import { LazyPagination } from './components/LazyPagination'; +import { ListGroup } from './components/ListGroup'; +import { ListSection } from './components/ListSection'; +import { RuleGroupActionsMenu } from './components/RuleGroupActionsMenu'; +import { useGrafanaGroupsGenerator } from './hooks/prometheusGroupsGenerator'; +import { usePaginatedPrometheusGroups } from './hooks/usePaginatedPrometheusGroups'; + +const GRAFANA_GROUP_PAGE_SIZE = 40; + +export function PaginatedGrafanaLoader() { + const grafanaGroupsGenerator = useGrafanaGroupsGenerator(); + + const groupsGenerator = useRef(grafanaGroupsGenerator(GRAFANA_GROUP_PAGE_SIZE)); + + useEffect(() => { + const currentGenerator = groupsGenerator.current; + return () => { + currentGenerator.return(); + }; + }, []); + + const { + page: groupsPage, + nextPage, + previousPage, + canMoveForward, + canMoveBackward, + isLoading, + } = usePaginatedPrometheusGroups(groupsGenerator.current, GRAFANA_GROUP_PAGE_SIZE); + + const groupsByFolder = useMemo(() => groupBy(groupsPage, 'folderUid'), [groupsPage]); + + return ( + + + {Object.entries(groupsByFolder).map(([folderUid, groups]) => { + // Groups are grouped by folder, so we can use the first group to get the folder name + const folderName = groups[0].file; + return ( + + {' '} + + {folderName} + + + } + > + {groups.map((group) => ( + + ))} + + ); + })} + + + + ); +} + +interface GrafanaRuleGroupListItemProps { + group: GrafanaPromRuleGroupDTO; + namespaceName: string; +} +export function GrafanaRuleGroupListItem({ group, namespaceName }: GrafanaRuleGroupListItemProps) { + return ( + }> + {group.rules.map((rule) => { + return ; + })} + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx index 193f6802ed7..be4e51eaed3 100644 --- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import pluralize from 'pluralize'; -import { ReactNode } from 'react'; +import { ReactNode, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Icon, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; @@ -266,15 +266,17 @@ interface UnknownRuleListItemProps { export const UnknownRuleListItem = ({ rule, groupIdentifier }: UnknownRuleListItemProps) => { const styles = useStyles2(getStyles); - const { rulesSource, namespace, groupName } = groupIdentifier; - const ruleContext = { - name: rule.name, - groupName, - namespace: JSON.stringify(namespace), - rulesSource: rulesSource.uid === GrafanaRulesSourceSymbol ? GRAFANA_RULES_SOURCE_NAME : rulesSource.uid, - }; - logError(new Error('unknown rule type'), ruleContext); + useEffect(() => { + const { rulesSource, namespace, groupName } = groupIdentifier; + const ruleContext = { + name: rule.name, + groupName, + namespace: JSON.stringify(namespace), + rulesSource: rulesSource.uid === GrafanaRulesSourceSymbol ? GRAFANA_RULES_SOURCE_NAME : rulesSource.uid, + }; + logError(new Error('unknown rule type'), ruleContext); + }, [rule, groupIdentifier]); return ( diff --git a/public/app/features/alerting/unified/rule-list/components/DataSourceErrorBoundary.tsx b/public/app/features/alerting/unified/rule-list/components/DataSourceErrorBoundary.tsx new file mode 100644 index 00000000000..14817653c5f --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/DataSourceErrorBoundary.tsx @@ -0,0 +1,47 @@ +import { Alert, ErrorBoundary, ErrorWithStack, Text } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; +import { RulesSourceIdentifier } from 'app/types/unified-alerting'; + +import { DataSourceSection } from './DataSourceSection'; + +/** + * Some more exotic Prometheus data sources might not be 100% compatible with Prometheus API + * We don't want them to break the whole page, so we wrap them in an error boundary + */ + +export function DataSourceErrorBoundary({ + children, + rulesSourceIdentifier, +}: { + children: React.ReactNode; + rulesSourceIdentifier: RulesSourceIdentifier; +}) { + return ( + + {({ error, errorInfo }) => { + if (error || errorInfo) { + const { uid, name } = rulesSourceIdentifier; + return ( + + + + + Check the data source configuration. Does the data source support Prometheus API? + + + + + + ); + } + return children; + }} + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx new file mode 100644 index 00000000000..9df0aa641b3 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx @@ -0,0 +1,94 @@ +import { css } from '@emotion/css'; +import { PropsWithChildren, ReactNode } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { LinkButton, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; +import { RulesSourceIdentifier } from 'app/types/unified-alerting'; +import { RulesSourceApplication } from 'app/types/unified-alerting-dto'; + +import { Spacer } from '../../components/Spacer'; +import { WithReturnButton } from '../../components/WithReturnButton'; + +import { DataSourceIcon } from './Namespace'; +import { LoadingIndicator } from './RuleGroup'; + +export interface DataSourceSectionProps extends PropsWithChildren { + uid: RulesSourceIdentifier['uid']; + name: string; + loader?: ReactNode; + application?: RulesSourceApplication; + isLoading?: boolean; + description?: ReactNode; +} + +export const DataSourceSection = ({ + uid, + name, + application, + children, + loader, + isLoading = false, + description = null, +}: DataSourceSectionProps) => { + const styles = useStyles2(getStyles); + + return ( +
    + + + {isLoading && } +
    + {loader ?? ( + + {application && } + + {name} + + {description && ( + <> + {'·'} + {description} + + )} + + + Configure + + } + /> + + )} +
    +
    +
    {children}
    +
    +
    + ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + itemsWrapper: css({ + position: 'relative', + marginLeft: theme.spacing(1.5), + + '&:before': { + content: "''", + position: 'absolute', + height: '100%', + + marginLeft: `-${theme.spacing(1.5)}`, + borderLeft: `solid 1px ${theme.colors.border.weak}`, + }, + }), + dataSourceSectionTitle: css({ + background: theme.colors.background.secondary, + padding: `${theme.spacing(1)} ${theme.spacing(1.5)}`, + + border: `solid 1px ${theme.colors.border.weak}`, + borderRadius: theme.shape.radius.default, + }), +}); diff --git a/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx b/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx new file mode 100644 index 00000000000..f064111c0b7 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx @@ -0,0 +1,34 @@ +import { Button, Icon, Stack } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +interface LazyPaginationProps { + canMoveForward: boolean; + canMoveBackward: boolean; + nextPage: () => void; + previousPage: () => void; +} + +export function LazyPagination({ canMoveForward, canMoveBackward, nextPage, previousPage }: LazyPaginationProps) { + return ( + + + + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx b/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx new file mode 100644 index 00000000000..7c2735ea5cd --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx @@ -0,0 +1,22 @@ +import { Dropdown, IconButton, Menu } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +export function RuleGroupActionsMenu() { + return ( + <> + + + + + + + + } + > + + + + ); +} diff --git a/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts new file mode 100644 index 00000000000..3c1c0a642a3 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts @@ -0,0 +1,81 @@ +import { BaseQueryFn } from '@reduxjs/toolkit/query'; +import { TypedLazyQueryTrigger } from '@reduxjs/toolkit/query/react'; +import { useCallback } from 'react'; + +import { DataSourceRulesSourceIdentifier } from 'app/types/unified-alerting'; + +import { BaseQueryFnArgs } from '../../api/alertingApi'; +import { PromRulesResponse, prometheusApi } from '../../api/prometheusApi'; + +const { useLazyGetGroupsQuery, useLazyGetGrafanaGroupsQuery } = prometheusApi; + +interface FetchGroupsOptions { + groupLimit?: number; + groupNextToken?: string; +} + +export function usePrometheusGroupsGenerator() { + const [getGroups] = useLazyGetGroupsQuery(); + + return useCallback( + async function* (ruleSource: DataSourceRulesSourceIdentifier, groupLimit: number) { + const getRuleSourceGroups = (options: FetchGroupsOptions) => + getGroups({ ruleSource: { uid: ruleSource.uid }, ...options }); + + yield* genericGroupsGenerator(getRuleSourceGroups, groupLimit); + }, + [getGroups] + ); +} + +export function useGrafanaGroupsGenerator() { + const [getGrafanaGroups] = useLazyGetGrafanaGroupsQuery(); + + return useCallback( + async function* (groupLimit: number) { + yield* genericGroupsGenerator(getGrafanaGroups, groupLimit); + }, + [getGrafanaGroups] + ); +} + +// Generator lazily provides groups one by one only when needed +// This might look a bit complex but it allows us to have one API for paginated and non-paginated Prometheus data sources +// For unpaginated data sources we fetch everything in one go +// For paginated we fetch the next page when needed +async function* genericGroupsGenerator( + fetchGroups: TypedLazyQueryTrigger, FetchGroupsOptions, BaseQueryFn>, + groupLimit: number +) { + const response = await fetchGroups({ groupLimit }); + + if (!response.isSuccess) { + return; + } + + if (response.data?.data) { + yield* response.data.data.groups; + } + + let lastToken: string | undefined = undefined; + if (response.data?.data?.groupNextToken) { + lastToken = response.data.data.groupNextToken; + } + + while (lastToken) { + const response = await fetchGroups({ + groupNextToken: lastToken, + groupLimit: groupLimit, + }); + + if (!response.isSuccess) { + return; + } + + if (response.data?.data) { + yield* response.data.data.groups; + } + + lastToken = response.data?.data?.groupNextToken; + } +} diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts index f3ac6cbf315..69eac1b63a7 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts @@ -1,97 +1,95 @@ +import { AsyncIterableX, from } from 'ix/asynciterable/index'; import { merge } from 'ix/asynciterable/merge'; import { filter, flatMap, map } from 'ix/asynciterable/operators'; import { compact } from 'lodash'; -import { useCallback } from 'react'; import { Matcher } from 'app/plugins/datasource/alertmanager/types'; -import { DataSourceRuleGroupIdentifier, ExternalRulesSourceIdentifier } from 'app/types/unified-alerting'; -import { PromRuleDTO, PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; +import { + DataSourceRuleGroupIdentifier, + DataSourceRulesSourceIdentifier, + GrafanaRuleGroupIdentifier, +} from 'app/types/unified-alerting'; +import { + GrafanaPromRuleDTO, + GrafanaPromRuleGroupDTO, + PromRuleDTO, + PromRuleGroupDTO, +} from 'app/types/unified-alerting-dto'; -import { prometheusApi } from '../../api/prometheusApi'; import { RulesFilter } from '../../search/rulesSearchParser'; import { labelsMatchMatchers } from '../../utils/alertmanager'; import { Annotation } from '../../utils/constants'; -import { getDatasourceAPIUid, getExternalRulesSources } from '../../utils/datasource'; +import { GrafanaRulesSource, getDatasourceAPIUid, getExternalRulesSources } from '../../utils/datasource'; import { parseMatcher } from '../../utils/matchers'; import { isAlertingRule } from '../../utils/rules'; -export interface RuleWithOrigin { - rule: PromRuleDTO; - groupIdentifier: DataSourceRuleGroupIdentifier; +import { useGrafanaGroupsGenerator, usePrometheusGroupsGenerator } from './prometheusGroupsGenerator'; + +export type RuleWithOrigin = PromRuleWithOrigin | GrafanaRuleWithOrigin; + +export interface GrafanaRuleWithOrigin { + rule: GrafanaPromRuleDTO; + groupIdentifier: GrafanaRuleGroupIdentifier; + /** + * The name of the namespace that contains the rule group + * groupIdentifier contains the uid of the namespace, but not the user-friendly display name + */ + namespaceName: string; + origin: 'grafana'; } -const { useLazyGroupsQuery } = prometheusApi; +export interface PromRuleWithOrigin { + rule: PromRuleDTO; + groupIdentifier: DataSourceRuleGroupIdentifier; + origin: 'datasource'; +} export function useFilteredRulesIteratorProvider() { - const [fetchGroups] = useLazyGroupsQuery(); const allExternalRulesSources = getExternalRulesSources(); - /** - * This async generator will continue to yield rule groups and will keep fetching backend pages as long as the consumer - * is iterating. - */ - const fetchRuleSourceGroups = useCallback( - async function* (ruleSource: ExternalRulesSourceIdentifier, maxGroups: number) { - const response = await fetchGroups({ ruleSource: { uid: ruleSource.uid }, groupLimit: maxGroups }); + const prometheusGroupsGenerator = usePrometheusGroupsGenerator(); + const grafanaGroupsGenerator = useGrafanaGroupsGenerator(); - if (!response.isSuccess) { - return; - } + const getFilteredRulesIterator = (filterState: RulesFilter, groupLimit: number): AsyncIterableX => { + const normalizedFilterState = normalizeFilterState(filterState); - if (response.data?.data) { - yield* response.data.data.groups.map((group) => [ruleSource, group] as const); - } - - let lastToken: string | undefined = undefined; - if (response.data?.data?.groupNextToken) { - lastToken = response.data.data.groupNextToken; - } - - while (lastToken) { - const response = await fetchGroups({ - ruleSource: { uid: ruleSource.uid }, - groupNextToken: lastToken, - groupLimit: maxGroups, - }); - - if (!response.isSuccess) { - return; - } - - if (response.data?.data) { - yield* response.data.data.groups.map((group) => [ruleSource, group] as const); - } - - lastToken = response.data?.data?.groupNextToken; - } - }, - [fetchGroups] - ); - - const getFilteredRulesIterator = (filterState: RulesFilter, groupLimit: number) => { const ruleSourcesToFetchFrom = filterState.dataSourceNames.length - ? filterState.dataSourceNames.map((ds) => ({ name: ds, uid: getDatasourceAPIUid(ds) })) + ? filterState.dataSourceNames.map((ds) => ({ + name: ds, + uid: getDatasourceAPIUid(ds), + ruleSourceType: 'datasource', + })) : allExternalRulesSources; - // This split into the first one and the rest is only for compatibility with the merge function from ix - const [source, ...iterables] = ruleSourcesToFetchFrom.map((ds) => fetchRuleSourceGroups(ds, groupLimit)); + const grafanaIterator = from(grafanaGroupsGenerator(groupLimit)).pipe( + filter((group) => groupFilter(group, normalizedFilterState)), + flatMap((group) => group.rules.map((rule) => [group, rule] as const)), + filter(([_, rule]) => ruleFilter(rule, normalizedFilterState)), + map(([group, rule]) => mapGrafanaRuleToRuleWithOrigin(group, rule)) + ); - return merge(source, ...iterables).pipe( - filter(([_, group]) => groupFilter(group, filterState)), + const [source, ...iterables] = ruleSourcesToFetchFrom.map((ds) => { + return from(prometheusGroupsGenerator(ds, groupLimit)).pipe(map((group) => [ds, group] as const)); + }); + + const dataSourcesIterator = merge(source, ...iterables).pipe( + filter(([_, group]) => groupFilter(group, normalizedFilterState)), flatMap(([rulesSource, group]) => group.rules.map((rule) => [rulesSource, group, rule] as const)), filter(([_, __, rule]) => ruleFilter(rule, filterState)), map(([rulesSource, group, rule]) => mapRuleToRuleWithOrigin(rulesSource, group, rule)) ); + + return merge(grafanaIterator, dataSourcesIterator); }; return { getFilteredRulesIterator }; } function mapRuleToRuleWithOrigin( - rulesSource: ExternalRulesSourceIdentifier, + rulesSource: DataSourceRulesSourceIdentifier, group: PromRuleGroupDTO, rule: PromRuleDTO -): RuleWithOrigin { +): PromRuleWithOrigin { return { rule, groupIdentifier: { @@ -100,6 +98,24 @@ function mapRuleToRuleWithOrigin( groupName: group.name, groupOrigin: 'datasource', }, + origin: 'datasource', + }; +} + +function mapGrafanaRuleToRuleWithOrigin( + group: GrafanaPromRuleGroupDTO, + rule: GrafanaPromRuleDTO +): GrafanaRuleWithOrigin { + return { + rule, + groupIdentifier: { + rulesSource: GrafanaRulesSource, + namespace: { uid: group.folderUid }, + groupName: group.name, + groupOrigin: 'grafana', + }, + namespaceName: group.file, + origin: 'grafana', }; } @@ -111,11 +127,11 @@ function groupFilter(group: PromRuleGroupDTO, filterState: RulesFilter): boolean const { name, file } = group; // TODO Add fuzzy filtering or not - if (filterState.namespace && !file.includes(filterState.namespace)) { + if (filterState.namespace && !file.toLowerCase().includes(filterState.namespace)) { return false; } - if (filterState.groupName && !name.includes(filterState.groupName)) { + if (filterState.groupName && !name.toLowerCase().includes(filterState.groupName)) { return false; } @@ -125,11 +141,13 @@ function groupFilter(group: PromRuleGroupDTO, filterState: RulesFilter): boolean function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) { const { name, labels = {}, health, type } = rule; - if (filterState.freeFormWords.length > 0 && !filterState.freeFormWords.some((word) => name.includes(word))) { + const nameLower = name.toLowerCase(); + + if (filterState.freeFormWords.length > 0 && !filterState.freeFormWords.some((word) => nameLower.includes(word))) { return false; } - if (filterState.ruleName && !name.includes(filterState.ruleName)) { + if (filterState.ruleName && !nameLower.includes(filterState.ruleName)) { return false; } @@ -165,6 +183,19 @@ function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) { return true; } +/** + * Lowercase free form words, rule name, group name and namespace + */ +function normalizeFilterState(filterState: RulesFilter): RulesFilter { + return { + ...filterState, + freeFormWords: filterState.freeFormWords.map((word) => word.toLowerCase()), + ruleName: filterState.ruleName?.toLowerCase(), + groupName: filterState.groupName?.toLowerCase(), + namespace: filterState.namespace?.toLowerCase(), + }; +} + function looseParseMatcher(matcherQuery: string): Matcher | undefined { try { return parseMatcher(matcherQuery); diff --git a/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx b/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx new file mode 100644 index 00000000000..81e269396f4 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx @@ -0,0 +1,77 @@ +import { useCallback, useMemo, useState } from 'react'; + +import { PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; + +import { isLoading, useAsync } from '../../hooks/useAsync'; + +/** + * Provides pagination functionality for rule groups with lazy loading. + * Instead of loading all groups at once, it uses a generator to fetch them in batches as needed, + * which helps with performance when dealing with large numbers of rules. + * + * @param groupsGenerator - An async generator that yields rule groups in batches + * @param pageSize - Number of groups to display per page + * @returns Pagination state and controls for navigating through rule groups + */ +export function usePaginatedPrometheusGroups( + groupsGenerator: AsyncGenerator, + pageSize: number +) { + const [currentPage, setCurrentPage] = useState(1); + const [groups, setGroups] = useState([]); + const [lastPage, setLastPage] = useState(undefined); + + const [{ execute: fetchMoreGroups }, groupsRequestState] = useAsync(async (groupsCount: number) => { + let done = false; + const currentGroups: TGroup[] = []; + + while (currentGroups.length < groupsCount) { + const generatorResult = await groupsGenerator.next(); + if (generatorResult.done) { + done = true; + break; + } + const group = generatorResult.value; + currentGroups.push(group); + } + + if (done) { + const groupsTotal = groups.length + currentGroups.length; + setLastPage(Math.ceil(groupsTotal / pageSize)); + } + + setGroups((groups) => [...groups, ...currentGroups]); + }); + + // lastPage could be computed from groups.length and pageSize + const fetchInProgress = isLoading(groupsRequestState); + const canMoveForward = !fetchInProgress && (!lastPage || currentPage < lastPage); + // When going backward we already have the groups loaded, so no need to check if fetchInProgress + const canMoveBackward = currentPage > 1; + + const nextPage = useCallback(async () => { + if (canMoveForward) { + setCurrentPage((page) => page + 1); + } + }, [canMoveForward]); + + const previousPage = useCallback(async () => { + if (canMoveBackward) { + setCurrentPage((page) => page - 1); + } + }, [canMoveBackward]); + + // groups.length - pageSize to have one more page loaded to prevent flickering with loading state + // lastPage === undefined because 0 is falsy but a value which should stop fetching (e.g for broken data sources) + const shouldFetchNextPage = groups.length - pageSize < pageSize * currentPage && lastPage === undefined; + + if (shouldFetchNextPage && !fetchInProgress) { + fetchMoreGroups(pageSize); + } + + const groupsPage = useMemo(() => { + return groups.slice((currentPage - 1) * pageSize, currentPage * pageSize); + }, [groups, currentPage, pageSize]); + + return { isLoading: fetchInProgress, page: groupsPage, nextPage, previousPage, canMoveForward, canMoveBackward }; +} diff --git a/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusRuleNamespaces.tsx b/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusRuleNamespaces.tsx deleted file mode 100644 index ab4bd2ca42b..00000000000 --- a/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusRuleNamespaces.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { usePrevious } from 'react-use'; - -import { PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; - -import { groupRulesByFileName } from '../../api/prometheus'; -import { prometheusApi } from '../../api/prometheusApi'; -import { isLoading, useAsync } from '../../hooks/useAsync'; -import { getDatasourceAPIUid } from '../../utils/datasource'; - -const { useLazyGroupsQuery } = prometheusApi; - -export function usePaginatedPrometheusRuleNamespaces(ruleSourceName: string, pageSize: number) { - const [currentPage, setCurrentPage] = useState(1); - const [groups, setGroups] = useState([]); - const [lastPage, setLastPage] = useState(undefined); - - const { groupsGenerator } = usePrometheusGroupsGenerator(ruleSourceName, pageSize); - - const [{ execute: fetchMoreGroups }, groupsRequestState] = useAsync(async (groupsCount: number) => { - let done = false; - const currentGroups: PromRuleGroupDTO[] = []; - - while (currentGroups.length < groupsCount) { - const group = await groupsGenerator.next(); - if (group.done) { - done = true; - break; - } - - currentGroups.push(group.value); - } - - if (done) { - const groupsTotal = groups.length + currentGroups.length; - setLastPage(Math.ceil(groupsTotal / pageSize)); - } - - setGroups((groups) => [...groups, ...currentGroups]); - }); - - const fetchInProgress = isLoading(groupsRequestState); - const canMoveForward = !fetchInProgress && (!lastPage || currentPage < lastPage); - const canMoveBackward = currentPage > 1 && !fetchInProgress; - - const nextPage = useCallback(async () => { - if (canMoveForward) { - setCurrentPage((page) => page + 1); - } - }, [canMoveForward]); - - const previousPage = useCallback(async () => { - if (canMoveBackward) { - setCurrentPage((page) => page - 1); - } - }, [canMoveBackward]); - - // groups.length - pageSize to have one more page loaded to prevent flickering with loading state - // lastPage === undefined because 0 is falsy but a value which should stop fetching (e.g for broken data sources) - const shouldFetchNextPage = groups.length - pageSize < pageSize * currentPage && lastPage === undefined; - - if (shouldFetchNextPage && !fetchInProgress) { - fetchMoreGroups(pageSize); - } - - const pageNamespaces = useMemo(() => { - const pageGroups = groups.slice((currentPage - 1) * pageSize, currentPage * pageSize); - // groupRulesByFileName mutates the array and RTKQ query freezes the response data - return groupRulesByFileName(structuredClone(pageGroups), ruleSourceName); - }, [groups, ruleSourceName, currentPage, pageSize]); - - return { isLoading: fetchInProgress, page: pageNamespaces, nextPage, previousPage, canMoveForward, canMoveBackward }; -} - -function usePrometheusGroupsGenerator(ruleSourceName: string, pageSize: number) { - const [fetchGroups, { isLoading }] = useLazyGroupsQuery(); - - const prevRuleSourceName = usePrevious(ruleSourceName); - // Generator lazily provides groups one by one only when needed - // This might look a bit complex but it allows us to have one API for paginated and non-paginated Prometheus data sources - // For unpaginated data sources we just fetch everything in one go - // For paginated we fetch the next page when needed - const getGroups = useCallback( - async function* (ruleSourceName: string, maxGroups: number) { - const ruleSourceUid = getDatasourceAPIUid(ruleSourceName); - - const response = await fetchGroups({ - ruleSource: { uid: ruleSourceUid }, - groupLimit: maxGroups, - }); - - if (!response.isSuccess) { - return; - } - - if (response.data?.data) { - yield* response.data.data.groups; - } - - let lastToken: string | undefined = undefined; - if (response.data?.data?.groupNextToken) { - lastToken = response.data.data.groupNextToken; - } - - while (lastToken) { - const response = await fetchGroups({ - ruleSource: { uid: ruleSourceUid }, - groupNextToken: lastToken, - groupLimit: maxGroups, - }); - - if (!response.isSuccess) { - return; - } - - if (response.data?.data) { - yield* response.data.data.groups; - } - - lastToken = response.data?.data?.groupNextToken; - } - }, - [fetchGroups] - ); - - const [groupsGenerator, setGroupsGenerator] = useState>( - getGroups(ruleSourceName, pageSize) - ); - - const resetGenerator = useCallback(() => { - setGroupsGenerator(getGroups(ruleSourceName, pageSize)); - }, [ruleSourceName, getGroups, pageSize]); - - if (prevRuleSourceName && prevRuleSourceName !== ruleSourceName) { - resetGenerator(); - } - - useEffect(() => { - const currentGenerator = groupsGenerator; - return () => { - currentGenerator.return(); - }; - }, [groupsGenerator]); - - return { groupsGenerator, isLoading }; -} diff --git a/public/app/features/alerting/unified/utils/datasource.ts b/public/app/features/alerting/unified/utils/datasource.ts index 9acc000ec59..5262000a258 100644 --- a/public/app/features/alerting/unified/utils/datasource.ts +++ b/public/app/features/alerting/unified/utils/datasource.ts @@ -10,7 +10,8 @@ import { } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import { - ExternalRulesSourceIdentifier, + DataSourceRulesSourceIdentifier as DataSourceRulesSourceIdentifier, + GrafanaRulesSourceIdentifier, GrafanaRulesSourceSymbol, RulesSource, RulesSourceUid, @@ -28,6 +29,12 @@ import { getAllDataSources } from './config'; export const GRAFANA_RULES_SOURCE_NAME = 'grafana'; export const GRAFANA_DATASOURCE_NAME = '-- Grafana --'; +export const GrafanaRulesSource: GrafanaRulesSourceIdentifier = { + uid: GrafanaRulesSourceSymbol, + name: GRAFANA_RULES_SOURCE_NAME, + ruleSourceType: 'grafana', +}; + export enum DataSourceType { Alertmanager = 'alertmanager', Loki = 'loki', @@ -214,10 +221,11 @@ export function getAllRulesSourceNames(): string[] { return availableRulesSources; } -export function getExternalRulesSources(): ExternalRulesSourceIdentifier[] { +export function getExternalRulesSources(): DataSourceRulesSourceIdentifier[] { return getRulesDataSources().map((ds) => ({ name: ds.name, uid: ds.uid, + ruleSourceType: 'datasource', })); } diff --git a/public/app/features/alerting/unified/utils/groupIdentifier.ts b/public/app/features/alerting/unified/utils/groupIdentifier.ts index 8f8a1e48b2d..1474b421c31 100644 --- a/public/app/features/alerting/unified/utils/groupIdentifier.ts +++ b/public/app/features/alerting/unified/utils/groupIdentifier.ts @@ -6,7 +6,7 @@ import { isGrafanaRulerRule } from './rules'; function fromCombinedRule(rule: CombinedRule): RuleGroupIdentifierV2 { if (isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulesSource(rule.namespace.rulesSource)) { return { - rulesSource: { uid: GrafanaRulesSourceSymbol, name: GRAFANA_RULES_SOURCE_NAME }, + rulesSource: { uid: GrafanaRulesSourceSymbol, name: GRAFANA_RULES_SOURCE_NAME, ruleSourceType: 'grafana' }, namespace: { uid: rule.rulerRule.grafana_alert.namespace_uid }, groupName: rule.group.name, groupOrigin: 'grafana', @@ -16,7 +16,7 @@ function fromCombinedRule(rule: CombinedRule): RuleGroupIdentifierV2 { const rulesSourceName = getRulesSourceName(rule.namespace.rulesSource); const rulesSourceUid = getDatasourceAPIUid(rulesSourceName); return { - rulesSource: { uid: rulesSourceUid, name: rulesSourceName }, + rulesSource: { uid: rulesSourceUid, name: rulesSourceName, ruleSourceType: 'datasource' }, namespace: { name: rule.namespace.name }, groupName: rule.group.name, groupOrigin: 'datasource', diff --git a/public/app/features/auth-config/fields.tsx b/public/app/features/auth-config/fields.tsx index d11d4eb9707..4ac00ed7c21 100644 --- a/public/app/features/auth-config/fields.tsx +++ b/public/app/features/auth-config/fields.tsx @@ -258,7 +258,7 @@ export function fieldMap(provider: string): Record { description: 'The client authentication method used to authenticate to the token endpoint.', multi: false, options: clientAuthenticationOptions(provider), - defaultValue: { value: 'client_secret_post', label: 'Client secret' }, + defaultValue: { value: 'none', label: 'None' }, validation: { required: true, message: 'This field is required', @@ -683,11 +683,15 @@ function clientAuthenticationOptions(provider: string): Array(overrides?: DeepPartial { let data: Record = {}; if (req.url.startsWith('/api/datasources/correlations') && req.method === 'GET') { diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/components/StreamingClientEditor.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/components/StreamingClientEditor.tsx index bd0abd4912e..954c0a2a1f8 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/components/StreamingClientEditor.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/components/StreamingClientEditor.tsx @@ -17,6 +17,7 @@ const types = [ { value: 'logs', label: 'Logs' }, { value: 'fetch', label: 'Fetch' }, { value: 'traces', label: 'Traces' }, + { value: 'watch', label: 'Watch' }, ]; export const StreamingClientEditor = ({ onChange, query }: EditorProps) => { @@ -34,7 +35,7 @@ export const StreamingClientEditor = ({ onChange, query }: EditorProps) => { const fields = streamType === 'signal' ? streamingClientFields - : ['logs', 'traces'].includes(streamType) + : ['logs', 'traces', 'watch'].includes(streamType) ? [streamingClientFields[0]] // speed : []; diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts b/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts index 119660af972..620f5233b0a 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts +++ b/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts @@ -41,7 +41,7 @@ export interface StreamingQuery { noise: number; speed: number; spread: number; - type: 'signal' | 'logs' | 'fetch' | 'traces'; + type: 'signal' | 'logs' | 'fetch' | 'traces' | 'watch'; url?: string; } diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts b/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts index 49a2522d997..26a6af2449b 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts +++ b/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts @@ -1,5 +1,5 @@ import { defaults } from 'lodash'; -import { Observable } from 'rxjs'; +import { Observable, throwError } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { @@ -18,6 +18,7 @@ import { getDisplayProcessor, createTheme, } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; import { getRandomLine } from './LogIpsum'; import { TestDataDataQuery, StreamingQuery } from './dataquery'; @@ -44,6 +45,8 @@ export function runStream( return runFetchStream(target, query, req); case 'traces': return runTracesStream(target, query, req); + case 'watch': + return runWatchStream(target, query, req); } throw new Error(`Unknown Stream Type: ${query.type}`); } @@ -176,6 +179,89 @@ export function runLogsStream( }); } +interface StreamMessage { + message: number; // incrementing number + time: number; + value: number; +} + +export function runWatchStream( + target: TestDataDataQuery, + query: StreamingQuery, + req: DataQueryRequest +): Observable { + const uid = req.targets[0].datasource?.uid; + if (!uid) { + return throwError(() => new Error('expected datasource uid')); + } + + return new Observable((subscriber) => { + const streamId = `watch-${req.panelId || 'explore'}-${target.refId}`; + const data = new CircularDataFrame({ + append: 'tail', + capacity: req.maxDataPoints || 1000, + }); + data.refId = target.refId; + data.name = target.alias || 'Logs ' + target.refId; + data.addField({ name: 'time', type: FieldType.time }); + data.addField({ name: 'message', type: FieldType.number }); + data.addField({ name: 'value', type: FieldType.number }); + const decoder = new TextDecoder(); + + const sub = getBackendSrv() + .chunked({ + url: `api/datasources/uid/${uid}/resources/stream`, + params: { + count: req.maxDataPoints || 1000, // connection will close when done + format: 'json', + speed: `${query.speed ?? 250}ms`, + flush: 85, // 85% (eg, sometimes send a few at a time) + }, + }) + .subscribe({ + next: (chunk) => { + if (!chunk.data || !chunk.ok) { + console.info('chunk missing data', chunk); + return; + } + decoder + .decode(chunk.data, { stream: true }) + .split('\n') + .forEach((line) => { + if (line?.length) { + try { + const msg: StreamMessage = JSON.parse(line); + + data.fields[0].values.push(msg.time); + data.fields[1].values.push(msg.message); + data.fields[2].values.push(msg.value); + + subscriber.next({ + data: [data], + key: streamId, + state: LoadingState.Streaming, + }); + } catch (err) { + console.warn('error parsing line', line, err); + } + } + }); + }, + error: (err) => { + console.warn('error in stream', streamId, err); + }, + complete: () => { + console.info('complete stream', streamId); + }, + }); + + return () => { + console.log('unsubscribing to stream', streamId); + sub.unsubscribe(); + }; + }); +} + export function runFetchStream( target: TestDataDataQuery, query: StreamingQuery, diff --git a/public/app/plugins/datasource/loki/querySplitting.test.ts b/public/app/plugins/datasource/loki/querySplitting.test.ts index 57f9859ea6c..5920b08fcdd 100644 --- a/public/app/plugins/datasource/loki/querySplitting.test.ts +++ b/public/app/plugins/datasource/loki/querySplitting.test.ts @@ -64,9 +64,20 @@ describe('runSplitQuery()', () => { }); test('Splits datasource queries', async () => { - await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => { + await expect(runSplitQuery(datasource, request)).toEmitValuesWith((emitted) => { // 3 days, 3 chunks, 3 requests. expect(datasource.runQuery).toHaveBeenCalledTimes(3); + // 3 sub-requests + complete + expect(emitted).toHaveLength(4); + }); + }); + + test('Skips partial updates as an option', async () => { + await expect(runSplitQuery(datasource, request, { skipPartialUpdates: true })).toEmitValuesWith((emitted) => { + // 3 days, 3 chunks, 3 requests. + expect(datasource.runQuery).toHaveBeenCalledTimes(3); + // partial updates skipped + expect(emitted).toHaveLength(1); }); }); @@ -80,6 +91,16 @@ describe('runSplitQuery()', () => { }); }); + test('Does not retry failed queries as an option', async () => { + jest + .mocked(datasource.runQuery) + .mockReturnValueOnce(of({ state: LoadingState.Error, errors: [{ refId: 'A', message: 'timeout' }], data: [] })); + await expect(runSplitQuery(datasource, request, { disableRetry: true })).toEmitValuesWith(() => { + // No retries + expect(datasource.runQuery).toHaveBeenCalledTimes(1); + }); + }); + test('Does not retry on other errors', async () => { jest .mocked(datasource.runQuery) diff --git a/public/app/plugins/datasource/loki/querySplitting.ts b/public/app/plugins/datasource/loki/querySplitting.ts index 262b8708289..d5ff1c870df 100644 --- a/public/app/plugins/datasource/loki/querySplitting.ts +++ b/public/app/plugins/datasource/loki/querySplitting.ts @@ -48,6 +48,17 @@ export function partitionTimeRange( }); } +interface QuerySplittingOptions { + /** + * Tells the query splitting code to not emit partial updates. Only emit on error or when it finishes querying. + */ + skipPartialUpdates?: boolean; + /** + * Do not retry failed queries. + */ + disableRetry?: boolean; +} + /** * Based in the state of the current response, if any, adjust target parameters such as `maxLines`. * For `maxLines`, we will update it as `maxLines - current amount of lines`. @@ -76,7 +87,11 @@ export function adjustTargetsFromResponseState(targets: LokiQuery[], response: D }) .filter((target) => target.maxLines === undefined || target.maxLines > 0); } -export function runSplitGroupedQueries(datasource: LokiDatasource, requests: LokiGroupedRequest[]) { +export function runSplitGroupedQueries( + datasource: LokiDatasource, + requests: LokiGroupedRequest[], + options: QuerySplittingOptions = {} +) { const responseKey = requests.length ? requests[0].request.queryGroupId : uuidv4(); let mergedResponse: DataQueryResponse = { data: [], state: LoadingState.Streaming, key: responseKey }; const totalRequests = Math.max(...requests.map(({ partition }) => partition.length)); @@ -116,6 +131,9 @@ export function runSplitGroupedQueries(datasource: LokiDatasource, requests: Lok }; const retry = (errorResponse?: DataQueryResponse) => { + if (options.disableRetry) { + return false; + } try { if (errorResponse && !isRetriableError(errorResponse)) { return false; @@ -170,13 +188,17 @@ export function runSplitGroupedQueries(datasource: LokiDatasource, requests: Lok shouldStop = true; } mergedResponse = combineResponses(mergedResponse, partialResponse); - mergedResponse = updateLoadingFrame(mergedResponse, subRequest, longestPartition, requestN); + if (!options.skipPartialUpdates) { + mergedResponse = updateLoadingFrame(mergedResponse, subRequest, longestPartition, requestN); + } }, complete: () => { if (retrying) { return; } - subscriber.next(mergedResponse); + if (!options.skipPartialUpdates) { + subscriber.next(mergedResponse); + } nextRequest(); }, error: (error) => { @@ -268,7 +290,11 @@ function querySupportsSplitting(query: LokiQuery) { ); } -export function runSplitQuery(datasource: LokiDatasource, request: DataQueryRequest) { +export function runSplitQuery( + datasource: LokiDatasource, + request: DataQueryRequest, + options: QuerySplittingOptions = {} +) { const queries = request.targets.filter((query) => !query.hide).filter((query) => query.expr); const [nonSplittingQueries, normalQueries] = partition(queries, (query) => !querySupportsSplitting(query)); const [logQueries, metricQueries] = partition(normalQueries, (query) => isLogsQuery(query.expr)); @@ -330,7 +356,7 @@ export function runSplitQuery(datasource: LokiDatasource, request: DataQueryRequ } const startTime = new Date(); - return runSplitGroupedQueries(datasource, requests).pipe( + return runSplitGroupedQueries(datasource, requests, options).pipe( tap((response) => { if (response.state === LoadingState.Done) { trackGroupedQueries(response, requests, request, startTime, { diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts index ea5e09877e0..51b8bb308ee 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts @@ -66,6 +66,24 @@ describe('runShardSplitQuery()', () => { }); test('Splits datasource queries', async () => { + const querySplittingRange = { + from: dateTime('2023-02-08T05:00:00.000Z'), + to: dateTime('2023-02-10T06:00:00.000Z'), + raw: { + from: dateTime('2023-02-08T05:00:00.000Z'), + to: dateTime('2023-02-10T06:00:00.000Z'), + }, + }; + request = createRequest([{ expr: '$SELECTOR', refId: 'A', direction: LokiQueryDirection.Scan }], { + range: querySplittingRange, + }); + await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { + // 5 shards, 3 groups + empty shard group, 4 requests * 3 days, 3 chunks, 3 requests = 12 requests + expect(datasource.runQuery).toHaveBeenCalledTimes(12); + }); + }); + + test('Users query splitting for querying over a day', async () => { await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { // 5 shards, 3 groups + empty shard group, 4 requests expect(datasource.runQuery).toHaveBeenCalledTimes(4); @@ -79,7 +97,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_0_2', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_0_2_1', targets: [ { expr: '{a="b", __stream_shard__=~"20|10"}', @@ -92,7 +111,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_2_2', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_2_2_1', targets: [ { expr: '{a="b", __stream_shard__=~"3|2"}', @@ -105,7 +125,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_4_1', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_4_1_1', targets: [ { expr: '{a="b", __stream_shard__="1"}', @@ -118,7 +139,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_5_1', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_5_1_1', targets: [ { expr: '{a="b", __stream_shard__=""}', @@ -147,7 +169,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_0_2', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_0_2_1', targets: [ { expr: '{service_name="test", filter="true", __stream_shard__=~"20|10"}', @@ -208,17 +231,6 @@ describe('runShardSplitQuery()', () => { }); test('Adjusts the group size based on errors and execution time', async () => { - const request = createRequest([{ expr: '$SELECTOR', refId: 'A', direction: LokiQueryDirection.Scan }], { - range: { - from: dateTime('2024-11-13T05:00:00.000Z'), - to: dateTime('2024-11-14T06:00:00.000Z'), - raw: { - from: dateTime('2024-11-13T05:00:00.000Z'), - to: dateTime('2024-11-14T06:00:00.000Z'), - }, - }, - }); - jest .mocked(datasource.languageProvider.fetchLabelValues) .mockResolvedValue(['1', '10', '2', '20', '3', '4', '5', '6', '7', '8', '9']); @@ -373,7 +385,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_0_3', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_0_3_1', targets: [ { expr: '{a="b", __stream_shard__=~"20|10|9"}', @@ -387,7 +400,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_3_4', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_3_4_1', targets: [ { expr: '{a="b", __stream_shard__=~"8|7|6|5"}', @@ -401,7 +415,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_3_2', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_3_2_1', targets: [ { expr: '{a="b", __stream_shard__=~"8|7"}', @@ -415,7 +430,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_5_3', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_5_3_1', targets: [ { expr: '{a="b", __stream_shard__=~"6|5|4"}', @@ -429,7 +445,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_8_2', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_8_2_1', targets: [ { expr: '{a="b", __stream_shard__=~"3|2"}', @@ -443,7 +460,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_10_1', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_10_1_1', targets: [ { expr: '{a="b", __stream_shard__="1"}', @@ -457,7 +475,8 @@ describe('runShardSplitQuery()', () => { expect(datasource.runQuery).toHaveBeenCalledWith({ intervalMs: expect.any(Number), range: expect.any(Object), - requestId: 'TEST_shard_0_11_1', + queryGroupId: expect.any(String), + requestId: 'TEST_shard_0_11_1_1', targets: [ { expr: '{a="b", __stream_shard__=""}', diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.ts index e159263640c..9f8dc397b8d 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.ts @@ -160,9 +160,10 @@ function splitQueriesByStreamShard( debug(shardsToQuery.length ? `Querying ${shardsToQuery.join(', ')}` : 'Running regular query'); - const queryRunner = - shardsToQuery.length > 0 ? datasource.runQuery.bind(datasource) : runSplitQuery.bind(null, datasource); - subquerySubscription = queryRunner(subRequest).subscribe({ + subquerySubscription = runSplitQuery(datasource, subRequest, { + skipPartialUpdates: true, + disableRetry: true, + }).subscribe({ next: (partialResponse: DataQueryResponse) => { if ((partialResponse.errors ?? []).length > 0 || partialResponse.error != null) { if (retry(partialResponse)) { diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 89c3626f355..1cc55fe038e 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -149,16 +149,31 @@ export interface PromRecordingRuleDTO extends PromRuleDTOBase { export type PromRuleDTO = PromAlertingRuleDTO | PromRecordingRuleDTO; -export interface PromRuleGroupDTO { +export interface PromRuleGroupDTO { name: string; file: string; - rules: PromRuleDTO[]; + rules: TRule[]; interval: number; evaluationTime?: number; // these 2 are not in older prometheus payloads lastEvaluation?: string; } +export interface GrafanaPromAlertingRuleDTO extends PromAlertingRuleDTO { + uid: string; + folderUid: string; +} + +export interface GrafanaPromRecordingRuleDTO extends PromRecordingRuleDTO { + uid: string; + folderUid: string; +} +export type GrafanaPromRuleDTO = GrafanaPromAlertingRuleDTO | GrafanaPromRecordingRuleDTO; + +export interface GrafanaPromRuleGroupDTO extends PromRuleGroupDTO { + folderUid: string; +} + export interface PromResponse { status: 'success' | 'error' | ''; // mocks return empty string data: T; diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index ccf4221e0a5..0e956d2cacf 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -2,7 +2,6 @@ import { AlertState, DataSourceInstanceSettings } from '@grafana/data'; import { PromOptions } from '@grafana/prometheus'; -import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { LokiOptions } from 'app/plugins/datasource/loki/types'; import { @@ -71,6 +70,18 @@ export interface RecordingRule extends RuleBase { export type Rule = AlertingRule | RecordingRule; +export interface GrafanaAlertingRule extends AlertingRule { + uid: string; + folderUid: string; +} + +export interface GrafanaRecordingRule extends RecordingRule { + uid: string; + folderUid: string; +} + +export type GrafanaRule = GrafanaAlertingRule | GrafanaRecordingRule; + export type BaseRuleGroup = { name: string }; type TotalsWithoutAlerting = Exclude; @@ -85,6 +96,18 @@ export interface RuleGroup { totals?: Partial>; } +export interface DataSourceRuleGroup { + id: DataSourceRuleGroupIdentifier; + interval: number; + rules: Rule[]; +} + +export interface DataSourceRuleNamespace { + rulesSource: DataSourceRulesSourceIdentifier; + id: DataSourceNamespaceIdentifier; + groups: DataSourceRuleGroup[]; +} + export interface RuleNamespace { dataSourceName: string; name: string; @@ -154,16 +177,20 @@ export interface RuleWithLocation { export const GrafanaRulesSourceSymbol = Symbol('grafana'); export type RulesSourceUid = string | typeof GrafanaRulesSourceSymbol; -export interface ExternalRulesSourceIdentifier { +export interface DataSourceRulesSourceIdentifier { uid: string; name: string; + // discriminator + ruleSourceType: 'datasource'; } export interface GrafanaRulesSourceIdentifier { uid: typeof GrafanaRulesSourceSymbol; - name: typeof GRAFANA_RULES_SOURCE_NAME; + name: 'grafana'; + // discriminator + ruleSourceType: 'grafana'; } -export type RulesSourceIdentifier = ExternalRulesSourceIdentifier | GrafanaRulesSourceIdentifier; +export type RulesSourceIdentifier = DataSourceRulesSourceIdentifier | GrafanaRulesSourceIdentifier; /** @deprecated use RuleGroupIdentifierV2 instead */ export interface RuleGroupIdentifier { @@ -189,7 +216,7 @@ export interface GrafanaRuleGroupIdentifier { } export interface DataSourceRuleGroupIdentifier { - rulesSource: ExternalRulesSourceIdentifier; + rulesSource: DataSourceRulesSourceIdentifier; groupName: string; namespace: DataSourceNamespaceIdentifier; groupOrigin: 'datasource'; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 73000e1594e..9f2a6569ae2 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -299,6 +299,13 @@ } } }, + "group-actions": { + "actions-trigger": "Rule group actions", + "delete": "Delete", + "edit": "Edit", + "export": "Export", + "reorder": "Re-order rules" + }, "list-view": { "empty": { "new-alert-rule": "New alert rule", @@ -484,11 +491,22 @@ }, "rule-list": { "configure-datasource": "Configure", + "ds-error-boundary": { + "description": "Check the data source configuration. Does the data source support Prometheus API?", + "title": "Unable to load rules from this data source" + }, "filter-view": { "no-more-results": "No more results – showing {{numberOfRules}} rules", "no-rules-found": "No alert or recording rules matched your current set of filters." }, - "new-alert-rule": "New alert rule" + "new-alert-rule": "New alert rule", + "pagination": { + "next-page": "next page", + "previous-page": "previous page" + }, + "return-button": { + "title": "Alert rules" + } }, "rule-state": { "creating": "Creating", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 0fbb5878033..118e80758e0 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -299,6 +299,13 @@ } } }, + "group-actions": { + "actions-trigger": "Ŗūľę ģřőūp äčŧįőʼnş", + "delete": "Đęľęŧę", + "edit": "Ēđįŧ", + "export": "Ēχpőřŧ", + "reorder": "Ŗę-őřđęř řūľęş" + }, "list-view": { "empty": { "new-alert-rule": "Ńęŵ äľęřŧ řūľę", @@ -484,11 +491,22 @@ }, "rule-list": { "configure-datasource": "Cőʼnƒįģūřę", + "ds-error-boundary": { + "description": "Cĥęčĸ ŧĥę đäŧä şőūřčę čőʼnƒįģūřäŧįőʼn. Đőęş ŧĥę đäŧä şőūřčę şūppőřŧ Přőmęŧĥęūş ÅPĨ?", + "title": "Ůʼnäþľę ŧő ľőäđ řūľęş ƒřőm ŧĥįş đäŧä şőūřčę" + }, "filter-view": { "no-more-results": "Ńő mőřę řęşūľŧş – şĥőŵįʼnģ {{numberOfRules}} řūľęş", "no-rules-found": "Ńő äľęřŧ őř řęčőřđįʼnģ řūľęş mäŧčĥęđ yőūř čūřřęʼnŧ şęŧ őƒ ƒįľŧęřş." }, - "new-alert-rule": "Ńęŵ äľęřŧ řūľę" + "new-alert-rule": "Ńęŵ äľęřŧ řūľę", + "pagination": { + "next-page": "ʼnęχŧ päģę", + "previous-page": "přęvįőūş päģę" + }, + "return-button": { + "title": "Åľęřŧ řūľęş" + } }, "rule-state": { "creating": "Cřęäŧįʼnģ", diff --git a/scripts/webpack/env-util.js b/scripts/webpack/env-util.js index 5d49fd4c8dc..c963f476fee 100644 --- a/scripts/webpack/env-util.js +++ b/scripts/webpack/env-util.js @@ -1,3 +1,4 @@ +// @ts-check const { parse } = require('ini'); const { readFileSync, existsSync } = require('node:fs'); const path = require('path'); @@ -18,14 +19,17 @@ const getEnvConfig = () => { const custom = parse(customSettings); const merged = { ...defaults.frontend_dev, ...custom.frontend_dev }; + // Take all frontend keys from the ini file and prefix with `frontend_dev_`, // so they can be added to `process.env` elsewhere - return Object.entries(merged).reduce((acc, [key, value]) => { - return { - ...acc, - [`frontend_dev_${key}`]: value, - }; - }, {}); + /** @type {Record} */ + const env = {}; + + for (const [key, value] of Object.entries(merged)) { + env[`frontend_dev_${key}`] = value; + } + + return env; }; module.exports = getEnvConfig; diff --git a/yarn.lock b/yarn.lock index e24ff7fc2c3..582c3493fc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29753,11 +29753,9 @@ __metadata: linkType: hard "ua-parser-js@npm:^1.0.32": - version: 1.0.40 - resolution: "ua-parser-js@npm:1.0.40" - bin: - ua-parser-js: script/cli.js - checksum: 10/7fced5f74ed570c83addffd4d367888d90c58803ff4bdd4a7b04b3f01d293263b8605e92ac560eb1c6a201ef3b11fcc46f3dbcbe764fbe54974924d542bc0135 + version: 1.0.33 + resolution: "ua-parser-js@npm:1.0.33" + checksum: 10/26455b1bbc165eea24b92283034e1e941ca5e272fcbb0a81ce2d064da369353182a1f6eb7472a6d2e738dd89b0cd008de9f1f17946f9b8f5ddfcf4e8a5949c1a languageName: node linkType: hard @@ -29769,11 +29767,11 @@ __metadata: linkType: hard "uglify-js@npm:^3.1.4": - version: 3.19.3 - resolution: "uglify-js@npm:3.19.3" + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" bin: uglifyjs: bin/uglifyjs - checksum: 10/6b9639c1985d24580b01bb0ab68e78de310d38eeba7db45bec7850ab4093d8ee464d80ccfaceda9c68d1c366efbee28573b52f95e69ac792354c145acd380b11 + checksum: 10/4c0b800e0ff192079d2c3ce8414fd3b656a570028c7c79af5c29c53d5c532b68bbcae4ad47307f89c2ee124d11826fff7a136b59d5c5bb18422bcdf5568afe1e languageName: node linkType: hard