Merge remote-tracking branch 'origin/main' into alerting/llm-experiment

pull/107367/head
Sonia Aguilar 2 weeks ago
commit 9e441a5139
  1. 164
      .betterer.results
  2. 4
      e2e/cloud-plugins-suite/azure-monitor.spec.ts
  3. 4
      package.json
  4. 2
      packages/grafana-data/src/types/featureToggles.gen.ts
  5. 3
      packages/grafana-runtime/src/utils/DataSourceWithBackend.ts
  6. 2
      pkg/login/social/connectors/gitlab_oauth.go
  7. 35
      pkg/login/social/connectors/gitlab_oauth_test.go
  8. 4
      pkg/services/featuremgmt/registry.go
  9. 2
      pkg/services/featuremgmt/toggles_gen.csv
  10. 11
      pkg/services/featuremgmt/toggles_gen.json
  11. 2
      public/app/core/components/NestedFolderPicker/NestedFolderList.tsx
  12. 77
      public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx
  13. 5
      public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx
  14. 108
      public/app/features/alerting/unified/components/rules/RulesTable.test.tsx
  15. 172
      public/app/features/alerting/unified/hooks/useAbilities.ts
  16. 8
      public/app/features/alerting/unified/rule-list/FilterView.tsx
  17. 182
      public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx
  18. 98
      public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx
  19. 71
      public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
  20. 150
      public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx
  21. 74
      public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx
  22. 8
      public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts
  23. 4
      public/app/features/alerting/unified/utils/rules.ts
  24. 3
      public/app/features/browse-dashboards/BrowseDashboardsPage.tsx
  25. 3
      public/app/features/browse-dashboards/RecentlyDeletedPage.tsx
  26. 2
      public/app/features/browse-dashboards/api/browseDashboardsAPI.ts
  27. 3
      public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
  28. 8
      public/app/features/browse-dashboards/components/BrowseView.tsx
  29. 2
      public/app/features/browse-dashboards/components/NameCell.tsx
  30. 3
      public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx
  31. 3
      public/app/features/browse-dashboards/components/SearchView.tsx
  32. 3
      public/app/features/browse-dashboards/state/index.ts
  33. 2
      public/app/features/canvas/element.ts
  34. 2
      public/app/features/canvas/elements/button.tsx
  35. 2
      public/app/features/canvas/elements/cloud.tsx
  36. 4
      public/app/features/canvas/elements/droneFront.tsx
  37. 4
      public/app/features/canvas/elements/droneSide.tsx
  38. 4
      public/app/features/canvas/elements/droneTop.tsx
  39. 5
      public/app/features/canvas/elements/icon.tsx
  40. 2
      public/app/features/canvas/elements/parallelogram.tsx
  41. 5
      public/app/features/canvas/elements/server/server.tsx
  42. 2
      public/app/features/canvas/elements/triangle.tsx
  43. 4
      public/app/features/canvas/elements/windTurbine.tsx
  44. 2
      public/app/features/canvas/runtime/element.tsx
  45. 2
      public/app/features/canvas/runtime/frame.tsx
  46. 2
      public/app/features/canvas/runtime/scene.tsx
  47. 14
      public/app/features/connections/Connections.tsx
  48. 2
      public/app/features/connections/pages/DataSourcesListPage.tsx
  49. 6
      public/app/features/connections/pages/index.tsx
  50. 10
      public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx
  51. 2
      public/app/features/correlations/Forms/AddCorrelationForm.tsx
  52. 2
      public/app/features/correlations/Forms/EditCorrelationForm.tsx
  53. 2
      public/app/features/correlations/components/Wizard/index.ts
  54. 2
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  55. 40
      public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx
  56. 3
      public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx
  57. 2
      public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
  58. 13
      public/app/features/dashboard-scene/settings/VersionsEditView.tsx
  59. 2
      public/app/features/dashboard-scene/settings/annotations/index.tsx
  60. 5
      public/app/features/dashboard-scene/settings/version-history/index.ts
  61. 2
      public/app/features/dashboard/components/AnnotationSettings/index.tsx
  62. 3
      public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx
  63. 3
      public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx
  64. 9
      public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx
  65. 2
      public/app/features/dashboard/components/LinksSettings/index.tsx
  66. 2
      public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx
  67. 2
      public/app/features/dataframe-import/index.ts
  68. 2
      public/app/features/datasources/components/DataSourceDashboards.tsx
  69. 3
      public/app/features/datasources/components/DataSourcesList.tsx
  70. 3
      public/app/features/datasources/components/DataSourcesListHeader.tsx
  71. 6
      public/app/features/datasources/components/EditDataSource.tsx
  72. 2
      public/app/features/datasources/components/EditDataSourceActions.tsx
  73. 9
      public/app/features/datasources/components/NewDataSource.tsx
  74. 6
      public/app/features/datasources/components/picker/DataSourceModal.tsx
  75. 6
      public/app/features/datasources/state/index.ts
  76. 4
      public/app/features/dimensions/editors/ResourceDimensionEditor.tsx
  77. 6
      public/app/features/dimensions/editors/index.ts
  78. 9
      public/app/features/dimensions/index.ts
  79. 12
      public/app/features/dimensions/utils.ts
  80. 14
      public/app/features/explore/TraceView/TraceView.tsx
  81. 2
      public/app/features/explore/TraceView/components/CriticalPath/index.tsx
  82. 4
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test1.ts
  83. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test2.ts
  84. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test3.ts
  85. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test4.ts
  86. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test5.ts
  87. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test6.ts
  88. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test7.ts
  89. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test8.ts
  90. 3
      public/app/features/explore/TraceView/components/CriticalPath/testCases/test9.ts
  91. 2
      public/app/features/explore/TraceView/components/CriticalPath/utils/findLastFinishingChildSpan.tsx
  92. 2
      public/app/features/explore/TraceView/components/CriticalPath/utils/getChildOfSpans.tsx
  93. 2
      public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.test.ts
  94. 2
      public/app/features/explore/TraceView/components/CriticalPath/utils/sanitizeOverFlowingChildren.tsx
  95. 2
      public/app/features/explore/TraceView/components/ScrollManager.tsx
  96. 2
      public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx
  97. 2
      public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx
  98. 2
      public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx
  99. 2
      public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx
  100. 2
      public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1496,22 +1496,9 @@ exports[`better eslint`] = {
"public/app/features/browse-dashboards/components/NewFolderForm.tsx:5381": [ "public/app/features/browse-dashboards/components/NewFolderForm.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
], ],
"public/app/features/browse-dashboards/state/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
],
"public/app/features/connections/components/ConnectionsRedirectNotice/index.ts:5381": [ "public/app/features/connections/components/ConnectionsRedirectNotice/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"]
], ],
"public/app/features/connections/pages/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./AddNewConnectionPage\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./DataSourceDashboardsPage\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./DataSourceDetailsPage\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./DataSourcesListPage\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./EditDataSourcePage\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./NewDataSourcePage\`)", "5"]
],
"public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx:5381": [ "public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
], ],
@ -1565,10 +1552,6 @@ exports[`better eslint`] = {
"public/app/features/correlations/components/EmptyCorrelationsCTA.tsx:5381": [ "public/app/features/correlations/components/EmptyCorrelationsCTA.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
], ],
"public/app/features/correlations/components/Wizard/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"]
],
"public/app/features/correlations/mocks/useCorrelations.mocks.ts:5381": [ "public/app/features/correlations/mocks/useCorrelations.mocks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -1727,10 +1710,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"]
], ],
"public/app/features/dashboard-scene/settings/annotations/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"]
],
"public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx:5381": [ "public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
@ -1778,13 +1757,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"], [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.", "1"]
], ],
"public/app/features/dashboard-scene/settings/version-history/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./HistorySrv\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryHeader\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryTable\`)", "4"]
],
"public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/ConfigEmailSharing.tsx:5381": [ "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/ConfigEmailSharing.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
], ],
@ -1867,10 +1839,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"]
], ],
"public/app/features/dashboard/components/AnnotationSettings/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"]
],
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [ "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@ -1953,10 +1921,6 @@ exports[`better eslint`] = {
"public/app/features/dashboard/components/Inspector/PanelInspector.tsx:5381": [ "public/app/features/dashboard/components/Inspector/PanelInspector.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/features/dashboard/components/LinksSettings/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./LinkSettingsEdit\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./LinkSettingsList\`)", "1"]
],
"public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx:5381": [ "public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
@ -2225,10 +2189,6 @@ exports[`better eslint`] = {
[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"] [0, 0, 0, "Unexpected any. Specify a different type.", "3"]
], ],
"public/app/features/dataframe-import/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"]
],
"public/app/features/datasources/components/DataSourceTypeCard.tsx:5381": [ "public/app/features/datasources/components/DataSourceTypeCard.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"]
@ -2248,14 +2208,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"], [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.", "1"]
], ],
"public/app/features/datasources/state/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"]
],
"public/app/features/datasources/state/navModel.ts:5381": [ "public/app/features/datasources/state/navModel.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [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.", "1"]
@ -2296,24 +2248,6 @@ exports[`better eslint`] = {
"public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx:5381": [ "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
], ],
"public/app/features/dimensions/editors/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"]
],
"public/app/features/dimensions/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "7"]
],
"public/app/features/dimensions/scale.ts:5381": [ "public/app/features/dimensions/scale.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -2359,17 +2293,6 @@ exports[`better eslint`] = {
"public/app/features/explore/TraceView/components/demo/trace-generators.ts:5381": [ "public/app/features/explore/TraceView/components/demo/trace-generators.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/features/explore/TraceView/components/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./TraceTimelineViewer/SpanDetail/DetailState\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./TraceTimelineViewer\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./model/transform-trace-data\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./settings/SpanBarSettings\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./utils/filter-spans\`)", "5"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "7"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "8"]
],
"public/app/features/explore/TraceView/components/model/ddg/types.tsx:5381": [ "public/app/features/explore/TraceView/components/model/ddg/types.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./PathElem\`)", "0"] [0, 0, 0, "Do not re-export imported variable (\`./PathElem\`)", "0"]
], ],
@ -2390,21 +2313,9 @@ exports[`better eslint`] = {
"public/app/features/explore/TraceView/components/model/transform-trace-data.tsx:5381": [ "public/app/features/explore/TraceView/components/model/transform-trace-data.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/features/explore/TraceView/components/types/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`../settings/SpanBarSettings\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./TNil\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./TTraceTimeline\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./links\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "4"]
],
"public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx:5381": [ "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./DraggableManagerDemo\`)", "0"] [0, 0, 0, "Do not re-export imported variable (\`./DraggableManagerDemo\`)", "0"]
], ],
"public/app/features/explore/TraceView/components/utils/DraggableManager/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./DraggableManager\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./EUpdateTypes\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
],
"public/app/features/explore/TraceView/createSpanLink.tsx:5381": [ "public/app/features/explore/TraceView/createSpanLink.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"] [0, 0, 0, "Do not use any type assertions.", "1"]
@ -2599,23 +2510,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"]
], ],
"public/app/features/plugins/admin/components/Badges/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "4"]
],
"public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [ "public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts:5381": [ "public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./GetStartedWithPlugin\`)", "0"] [0, 0, 0, "Do not re-export imported variable (\`./GetStartedWithPlugin\`)", "0"]
], ],
"public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsButton\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "1"]
],
"public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -3139,10 +3039,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"] [0, 0, 0, "Unexpected any. Specify a different type.", "1"]
], ],
"public/app/plugins/datasource/azuremonitor/azureMetadata/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"]
],
"public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/index.tsx:5381": [ "public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./ArgQueryEditor\`)", "0"] [0, 0, 0, "Do not re-export imported variable (\`./ArgQueryEditor\`)", "0"]
], ],
@ -3224,11 +3120,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/azuremonitor/mocks/variables.ts:5381": [ "public/app/plugins/datasource/azuremonitor/mocks/variables.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/azuremonitor/types/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
],
"public/app/plugins/datasource/azuremonitor/types/query.ts:5381": [ "public/app/plugins/datasource/azuremonitor/types/query.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"],
@ -3260,23 +3151,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx:5381": [ "public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloud-monitoring/components/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./Aggregation\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./AliasBy\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./AlignmentFunction\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./Alignment\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationsHelp\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./Fields\`)", "5"],
[0, 0, 0, "Do not re-export imported variable (\`./GroupBy\`)", "6"],
[0, 0, 0, "Do not re-export imported variable (\`./LabelFilter\`)", "7"],
[0, 0, 0, "Do not re-export imported variable (\`./MQLQueryEditor\`)", "8"],
[0, 0, 0, "Do not re-export imported variable (\`./MetricQueryEditor\`)", "9"],
[0, 0, 0, "Do not re-export imported variable (\`./PeriodSelect\`)", "10"],
[0, 0, 0, "Do not re-export imported variable (\`./Preprocessor\`)", "11"],
[0, 0, 0, "Do not re-export imported variable (\`./Project\`)", "12"],
[0, 0, 0, "Do not re-export imported variable (\`./SLOQueryEditor\`)", "13"],
[0, 0, 0, "Do not re-export imported variable (\`./VisualMetricQueryEditor\`)", "14"]
],
"public/app/plugins/datasource/cloud-monitoring/datasource.ts:5381": [ "public/app/plugins/datasource/cloud-monitoring/datasource.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "1"],
@ -3374,16 +3248,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./commentOnlyQuery\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./empty\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./filterQuery\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./newCommandQuery\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "5"],
[0, 0, 0, "Do not re-export imported variable (\`./sortQuery\`)", "6"],
[0, 0, 0, "Do not re-export imported variable (\`./whitespaceQuery\`)", "7"]
],
"public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -3426,13 +3290,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "16"], [0, 0, 0, "Do not use any type assertions.", "16"],
[0, 0, 0, "Do not use any type assertions.", "17"] [0, 0, 0, "Do not use any type assertions.", "17"]
], ],
"public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./multiLineIncompleteQueryWithoutNamespace\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./singleLineTwoQueries\`)", "4"]
],
"public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -3454,24 +3311,12 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./afterLabelValue\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./insideLabelValue\`)", "1"]
],
"public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./afterFunctionQuery\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./secondArgAfterSearchQuery\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./secondArgQuery\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./thirdArgAfterSearchQuery\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./withinStringQuery\`)", "5"]
],
"public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts:5381": [ "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
@ -3579,11 +3424,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx:5381": [ "public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/elasticsearch/types.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./dataquery.gen\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`ElasticsearchQuery\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
],
"public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx:5381": [ "public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
@ -3624,10 +3464,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx:5381": [ "public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/datasource/grafana-testdata-datasource/components/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./RandomWalkEditor\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./StreamingClientEditor\`)", "1"]
],
"public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts:5381": [ "public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [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.", "1"]

@ -5,11 +5,11 @@ import { v4 as uuidv4 } from 'uuid';
import { selectors as rawSelectors } from '@grafana/e2e-selectors'; import { selectors as rawSelectors } from '@grafana/e2e-selectors';
import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors'; import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors';
import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/types/query';
import { import {
AzureMonitorDataSourceJsonData, AzureMonitorDataSourceJsonData,
AzureMonitorDataSourceSecureJsonData, AzureMonitorDataSourceSecureJsonData,
AzureQueryType, } from '../../public/app/plugins/datasource/azuremonitor/types/types';
} from '../../public/app/plugins/datasource/azuremonitor/types';
import { e2e } from '../utils'; import { e2e } from '../utils';
const provisioningPath = `provisioning/datasources/azmonitor-ds.yaml`; const provisioningPath = `provisioning/datasources/azmonitor-ds.yaml`;

@ -380,7 +380,7 @@
"react": "18.3.1", "react": "18.3.1",
"react-diff-viewer-continued": "^3.4.0", "react-diff-viewer-continued": "^3.4.0",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-draggable": "4.4.6", "react-draggable": "4.5.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch", "react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
"react-highlight-words": "0.21.0", "react-highlight-words": "0.21.0",
@ -416,7 +416,7 @@
"slate": "0.47.9", "slate": "0.47.9",
"slate-plain-serializer": "0.7.13", "slate-plain-serializer": "0.7.13",
"slate-react": "0.22.10", "slate-react": "0.22.10",
"swagger-ui-react": "5.25.2", "swagger-ui-react": "5.26.1",
"symbol-observable": "4.0.0", "symbol-observable": "4.0.0",
"systemjs": "6.15.1", "systemjs": "6.15.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",

@ -979,7 +979,7 @@ export interface FeatureToggles {
restoreDashboards?: boolean; restoreDashboards?: boolean;
/** /**
* Skip token rotation if it was already rotated less than 5 seconds ago * Skip token rotation if it was already rotated less than 5 seconds ago
* @default false * @default true
*/ */
skipTokenRotationIfRecent?: boolean; skipTokenRotationIfRecent?: boolean;
/** /**

@ -226,9 +226,6 @@ class DataSourceWithBackend<
if (!(config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs)) { if (!(config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs)) {
console.warn('feature toggle queryServiceFromUI also requires the queryService to be running'); console.warn('feature toggle queryServiceFromUI also requires the queryService to be running');
} else { } else {
if (!hasExpr && dsUIDs.size === 1) {
// TODO? can we talk directly to the apiserver?
}
url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=${this.type}`; url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=${this.type}`;
} }
} }

@ -302,6 +302,8 @@ func (s *SocialGitlab) extractFromToken(ctx context.Context, client *http.Client
data.Groups = userInfo.Groups data.Groups = userInfo.Groups
} }
data.raw = rawJSON
s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data)) s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data))
return &data, nil return &data, nil
} }

@ -37,6 +37,8 @@ const (
rootUserRespBody = `{"id":1,"username":"root","name":"Administrator","state":"active","email":"root@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":true,"namespace_id":1}` rootUserRespBody = `{"id":1,"username":"root","name":"Administrator","state":"active","email":"root@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":true,"namespace_id":1}`
editorUserRespBody = `{"id":3,"username":"gitlab-editor","name":"Gitlab Editor","state":"active","email":"gitlab-editor@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":false,"namespace_id":1}` editorUserRespBody = `{"id":3,"username":"gitlab-editor","name":"Gitlab Editor","state":"active","email":"gitlab-editor@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":false,"namespace_id":1}`
editorUserIDToken = `{"sub":"3","preferred_username":"gitlab-editor","name":"Gitlab Editor","email":"gitlab-editor@example.org","email_verified":true,"groups_direct":["editors", "viewers"]}` // #nosec G101 not a hardcoded credential
adminGroup = `{"id":4,"web_url":"http://grafana-gitlab.local/groups/admins","name":"Admins","path":"admins","project_creation_level":"developer","full_name":"Admins","full_path":"admins","created_at":"2022-09-13T19:38:04.891Z"}` adminGroup = `{"id":4,"web_url":"http://grafana-gitlab.local/groups/admins","name":"Admins","path":"admins","project_creation_level":"developer","full_name":"Admins","full_path":"admins","created_at":"2022-09-13T19:38:04.891Z"}`
editorGroup = `{"id":5,"web_url":"http://grafana-gitlab.local/groups/editors","name":"Editors","path":"editors","project_creation_level":"developer","full_name":"Editors","full_path":"editors","created_at":"2022-09-13T19:38:15.074Z"}` editorGroup = `{"id":5,"web_url":"http://grafana-gitlab.local/groups/editors","name":"Editors","path":"editors","project_creation_level":"developer","full_name":"Editors","full_path":"editors","created_at":"2022-09-13T19:38:15.074Z"}`
viewerGroup = `{"id":6,"web_url":"http://grafana-gitlab.local/groups/viewers","name":"Viewers","path":"viewers","project_creation_level":"developer","full_name":"Viewers","full_path":"viewers","created_at":"2022-09-13T19:38:25.777Z"}` viewerGroup = `{"id":6,"web_url":"http://grafana-gitlab.local/groups/viewers","name":"Viewers","path":"viewers","project_creation_level":"developer","full_name":"Viewers","full_path":"viewers","created_at":"2022-09-13T19:38:25.777Z"}`
@ -61,6 +63,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
GroupsRespBody string GroupsRespBody string
GroupHeaders map[string]string GroupHeaders map[string]string
RoleAttributePath string RoleAttributePath string
IDToken string
ExpectedLogin string ExpectedLogin string
ExpectedEmail string ExpectedEmail string
ExpectedRoles map[int64]org.RoleType ExpectedRoles map[int64]org.RoleType
@ -180,6 +183,24 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
ExpectedEmail: "gitlab-editor@example.org", ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"}, ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"},
}, },
{
Name: "Maps roles from ID token attributes if available",
RoleAttributePath: `email=='gitlab-editor@example.org' && 'Editor' || 'Viewer'`,
IDToken: editorUserIDToken,
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{1: "Editor"},
ExpectedGrafanaAdmin: nilPointer,
},
{
Name: "Maps groups from ID token groups if available",
RoleAttributePath: gitlabAttrPath,
IDToken: editorUserIDToken,
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{1: "Editor"},
ExpectedGrafanaAdmin: nilPointer,
},
{ {
Name: "Should return error when neither role attribute path nor org mapping evaluates to a role and role attribute strict is enabled", Name: "Should return error when neither role attribute path nor org mapping evaluates to a role and role attribute strict is enabled",
Cfg: conf{RoleAttributeStrict: true, OrgMapping: []string{"other:Org4:Editor"}}, Cfg: conf{RoleAttributeStrict: true, OrgMapping: []string{"other:Org4:Editor"}},
@ -230,8 +251,17 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
require.Fail(t, "unexpected request URI: "+r.RequestURI) require.Fail(t, "unexpected request URI: "+r.RequestURI)
} }
})) }))
token := &oauth2.Token{}
if tt.IDToken != "" {
emptyJWTHeader := base64.RawURLEncoding.EncodeToString([]byte("{}"))
JWTBody := base64.RawURLEncoding.EncodeToString([]byte(tt.IDToken))
idToken := fmt.Sprintf("%s.%s.signature", emptyJWTHeader, JWTBody)
token = token.WithExtra(map[string]any{"id_token": idToken})
}
provider.info.ApiUrl = ts.URL + apiURI provider.info.ApiUrl = ts.URL + apiURI
actualResult, err := provider.UserInfo(context.Background(), ts.Client(), &oauth2.Token{}) actualResult, err := provider.UserInfo(context.Background(), ts.Client(), token)
if tt.ExpectedError != nil { if tt.ExpectedError != nil {
require.ErrorIs(t, err, tt.ExpectedError) require.ErrorIs(t, err, tt.ExpectedError)
return return
@ -382,6 +412,9 @@ func TestSocialGitlab_extractFromToken(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
if tc.wantUser != nil {
tc.wantUser.raw = []byte(tc.payload)
}
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Create a test client with a dummy token // Create a test client with a dummy token
client := oauth2.NewClient(context.Background(), &tokenSource{accessToken: "dummy_access_token"}) client := oauth2.NewClient(context.Background(), &tokenSource{accessToken: "dummy_access_token"})

@ -1678,11 +1678,11 @@ var (
{ {
Name: "skipTokenRotationIfRecent", Name: "skipTokenRotationIfRecent",
Description: "Skip token rotation if it was already rotated less than 5 seconds ago", Description: "Skip token rotation if it was already rotated less than 5 seconds ago",
Stage: FeatureStagePrivatePreview, Stage: FeatureStageGeneralAvailability,
Owner: identityAccessTeam, Owner: identityAccessTeam,
HideFromAdminPage: true, HideFromAdminPage: true,
HideFromDocs: true, HideFromDocs: true,
Expression: "false", Expression: "true", // enabled by default
}, },
{ {
Name: "alertEnrichment", Name: "alertEnrichment",

@ -219,7 +219,7 @@ alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,
alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true
kubernetesAuthzApis,experimental,@grafana/identity-access-team,false,false,false kubernetesAuthzApis,experimental,@grafana/identity-access-team,false,false,false
restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false
skipTokenRotationIfRecent,privatePreview,@grafana/identity-access-team,false,false,false skipTokenRotationIfRecent,GA,@grafana/identity-access-team,false,false,false
alertEnrichment,experimental,@grafana/alerting-squad,false,false,false alertEnrichment,experimental,@grafana/alerting-squad,false,false,false
alertingAIGenAlertRules,experimental,@grafana/alerting-squad,false,false,false alertingAIGenAlertRules,experimental,@grafana/alerting-squad,false,false,false
alertingAIImproveAlertRules,experimental,@grafana/alerting-squad,false,false,false alertingAIImproveAlertRules,experimental,@grafana/alerting-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
219 alertingBulkActionsInUI GA @grafana/alerting-squad false false true
220 kubernetesAuthzApis experimental @grafana/identity-access-team false false false
221 restoreDashboards experimental @grafana/grafana-frontend-platform false false false
222 skipTokenRotationIfRecent privatePreview GA @grafana/identity-access-team false false false
223 alertEnrichment experimental @grafana/alerting-squad false false false
224 alertingAIGenAlertRules experimental @grafana/alerting-squad false false false
225 alertingAIImproveAlertRules experimental @grafana/alerting-squad false false false

@ -2865,16 +2865,19 @@
{ {
"metadata": { "metadata": {
"name": "skipTokenRotationIfRecent", "name": "skipTokenRotationIfRecent",
"resourceVersion": "1750434297879", "resourceVersion": "1751872762065",
"creationTimestamp": "2025-06-03T06:59:40Z" "creationTimestamp": "2025-06-03T06:59:40Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-07-07 07:19:22.065046 +0000 UTC"
}
}, },
"spec": { "spec": {
"description": "Skip token rotation if it was already rotated less than 5 seconds ago", "description": "Skip token rotation if it was already rotated less than 5 seconds ago",
"stage": "privatePreview", "stage": "GA",
"codeowner": "@grafana/identity-access-team", "codeowner": "@grafana/identity-access-team",
"hideFromAdminPage": true, "hideFromAdminPage": true,
"hideFromDocs": true, "hideFromDocs": true,
"expression": "false" "expression": "true"
} }
}, },
{ {

@ -9,7 +9,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n'; import { Trans } from '@grafana/i18n';
import { IconButton, useStyles2, Text } from '@grafana/ui'; import { IconButton, useStyles2, Text } from '@grafana/ui';
import { Indent } from 'app/core/components/Indent/Indent'; import { Indent } from 'app/core/components/Indent/Indent';
import { childrenByParentUIDSelector, rootItemsSelector } from 'app/features/browse-dashboards/state'; import { childrenByParentUIDSelector, rootItemsSelector } from 'app/features/browse-dashboards/state/hooks';
import { DashboardsTreeItem } from 'app/features/browse-dashboards/types'; import { DashboardsTreeItem } from 'app/features/browse-dashboards/types';
import { DashboardViewItem } from 'app/features/search/types'; import { DashboardViewItem } from 'app/features/search/types';
import { useSelector } from 'app/types'; import { useSelector } from 'app/types';

@ -10,7 +10,12 @@ import { useRulePluginLinkExtension } from 'app/features/alerting/unified/plugin
import { Rule, RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting'; import { Rule, RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting';
import { PromAlertingRuleState, RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { PromAlertingRuleState, RulerRuleDTO } from 'app/types/unified-alerting-dto';
import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; import {
AlertRuleAction,
skipToken,
useGrafanaPromRuleAbilities,
useRulerRuleAbilities,
} from '../../hooks/useAbilities';
import { createShareLink, isLocalDevEnv, isOpenSourceEdition } from '../../utils/misc'; import { createShareLink, isLocalDevEnv, isOpenSourceEdition } from '../../utils/misc';
import * as ruleId from '../../utils/rule-id'; import * as ruleId from '../../utils/rule-id';
import { prometheusRuleType, rulerRuleType } from '../../utils/rules'; import { prometheusRuleType, rulerRuleType } from '../../utils/rules';
@ -33,6 +38,8 @@ interface Props {
/** /**
* Get a list of menu items + divider elements for rendering in an alert rule's * Get a list of menu items + divider elements for rendering in an alert rule's
* dropdown menu * dropdown menu
* If the consumer of this component comes from the alert list view, we need to use promRule to check abilities and permissions,
* as we have removed all requests to the ruler API in the list view.
*/ */
const AlertRuleMenu = ({ const AlertRuleMenu = ({
promRule, promRule,
@ -46,29 +53,51 @@ const AlertRuleMenu = ({
buttonSize, buttonSize,
fill, fill,
}: Props) => { }: Props) => {
// check all abilities and permissions // check all abilities and permissions using rulerRule
const [pauseSupported, pauseAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Pause); const [rulerPauseAbility, rulerDeleteAbility, rulerDuplicateAbility, rulerSilenceAbility, rulerExportAbility] =
const canPause = pauseSupported && pauseAllowed; useRulerRuleAbilities(rulerRule, groupIdentifier, [
AlertRuleAction.Pause,
const [deleteSupported, deleteAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Delete); AlertRuleAction.Delete,
const canDelete = deleteSupported && deleteAllowed; AlertRuleAction.Duplicate,
AlertRuleAction.Silence,
const [duplicateSupported, duplicateAllowed] = useRulerRuleAbility( AlertRuleAction.ModifyExport,
rulerRule, ]);
groupIdentifier,
AlertRuleAction.Duplicate // check all abilities and permissions using promRule
); const [
const canDuplicate = duplicateSupported && duplicateAllowed; grafanaPauseAbility,
grafanaDeleteAbility,
const [silenceSupported, silenceAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Silence); grafanaDuplicateAbility,
const canSilence = silenceSupported && silenceAllowed; grafanaSilenceAbility,
grafanaExportAbility,
const [exportSupported, exportAllowed] = useRulerRuleAbility( ] = useGrafanaPromRuleAbilities(prometheusRuleType.grafana.rule(promRule) ? promRule : skipToken, [
rulerRule, AlertRuleAction.Pause,
groupIdentifier, AlertRuleAction.Delete,
AlertRuleAction.ModifyExport AlertRuleAction.Duplicate,
); AlertRuleAction.Silence,
const canExport = exportSupported && exportAllowed; AlertRuleAction.ModifyExport,
]);
const [pauseSupported, pauseAllowed] = rulerPauseAbility;
const [grafanaPauseSupported, grafanaPauseAllowed] = grafanaPauseAbility;
const canPause = (pauseSupported && pauseAllowed) || (grafanaPauseSupported && grafanaPauseAllowed);
const [deleteSupported, deleteAllowed] = rulerDeleteAbility;
const [grafanaDeleteSupported, grafanaDeleteAllowed] = grafanaDeleteAbility;
const canDelete = (deleteSupported && deleteAllowed) || (grafanaDeleteSupported && grafanaDeleteAllowed);
const [duplicateSupported, duplicateAllowed] = rulerDuplicateAbility;
const [grafanaDuplicateSupported, grafanaDuplicateAllowed] = grafanaDuplicateAbility;
const canDuplicate =
(duplicateSupported && duplicateAllowed) || (grafanaDuplicateSupported && grafanaDuplicateAllowed);
const [silenceSupported, silenceAllowed] = rulerSilenceAbility;
const [grafanaSilenceSupported, grafanaSilenceAllowed] = grafanaSilenceAbility;
const canSilence = (silenceSupported && silenceAllowed) || (grafanaSilenceSupported && grafanaSilenceAllowed);
const [exportSupported, exportAllowed] = rulerExportAbility;
const [grafanaExportSupported, grafanaExportAllowed] = grafanaExportAbility;
const canExport = (exportSupported && exportAllowed) || (grafanaExportSupported && grafanaExportAllowed);
const ruleExtensionLinks = useRulePluginLinkExtension(promRule, groupIdentifier); const ruleExtensionLinks = useRulePluginLinkExtension(promRule, groupIdentifier);

@ -7,6 +7,7 @@ import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { useIsRuleEditable } from '../../hooks/useIsRuleEditable';
import { getCloudRule, getGrafanaRule } from '../../mocks'; import { getCloudRule, getGrafanaRule } from '../../mocks';
import { mimirDataSource } from '../../mocks/server/configure';
import { RuleDetails } from './RuleDetails'; import { RuleDetails } from './RuleDetails';
@ -32,6 +33,8 @@ const ui = {
setupMswServer(); setupMswServer();
const { dataSource: mimirDs } = mimirDataSource();
beforeAll(() => { beforeAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@ -81,7 +84,7 @@ describe('RuleDetails RBAC', () => {
}); });
describe('Cloud rules action buttons', () => { describe('Cloud rules action buttons', () => {
const cloudRule = getCloudRule({ name: 'Cloud' }); const cloudRule = getCloudRule({ name: 'Cloud' }, { rulesSource: mimirDs });
it('Should not render Edit button for users with the update permission', async () => { it('Should not render Edit button for users with the update permission', async () => {
// Arrange // Arrange

@ -4,7 +4,14 @@ import { byRole } from 'testing-library-selector';
import { setPluginLinksHook } from '@grafana/runtime'; import { setPluginLinksHook } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { AlertRuleAction, useAlertRuleAbility, useRulerRuleAbility } from '../../hooks/useAbilities'; import {
AlertRuleAction,
useAlertRuleAbility,
useGrafanaPromRuleAbilities,
useGrafanaPromRuleAbility,
useRulerRuleAbilities,
useRulerRuleAbility,
} from '../../hooks/useAbilities';
import { getCloudRule, getGrafanaRule } from '../../mocks'; import { getCloudRule, getGrafanaRule } from '../../mocks';
import { mimirDataSource } from '../../mocks/server/configure'; import { mimirDataSource } from '../../mocks/server/configure';
@ -13,11 +20,15 @@ import { RulesTable } from './RulesTable';
jest.mock('../../hooks/useAbilities'); jest.mock('../../hooks/useAbilities');
const mocks = { const mocks = {
// This is a bit unfortunate, but we need to mock both abilities // Mock the hooks that are actually used by the components:
// RuleActionButtons still needs to use the useAlertRuleAbility hook // RuleActionsButtons uses: useAlertRuleAbility (singular)
// whereas AlertRuleMenu has already been refactored to use useRulerRuleAbility // AlertRuleMenu uses: useRulerRuleAbilities and useGrafanaPromRuleAbilities (plural)
// We can also use useGrafanaPromRuleAbility (singular) for simpler mocking
useRulerRuleAbility: jest.mocked(useRulerRuleAbility), useRulerRuleAbility: jest.mocked(useRulerRuleAbility),
useAlertRuleAbility: jest.mocked(useAlertRuleAbility), useAlertRuleAbility: jest.mocked(useAlertRuleAbility),
useGrafanaPromRuleAbility: jest.mocked(useGrafanaPromRuleAbility),
useRulerRuleAbilities: jest.mocked(useRulerRuleAbilities),
useGrafanaPromRuleAbilities: jest.mocked(useGrafanaPromRuleAbilities),
}; };
setPluginLinksHook(() => ({ setPluginLinksHook(() => ({
@ -46,18 +57,40 @@ describe('RulesTable RBAC', () => {
jest.clearAllMocks(); jest.clearAllMocks();
jest.restoreAllMocks(); jest.restoreAllMocks();
jest.resetAllMocks(); jest.resetAllMocks();
// Set up default neutral mocks for all hooks
// Singular hooks (used by RuleActionsButtons and can simplify mocking)
mocks.useAlertRuleAbility.mockReturnValue([false, false]);
mocks.useRulerRuleAbility.mockReturnValue([false, false]);
mocks.useGrafanaPromRuleAbility.mockReturnValue([false, false]);
// Plural hooks (used by AlertRuleMenu) - need to return arrays based on input actions
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map(() => [false, false]);
});
mocks.useGrafanaPromRuleAbilities.mockImplementation((_rule, actions) => {
return actions.map(() => [false, false]);
});
}); });
describe('Grafana rules action buttons', () => { describe('Grafana rules action buttons', () => {
const grafanaRule = getGrafanaRule({ name: 'Grafana' }); const grafanaRule = getGrafanaRule({ name: 'Grafana' });
it('Should not render Edit button for users without the update permission', async () => { it('Should not render Edit button for users without the update permission', async () => {
mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { // Mock the specific hooks needed for Grafana rules
// Using singular hook for simpler mocking
mocks.useAlertRuleAbility.mockImplementation((rule, action) => {
return action === AlertRuleAction.Update ? [true, false] : [true, true]; return action === AlertRuleAction.Update ? [true, false] : [true, true];
}); });
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useGrafanaPromRuleAbility.mockImplementation((rule, action) => {
return action === AlertRuleAction.Update ? [true, false] : [true, true]; return action === AlertRuleAction.Update ? [true, false] : [true, true];
}); });
// Still need plural hook for AlertRuleMenu component
mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => {
return actions.map((action) => {
return action === AlertRuleAction.Update ? [true, false] : [true, true];
});
});
render(<RulesTable rules={[grafanaRule]} />); render(<RulesTable rules={[grafanaRule]} />);
@ -65,11 +98,14 @@ describe('RulesTable RBAC', () => {
}); });
it('Should not render Delete button for users without the delete permission', async () => { it('Should not render Delete button for users without the delete permission', async () => {
mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { // Mock the specific hooks needed for Grafana rules
mocks.useAlertRuleAbility.mockImplementation((rule, action) => {
return action === AlertRuleAction.Delete ? [true, false] : [true, true]; return action === AlertRuleAction.Delete ? [true, false] : [true, true];
}); });
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => {
return action === AlertRuleAction.Delete ? [true, false] : [true, true]; return actions.map((action) => {
return action === AlertRuleAction.Delete ? [true, false] : [true, true];
});
}); });
render(<RulesTable rules={[grafanaRule]} />); render(<RulesTable rules={[grafanaRule]} />);
@ -80,11 +116,14 @@ describe('RulesTable RBAC', () => {
}); });
it('Should render Edit button for users with the update permission', async () => { it('Should render Edit button for users with the update permission', async () => {
mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { // Mock the specific hooks needed for Grafana rules
mocks.useAlertRuleAbility.mockImplementation((rule, action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false]; return action === AlertRuleAction.Update ? [true, true] : [false, false];
}); });
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false]; return actions.map((action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false];
});
}); });
render(<RulesTable rules={[grafanaRule]} />); render(<RulesTable rules={[grafanaRule]} />);
@ -93,11 +132,14 @@ describe('RulesTable RBAC', () => {
}); });
it('Should render Delete button for users with the delete permission', async () => { it('Should render Delete button for users with the delete permission', async () => {
mocks.useRulerRuleAbility.mockImplementation((_rule, _groupIdentifier, action) => { // Mock the specific hooks needed for Grafana rules
mocks.useAlertRuleAbility.mockImplementation((rule, action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false]; return action === AlertRuleAction.Delete ? [true, true] : [false, false];
}); });
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useGrafanaPromRuleAbilities.mockImplementation((rule, actions) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false]; return actions.map((action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false];
});
}); });
render(<RulesTable rules={[grafanaRule]} />); render(<RulesTable rules={[grafanaRule]} />);
@ -123,11 +165,15 @@ describe('RulesTable RBAC', () => {
}; };
beforeEach(() => { beforeEach(() => {
mocks.useRulerRuleAbility.mockImplementation(() => { // Mock all hooks needed for the creating/deleting state tests
return [true, true]; mocks.useRulerRuleAbility.mockImplementation(() => [true, true]);
mocks.useAlertRuleAbility.mockImplementation(() => [true, true]);
// Mock plural hooks for AlertRuleMenu
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map(() => [true, true]);
}); });
mocks.useAlertRuleAbility.mockImplementation(() => { mocks.useGrafanaPromRuleAbilities.mockImplementation((_rule, actions) => {
return [true, true]; return actions.map(() => [true, true]);
}); });
}); });
@ -164,6 +210,12 @@ describe('RulesTable RBAC', () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Update ? [true, false] : [true, true]; return action === AlertRuleAction.Update ? [true, false] : [true, true];
}); });
// Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken)
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map((action) => {
return action === AlertRuleAction.Update ? [true, false] : [true, true];
});
});
render(<RulesTable rules={[cloudRule]} />); render(<RulesTable rules={[cloudRule]} />);
@ -177,6 +229,12 @@ describe('RulesTable RBAC', () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Delete ? [true, false] : [true, true]; return action === AlertRuleAction.Delete ? [true, false] : [true, true];
}); });
// Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken)
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map((action) => {
return action === AlertRuleAction.Delete ? [true, false] : [true, true];
});
});
render(<RulesTable rules={[cloudRule]} />); render(<RulesTable rules={[cloudRule]} />);
@ -191,6 +249,12 @@ describe('RulesTable RBAC', () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false]; return action === AlertRuleAction.Update ? [true, true] : [false, false];
}); });
// Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken)
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map((action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false];
});
});
render(<RulesTable rules={[cloudRule]} />); render(<RulesTable rules={[cloudRule]} />);
@ -204,6 +268,12 @@ describe('RulesTable RBAC', () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => { mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false]; return action === AlertRuleAction.Delete ? [true, true] : [false, false];
}); });
// Cloud rules only need useRulerRuleAbilities mock (useGrafanaPromRuleAbilities gets skipToken)
mocks.useRulerRuleAbilities.mockImplementation((_rule, _groupIdentifier, actions) => {
return actions.map((action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false];
});
});
render(<RulesTable rules={[cloudRule]} />); render(<RulesTable rules={[cloudRule]} />);

@ -14,15 +14,20 @@ import { useFolder } from 'app/features/alerting/unified/hooks/useFolder';
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
import { CombinedRule, RuleGroupIdentifierV2 } from 'app/types/unified-alerting'; import { CombinedRule, RuleGroupIdentifierV2 } from 'app/types/unified-alerting';
import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { GrafanaPromRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto';
import { alertmanagerApi } from '../api/alertmanagerApi'; import { alertmanagerApi } from '../api/alertmanagerApi';
import { useAlertmanager } from '../state/AlertmanagerContext'; import { useAlertmanager } from '../state/AlertmanagerContext';
import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissions } from '../utils/access-control'; import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissions } from '../utils/access-control';
import { getRulesSourceName } from '../utils/datasource'; import { getGroupOriginName, groupIdentifier } from '../utils/groupIdentifier';
import { getGroupOriginName } from '../utils/groupIdentifier';
import { isAdmin } from '../utils/misc'; import { isAdmin } from '../utils/misc';
import { isFederatedRuleGroup, isPluginProvidedRule, rulerRuleType } from '../utils/rules'; import {
isPluginProvidedRule,
isProvisionedPromRule,
isProvisionedRule,
prometheusRuleType,
rulerRuleType,
} from '../utils/rules';
import { useIsRuleEditable } from './useIsRuleEditable'; import { useIsRuleEditable } from './useIsRuleEditable';
@ -200,7 +205,7 @@ export function useRulerRuleAbility(
} }
export function useRulerRuleAbilities( export function useRulerRuleAbilities(
rule: RulerRuleDTO, rule: RulerRuleDTO | undefined,
groupIdentifier: RuleGroupIdentifierV2, groupIdentifier: RuleGroupIdentifierV2,
actions: AlertRuleAction[] actions: AlertRuleAction[]
): Ability[] { ): Ability[] {
@ -211,28 +216,35 @@ export function useRulerRuleAbilities(
}, [abilities, actions]); }, [abilities, actions]);
} }
// This hook is being called a lot in different places /**
// In some cases multiple times for ~80 rules (e.g. on the list page) * @deprecated Use {@link useAllRulerRuleAbilities} instead
// We need to investigate further if some of these calls are redundant */
// In the meantime, memoizing the result helps
export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities<AlertRuleAction> { export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities<AlertRuleAction> {
const rulesSourceName = getRulesSourceName(rule.namespace.rulesSource); // This hook is being called a lot in different places
// In some cases multiple times for ~80 rules (e.g. on the list page)
// We need to investigate further if some of these calls are redundant
// In the meantime, memoizing the result helps
const groupIdentifierV2 = useMemo(() => groupIdentifier.fromCombinedRule(rule), [rule]);
return useAllRulerRuleAbilities(rule.rulerRule, groupIdentifierV2);
}
const { export function useAllRulerRuleAbilities(
isEditable, rule: RulerRuleDTO | undefined,
isRemovable, groupIdentifier: RuleGroupIdentifierV2
isRulerAvailable = false, ): Abilities<AlertRuleAction> {
loading, const rulesSourceName = getGroupOriginName(groupIdentifier);
} = useIsRuleEditable(rulesSourceName, rule.rulerRule);
const { isEditable, isRemovable, isRulerAvailable = false, loading } = useIsRuleEditable(rulesSourceName, rule);
const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);
const canSilence = useCanSilence(rule.rulerRule); const canSilence = useCanSilence(rule);
const abilities = useMemo<Abilities<AlertRuleAction>>(() => { const abilities = useMemo<Abilities<AlertRuleAction>>(() => {
const isProvisioned = const isProvisioned = rule ? isProvisionedRule(rule) : false;
rulerRuleType.grafana.rule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); // TODO: Add support for federated rules
const isFederated = isFederatedRuleGroup(rule.group); // const isFederated = isFederatedRuleGroup();
const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule.rulerRule); const isFederated = false;
const isPluginProvided = isPluginProvidedRule(rule.rulerRule); const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule);
const isPluginProvided = isPluginProvidedRule(rule);
// if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited
const immutableRule = isProvisioned || isFederated || isPluginProvided; const immutableRule = isProvisioned || isFederated || isPluginProvided;
@ -263,39 +275,44 @@ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities<AlertRul
}; };
return abilities; return abilities;
}, [rule, loading, isRulerAvailable, isEditable, isRemovable, rulesSourceName, exportAllowed, canSilence]); }, [rule, loading, isRulerAvailable, rulesSourceName, isEditable, isRemovable, canSilence, exportAllowed]);
return abilities; return abilities;
} }
export function useAllRulerRuleAbilities( /**
rule: RulerRuleDTO | undefined, * Hook for checking abilities on Grafana Prometheus rules (GrafanaPromRuleDTO)
groupIdentifier: RuleGroupIdentifierV2 * This is the next version of useAllRulerRuleAbilities designed to work with GrafanaPromRuleDTO
): Abilities<AlertRuleAction> { */
const rulesSourceName = getGroupOriginName(groupIdentifier); export function useAllGrafanaPromRuleAbilities(rule: GrafanaPromRuleDTO | undefined): Abilities<AlertRuleAction> {
// For GrafanaPromRuleDTO, we use useIsGrafanaPromRuleEditable instead
const { isEditable, isRemovable, isRulerAvailable = false, loading } = useIsRuleEditable(rulesSourceName, rule); const { isEditable, isRemovable, loading } = useIsGrafanaPromRuleEditable(rule); // duplicate
const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);
const canSilence = useCanSilence(rule);
const silenceSupported = useGrafanaRulesSilenceSupport();
const canSilenceInFolder = useCanSilenceInFolder(rule?.folderUid);
const abilities = useMemo<Abilities<AlertRuleAction>>(() => { const abilities = useMemo<Abilities<AlertRuleAction>>(() => {
const isProvisioned = rulerRuleType.grafana.rule(rule) && Boolean(rule.grafana_alert.provenance); const isProvisioned = rule ? isProvisionedPromRule(rule) : false;
// const isFederated = isFederatedRuleGroup();
// Note: Grafana managed rules can't be federated - this is strictly a Mimir feature
// See: https://grafana.com/docs/mimir/latest/references/architecture/components/ruler/#federated-rule-groups
const isFederated = false; const isFederated = false;
const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule); // All GrafanaPromRuleDTO rules are Grafana-managed by definition
const isAlertingRule = prometheusRuleType.grafana.alertingRule(rule);
const isPluginProvided = isPluginProvidedRule(rule); const isPluginProvided = isPluginProvidedRule(rule);
// if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited
const immutableRule = isProvisioned || isFederated || isPluginProvided; const immutableRule = isProvisioned || isFederated || isPluginProvided;
// while we gather info, pretend it's not supported // GrafanaPromRuleDTO rules are always supported (no loading state for ruler availability)
const MaybeSupported = loading ? NotSupported : isRulerAvailable; const MaybeSupported = loading ? NotSupported : AlwaysSupported;
const MaybeSupportedUnlessImmutable = immutableRule ? NotSupported : MaybeSupported; const MaybeSupportedUnlessImmutable = immutableRule ? NotSupported : MaybeSupported;
// Creating duplicates of plugin-provided rules does not seem to make a lot of sense // Creating duplicates of plugin-provided rules does not seem to make a lot of sense
const duplicateSupported = isPluginProvided ? NotSupported : MaybeSupported; const duplicateSupported = isPluginProvided ? NotSupported : MaybeSupported;
const rulesPermissions = getRulesPermissions(rulesSourceName); const rulesPermissions = getRulesPermissions('grafana');
const abilities: Abilities<AlertRuleAction> = { const abilities: Abilities<AlertRuleAction> = {
[AlertRuleAction.Duplicate]: toAbility(duplicateSupported, rulesPermissions.create), [AlertRuleAction.Duplicate]: toAbility(duplicateSupported, rulesPermissions.create),
@ -303,22 +320,91 @@ export function useAllRulerRuleAbilities(
[AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false], [AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false],
[AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false], [AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false],
[AlertRuleAction.Explore]: toAbility(AlwaysSupported, AccessControlAction.DataSourcesExplore), [AlertRuleAction.Explore]: toAbility(AlwaysSupported, AccessControlAction.DataSourcesExplore),
[AlertRuleAction.Silence]: canSilence, [AlertRuleAction.Silence]: [silenceSupported, canSilenceInFolder && isAlertingRule],
[AlertRuleAction.ModifyExport]: [isGrafanaManagedAlertRule, exportAllowed], [AlertRuleAction.ModifyExport]: [isAlertingRule, exportAllowed],
[AlertRuleAction.Pause]: [MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, isEditable ?? false], [AlertRuleAction.Pause]: [MaybeSupportedUnlessImmutable && isAlertingRule, isEditable ?? false],
[AlertRuleAction.Restore]: [MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, isEditable ?? false], [AlertRuleAction.Restore]: [MaybeSupportedUnlessImmutable && isAlertingRule, isEditable ?? false],
[AlertRuleAction.DeletePermanently]: [ [AlertRuleAction.DeletePermanently]: [
MaybeSupportedUnlessImmutable && isGrafanaManagedAlertRule, MaybeSupportedUnlessImmutable && isAlertingRule,
(isRemovable && isAdmin()) ?? false, (isRemovable && isAdmin()) ?? false,
], ],
}; };
return abilities; return abilities;
}, [rule, loading, isRulerAvailable, rulesSourceName, isEditable, isRemovable, canSilence, exportAllowed]); }, [rule, loading, isEditable, isRemovable, canSilenceInFolder, exportAllowed, silenceSupported]);
return abilities; return abilities;
} }
interface IsGrafanaPromRuleEditableResult {
isEditable: boolean;
isRemovable: boolean;
loading: boolean;
}
/**
* Hook for checking if a GrafanaPromRuleDTO is editable
* Adapted version of useIsRuleEditable for GrafanaPromRuleDTO
*/
function useIsGrafanaPromRuleEditable(rule?: GrafanaPromRuleDTO): IsGrafanaPromRuleEditableResult {
const folderUID = rule?.folderUid;
const { folder, loading } = useFolder(folderUID);
return useMemo(() => {
if (!rule || !folderUID) {
return { isEditable: false, isRemovable: false, loading: false };
}
if (!folder) {
// Loading or invalid folder UID
return {
isEditable: false,
isRemovable: false,
loading,
};
}
// For Grafana-managed rules, check folder permissions
const rulesPermissions = getRulesPermissions('grafana');
const canEditGrafanaRules = ctx.hasPermissionInMetadata(rulesPermissions.update, folder);
const canRemoveGrafanaRules = ctx.hasPermissionInMetadata(rulesPermissions.delete, folder);
return {
isEditable: canEditGrafanaRules,
isRemovable: canRemoveGrafanaRules,
loading,
};
}, [rule, folderUID, folder, loading]);
}
export const skipToken = Symbol('ability-skip-token');
type SkipToken = typeof skipToken;
/**
* Hook for checking a single ability on a GrafanaPromRuleDTO
*/
export function useGrafanaPromRuleAbility(rule: GrafanaPromRuleDTO | SkipToken, action: AlertRuleAction): Ability {
const abilities = useAllGrafanaPromRuleAbilities(rule === skipToken ? undefined : rule);
return useMemo(() => {
return abilities[action];
}, [abilities, action]);
}
/**
* Hook for checking multiple abilities on a GrafanaPromRuleDTO
*/
export function useGrafanaPromRuleAbilities(
rule: GrafanaPromRuleDTO | SkipToken,
actions: AlertRuleAction[]
): Ability[] {
const abilities = useAllGrafanaPromRuleAbilities(rule === skipToken ? undefined : rule);
return useMemo(() => {
return actions.map((action) => abilities[action]);
}, [abilities, actions]);
}
export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> { export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> {
const { const {
selectedAlertmanager, selectedAlertmanager,

@ -12,7 +12,7 @@ import { hashRule } from '../utils/rule-id';
import { DataSourceRuleLoader } from './DataSourceRuleLoader'; import { DataSourceRuleLoader } from './DataSourceRuleLoader';
import { FilterProgressState, FilterStatus } from './FilterViewStatus'; import { FilterProgressState, FilterStatus } from './FilterViewStatus';
import { GrafanaRuleLoader } from './GrafanaRuleLoader'; import { GrafanaRuleListItem } from './GrafanaRuleListItem';
import LoadMoreHelper from './LoadMoreHelper'; import LoadMoreHelper from './LoadMoreHelper';
import { UnknownRuleListItem } from './components/AlertRuleListItem'; import { UnknownRuleListItem } from './components/AlertRuleListItem';
import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader'; import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader';
@ -154,11 +154,11 @@ function FilterViewResults({ filterState }: FilterViewProps) {
switch (origin) { switch (origin) {
case 'grafana': case 'grafana':
return ( return (
<GrafanaRuleLoader <GrafanaRuleListItem
key={key} rule={rule}
ruleIdentifier={{ ruleSourceName: 'grafana', uid: rule.uid }}
groupIdentifier={groupIdentifier} groupIdentifier={groupIdentifier}
namespaceName={ruleWithOrigin.namespaceName} namespaceName={ruleWithOrigin.namespaceName}
showLocation={true}
/> />
); );
case 'datasource': case 'datasource':

@ -2,6 +2,7 @@ import { render } from 'test/test-utils';
import { byRole, byTitle } from 'testing-library-selector'; import { byRole, byTitle } from 'testing-library-selector';
import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime'; import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime';
import { AccessControlAction } from 'app/types';
import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting';
import { import {
GrafanaPromRuleDTO, GrafanaPromRuleDTO,
@ -13,13 +14,13 @@ import {
} from 'app/types/unified-alerting-dto'; } from 'app/types/unified-alerting-dto';
import { setupMswServer } from '../mockApi'; import { setupMswServer } from '../mockApi';
import { mockGrafanaPromAlertingRule, mockGrafanaRulerRule } from '../mocks'; import { grantUserPermissions } from '../mocks';
import { grafanaRulerGroup, grafanaRulerNamespace } from '../mocks/grafanaRulerApi'; import { grafanaRulerGroup, grafanaRulerNamespace } from '../mocks/grafanaRulerApi';
import { setGrafanaPromRules } from '../mocks/server/configure'; import { setFolderAccessControl, setGrafanaPromRules } from '../mocks/server/configure';
import { rulerRuleType } from '../utils/rules'; import { rulerRuleType } from '../utils/rules';
import { intervalToSeconds } from '../utils/time'; import { intervalToSeconds } from '../utils/time';
import { GrafanaGroupLoader, matchRules } from './GrafanaGroupLoader'; import { GrafanaGroupLoader } from './GrafanaGroupLoader';
setPluginLinksHook(() => ({ links: [], isLoading: false })); setPluginLinksHook(() => ({ links: [], isLoading: false }));
setPluginComponentsHook(() => ({ components: [], isLoading: false })); setPluginComponentsHook(() => ({ components: [], isLoading: false }));
@ -32,9 +33,35 @@ const ui = {
ruleLink: (ruleName: string) => byRole('link', { name: ruleName }), ruleLink: (ruleName: string) => byRole('link', { name: ruleName }),
editButton: () => byRole('link', { name: 'Edit' }), editButton: () => byRole('link', { name: 'Edit' }),
moreButton: () => byRole('button', { name: 'More' }), moreButton: () => byRole('button', { name: 'More' }),
// Menu items that appear when More button is clicked
menuItems: {
silence: () => byRole('menuitem', { name: /silence/i }),
duplicate: () => byRole('menuitem', { name: /duplicate/i }),
copyLink: () => byRole('menuitem', { name: /copy link/i }),
export: () => byRole('menuitem', { name: /export/i }),
delete: () => byRole('menuitem', { name: /delete/i }),
},
}; };
describe('GrafanaGroupLoader', () => { describe('GrafanaGroupLoader', () => {
beforeEach(() => {
grantUserPermissions([
AccessControlAction.AlertingRuleUpdate,
AccessControlAction.AlertingRuleDelete,
AccessControlAction.AlertingSilenceCreate,
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleRead,
]);
// Grant necessary permissions for editing rules
setFolderAccessControl({
[AccessControlAction.AlertingRuleUpdate]: true,
[AccessControlAction.AlertingRuleDelete]: true,
[AccessControlAction.AlertingSilenceCreate]: true,
[AccessControlAction.AlertingRuleCreate]: true, // For duplicate action
[AccessControlAction.AlertingRuleRead]: true, // For export action
});
});
it('should render rule with url when ruler and prom rule exist', async () => { it('should render rule with url when ruler and prom rule exist', async () => {
setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]); setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]);
@ -55,8 +82,8 @@ describe('GrafanaGroupLoader', () => {
); );
}); });
it('should render rule with url and creating state when only ruler rule exists', async () => { it('should render More button with action menu options', async () => {
setGrafanaPromRules([]); setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]);
const groupIdentifier = getGroupIdentifier(grafanaRulerGroup); const groupIdentifier = getGroupIdentifier(grafanaRulerGroup);
@ -65,92 +92,119 @@ describe('GrafanaGroupLoader', () => {
const [rule1] = grafanaRulerGroup.rules; const [rule1] = grafanaRulerGroup.rules;
const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find(); const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find();
const creatingIcon = ui.ruleStatus('Creating').get(ruleListItem); // Check that More button is present
expect(creatingIcon).toBeInTheDocument(); const moreButton = ui.moreButton().get(ruleListItem);
expect(moreButton).toBeInTheDocument();
const ruleLink = ui.ruleLink(rule1.grafana_alert.title).get(ruleListItem); // Verify More button accessibility
expect(ruleLink).toHaveAttribute( expect(moreButton).toHaveAttribute('aria-label', 'More');
'href', expect(moreButton).toHaveTextContent('More');
expect.stringContaining(`/alerting/grafana/${rule1.grafana_alert.uid}/view`)
);
}); });
it('should render delete rule operation list item when only prom rule exists', async () => { it('should render multiple rules with their own action buttons', async () => {
const promOnlyGroup: GrafanaPromRuleGroupDTO = { // Create a group with multiple rules
...rulerGroupToPromGroup(grafanaRulerGroup), const multiRuleGroup = {
name: 'prom-only-group', ...grafanaRulerGroup,
rules: [
grafanaRulerGroup.rules[0],
{
...grafanaRulerGroup.rules[0],
grafana_alert: {
...grafanaRulerGroup.rules[0].grafana_alert,
uid: 'second-rule-uid',
title: 'Second Rule',
},
},
],
}; };
setGrafanaPromRules([promOnlyGroup]); setGrafanaPromRules([rulerGroupToPromGroup(multiRuleGroup)]);
const groupIdentifier = getGroupIdentifier(promOnlyGroup); const groupIdentifier = getGroupIdentifier(multiRuleGroup);
render(<GrafanaGroupLoader groupIdentifier={groupIdentifier} namespaceName={grafanaRulerNamespace.name} />); render(<GrafanaGroupLoader groupIdentifier={groupIdentifier} namespaceName={grafanaRulerNamespace.name} />);
const [rule1] = promOnlyGroup.rules; // Check first rule
const promRule = await ui.ruleItem(rule1.name).find(); const [rule1, rule2] = multiRuleGroup.rules;
const ruleListItem1 = await ui.ruleItem(rule1.grafana_alert.title).find();
const ruleListItem2 = await ui.ruleItem(rule2.grafana_alert.title).find();
// Each rule should have its own More button
expect(ui.moreButton().get(ruleListItem1)).toBeInTheDocument();
expect(ui.moreButton().get(ruleListItem2)).toBeInTheDocument();
const deletingIcon = ui.ruleStatus('Deleting').get(promRule); // Check that edit buttons are present and have correct URLs
expect(deletingIcon).toBeInTheDocument(); const editButton1 = ui.editButton().get(ruleListItem1);
const editButton2 = ui.editButton().get(ruleListItem2);
expect(ui.editButton().query(promRule)).not.toBeInTheDocument(); expect(editButton1).toBeInTheDocument();
expect(ui.moreButton().query(promRule)).not.toBeInTheDocument(); expect(editButton2).toBeInTheDocument();
// Check that edit buttons have correct URLs (the actual format is simpler)
expect(editButton1).toHaveAttribute('href', expect.stringContaining(`/alerting/${rule1.grafana_alert.uid}/edit`));
expect(editButton2).toHaveAttribute('href', expect.stringContaining(`/alerting/${rule2.grafana_alert.uid}/edit`));
}); });
});
describe('matchRules', () => { it('should not render edit button when user lacks edit permissions', async () => {
it('should return matches for all items and have empty promOnlyRules if all rules are matched by uid', () => { // Override permissions to deny editing
const rulerRules = [ setFolderAccessControl({
mockGrafanaRulerRule({ uid: '1' }), [AccessControlAction.AlertingRuleUpdate]: false,
mockGrafanaRulerRule({ uid: '2' }), [AccessControlAction.AlertingRuleDelete]: false,
mockGrafanaRulerRule({ uid: '3' }), });
];
const promRules = rulerRules.map(rulerRuleToPromRule); setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]);
const { matches, promOnlyRules } = matchRules(promRules, rulerRules); const groupIdentifier = getGroupIdentifier(grafanaRulerGroup);
expect(matches.size).toBe(rulerRules.length); render(<GrafanaGroupLoader groupIdentifier={groupIdentifier} namespaceName={grafanaRulerNamespace.name} />);
expect(promOnlyRules).toHaveLength(0);
for (const [rulerRule, promRule] of matches) { const [rule1] = grafanaRulerGroup.rules;
expect(rulerRule.grafana_alert.uid).toBe(promRule.uid); const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find();
}
// Edit button should not be present
expect(ui.editButton().query(ruleListItem)).not.toBeInTheDocument();
// More button should still be present (for other actions like viewing)
expect(ui.moreButton().get(ruleListItem)).toBeInTheDocument();
}); });
it('should return unmatched prometheus rules in promOnlyRules array', () => { it('should render correct menu actions when More button is clicked', async () => {
const rulerRules = [mockGrafanaRulerRule({ uid: '1' }), mockGrafanaRulerRule({ uid: '2' })]; setGrafanaPromRules([rulerGroupToPromGroup(grafanaRulerGroup)]);
const matchingPromRules = rulerRules.map(rulerRuleToPromRule); const groupIdentifier = getGroupIdentifier(grafanaRulerGroup);
const unmatchedPromRules = [mockGrafanaPromAlertingRule({ uid: '3' }), mockGrafanaPromAlertingRule({ uid: '4' })];
const allPromRules = [...matchingPromRules, ...unmatchedPromRules]; const { user } = render(
const { matches, promOnlyRules } = matchRules(allPromRules, rulerRules); <GrafanaGroupLoader groupIdentifier={groupIdentifier} namespaceName={grafanaRulerNamespace.name} />
);
expect(matches.size).toBe(rulerRules.length); const [rule1] = grafanaRulerGroup.rules;
expect(promOnlyRules).toHaveLength(unmatchedPromRules.length); const ruleListItem = await ui.ruleItem(rule1.grafana_alert.title).find();
expect(promOnlyRules).toEqual(expect.arrayContaining(unmatchedPromRules));
}); // Find and click the More button
const moreButton = ui.moreButton().get(ruleListItem);
await user.click(moreButton);
// Check that the dropdown menu appears
const menu = byRole('menu').get();
expect(menu).toBeInTheDocument();
// With proper permissions, all 4 menu actions should be available:
it('should not include ruler rules in matches if they have no prometheus counterpart', () => { // 1. Silence notifications - available for alerting rules (AlertingSilenceCreate permission)
const rulerRules = [ expect(ui.menuItems.silence().get()).toBeInTheDocument();
mockGrafanaRulerRule({ uid: '1' }),
mockGrafanaRulerRule({ uid: '2' }),
mockGrafanaRulerRule({ uid: '3' }),
];
// Only create prom rule for the second ruler rule // 2. Copy link - always available
const promRules = [rulerRuleToPromRule(rulerRules[1])]; expect(ui.menuItems.copyLink().get()).toBeInTheDocument();
const { matches, promOnlyRules } = matchRules(promRules, rulerRules); // 3. Duplicate - should be available with create permissions (AlertingRuleCreate permission)
expect(ui.menuItems.duplicate().get()).toBeInTheDocument();
expect(matches.size).toBe(1); // 4. Export - should be available for Grafana alerting rules (AlertingRuleRead permission)
expect(promOnlyRules).toHaveLength(0); expect(ui.menuItems.export().get()).toBeInTheDocument();
// Verify that only the second ruler rule is in matches // Verify that the menu contains all 4 expected menu items
expect(matches.has(rulerRules[0])).toBe(false); const menuItems = byRole('menuitem').getAll();
expect(matches.get(rulerRules[1])).toBe(promRules[0]); expect(menuItems.length).toBe(4);
expect(matches.has(rulerRules[2])).toBe(false);
}); });
}); });

@ -1,22 +1,13 @@
import { useMemo } from 'react';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { Alert } from '@grafana/ui'; import { Alert } from '@grafana/ui';
import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting';
import { GrafanaPromRuleDTO, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
import { logWarning } from '../Analytics';
import { alertRuleApi } from '../api/alertRuleApi';
import { prometheusApi } from '../api/prometheusApi'; import { prometheusApi } from '../api/prometheusApi';
import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants'; import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants';
import { GrafanaRulesSource } from '../utils/datasource';
import { GrafanaRuleListItem } from './GrafanaRuleLoader'; import { GrafanaRuleListItem } from './GrafanaRuleListItem';
import { RuleOperationListItem } from './components/AlertRuleListItem';
import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader'; import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader';
import { RuleOperation } from './components/RuleListIcon';
const { useGetGrafanaRulerGroupQuery } = alertRuleApi;
const { useGetGrafanaGroupsQuery } = prometheusApi; const { useGetGrafanaGroupsQuery } = prometheusApi;
export interface GrafanaGroupLoaderProps { export interface GrafanaGroupLoaderProps {
@ -48,20 +39,8 @@ export function GrafanaGroupLoader({
}, },
{ pollingInterval: RULE_LIST_POLL_INTERVAL_MS } { pollingInterval: RULE_LIST_POLL_INTERVAL_MS }
); );
const { data: rulerResponse, isLoading: isRulerGroupLoading } = useGetGrafanaRulerGroupQuery({
folderUid: groupIdentifier.namespace.uid,
groupName: groupIdentifier.groupName,
});
const { matches, promOnlyRules } = useMemo(() => {
const promRules = promResponse?.data.groups.at(0)?.rules ?? [];
const rulerRules = rulerResponse?.rules ?? [];
return matchRules(promRules, rulerRules);
}, [promResponse, rulerResponse]);
const isLoading = isPromResponseLoading || isRulerGroupLoading; if (isPromResponseLoading) {
if (isLoading) {
return ( return (
<> <>
{Array.from({ length: expectedRulesCount }).map((_, index) => ( {Array.from({ length: expectedRulesCount }).map((_, index) => (
@ -71,7 +50,7 @@ export function GrafanaGroupLoader({
); );
} }
if (!rulerResponse && !promResponse) { if (!promResponse) {
return ( return (
<Alert <Alert
title={t( title={t(
@ -86,28 +65,11 @@ export function GrafanaGroupLoader({
return ( return (
<> <>
{rulerResponse?.rules.map((rulerRule) => { {promResponse.data.groups.at(0)?.rules.map((promRule) => {
const promRule = matches.get(rulerRule);
if (!promRule) {
return (
<GrafanaRuleListItem
key={rulerRule.grafana_alert.uid}
rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier}
namespaceName={namespaceName}
operation={RuleOperation.Creating}
showLocation={false}
/>
);
}
return ( return (
<GrafanaRuleListItem <GrafanaRuleListItem
key={promRule.uid} key={promRule.uid}
rule={promRule} rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier} groupIdentifier={groupIdentifier}
namespaceName={namespaceName} namespaceName={namespaceName}
// we don't show the location again for rules, it's redundant because they are shown in a folder > group hierarchy // we don't show the location again for rules, it's redundant because they are shown in a folder > group hierarchy
@ -115,58 +77,6 @@ export function GrafanaGroupLoader({
/> />
); );
})} })}
{promOnlyRules.map((rule) => (
<RuleOperationListItem
key={rule.uid}
name={rule.name}
namespace={namespaceName}
group={groupIdentifier.groupName}
rulesSource={GrafanaRulesSource}
application="grafana"
operation={RuleOperation.Deleting}
showLocation={false}
/>
))}
</> </>
); );
} }
interface MatchingResult {
matches: Map<RulerGrafanaRuleDTO, GrafanaPromRuleDTO>;
/**
* Rules that were already removed from the Ruler but the changes has not been yet propagated to Prometheus
*/
promOnlyRules: GrafanaPromRuleDTO[];
}
export function matchRules(
promRules: GrafanaPromRuleDTO[],
rulerRules: RulerGrafanaRuleDTO[]
): Readonly<MatchingResult> {
const promRulesMap = new Map(promRules.map((rule) => [rule.uid, rule]));
const matchingResult = rulerRules.reduce<MatchingResult>(
(acc, rulerRule) => {
const { matches } = acc;
const promRule = promRulesMap.get(rulerRule.grafana_alert.uid);
if (promRule) {
matches.set(rulerRule, promRule);
promRulesMap.delete(rulerRule.grafana_alert.uid);
}
return acc;
},
{ matches: new Map(), promOnlyRules: [] }
);
matchingResult.promOnlyRules.push(...promRulesMap.values());
if (matchingResult.promOnlyRules.length > 0) {
// Grafana Prometheus rules should be strongly consistent now so each Ruler rule should have a matching Prometheus rule
// If not, log it as a warning
logWarning('Grafana Managed Rules: No matching Prometheus rule found for Ruler rule', {
promOnlyRulesCount: matchingResult.promOnlyRules.length.toString(),
});
}
return matchingResult;
}

@ -0,0 +1,71 @@
import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting';
import { GrafanaPromRuleDTO, PromRuleType } from 'app/types/unified-alerting-dto';
import { GrafanaRulesSource } from '../utils/datasource';
import { totalFromStats } from '../utils/ruleStats';
import { prometheusRuleType } from '../utils/rules';
import { createRelativeUrl } from '../utils/url';
import {
AlertRuleListItem,
RecordingRuleListItem,
RuleListItemCommonProps,
UnknownRuleListItem,
} from './components/AlertRuleListItem';
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
import { RuleOperation } from './components/RuleListIcon';
interface GrafanaRuleListItemProps {
rule: GrafanaPromRuleDTO;
groupIdentifier: GrafanaRuleGroupIdentifier;
namespaceName: string;
operation?: RuleOperation;
showLocation?: boolean;
}
export function GrafanaRuleListItem({
rule,
groupIdentifier,
namespaceName,
operation,
showLocation = true,
}: GrafanaRuleListItemProps) {
const { name, uid, labels, provenance } = rule;
const commonProps: RuleListItemCommonProps = {
name,
rulesSource: GrafanaRulesSource,
group: groupIdentifier.groupName,
namespace: namespaceName,
href: createRelativeUrl(`/alerting/grafana/${uid}/view`),
health: rule?.health,
error: rule?.lastError,
labels: labels,
isProvisioned: Boolean(provenance),
isPaused: rule?.isPaused,
application: 'grafana' as const,
actions: <RuleActionsButtons promRule={rule} groupIdentifier={groupIdentifier} compact />,
};
if (prometheusRuleType.grafana.alertingRule(rule)) {
const promAlertingRule = rule && rule.type === PromRuleType.Alerting ? rule : undefined;
const instancesCount = totalFromStats(promAlertingRule?.totals ?? {});
return (
<AlertRuleListItem
{...commonProps}
summary={rule.annotations?.summary}
state={promAlertingRule?.state}
instancesCount={instancesCount}
operation={operation}
showLocation={showLocation}
/>
);
}
if (prometheusRuleType.grafana.recordingRule(rule)) {
return <RecordingRuleListItem {...commonProps} showLocation={showLocation} />;
}
return <UnknownRuleListItem ruleName={name} groupIdentifier={groupIdentifier} ruleDefinition={rule} />;
}

@ -1,150 +0,0 @@
import { Trans, t } from '@grafana/i18n';
import { Alert } from '@grafana/ui';
import { GrafanaRuleGroupIdentifier, GrafanaRuleIdentifier } from 'app/types/unified-alerting';
import { GrafanaPromRuleDTO, PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
import { alertRuleApi } from '../api/alertRuleApi';
import { prometheusApi } from '../api/prometheusApi';
import { createReturnTo } from '../hooks/useReturnTo';
import { GrafanaRulesSource } from '../utils/datasource';
import { totalFromStats } from '../utils/ruleStats';
import { rulerRuleType } from '../utils/rules';
import { createRelativeUrl } from '../utils/url';
import {
AlertRuleListItem,
RecordingRuleListItem,
RuleListItemCommonProps,
UnknownRuleListItem,
} from './components/AlertRuleListItem';
import { AlertRuleListItemSkeleton, RulerRuleLoadingError } from './components/AlertRuleListItemLoader';
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
import { RuleOperation } from './components/RuleListIcon';
const { useGetGrafanaRulerGroupQuery } = alertRuleApi;
const { useGetGrafanaGroupsQuery } = prometheusApi;
interface GrafanaRuleLoaderProps {
ruleIdentifier: GrafanaRuleIdentifier;
groupIdentifier: GrafanaRuleGroupIdentifier;
namespaceName: string;
}
export function GrafanaRuleLoader({ ruleIdentifier, groupIdentifier, namespaceName }: GrafanaRuleLoaderProps) {
const {
data: rulerRuleGroup,
error: rulerRuleGroupError,
isLoading: isRulerRuleGroupLoading,
} = useGetGrafanaRulerGroupQuery({
folderUid: groupIdentifier.namespace.uid,
groupName: groupIdentifier.groupName,
});
const {
data: promRuleGroup,
error: promRuleGroupError,
isLoading: isPromRuleGroupLoading,
} = useGetGrafanaGroupsQuery({
folderUid: groupIdentifier.namespace.uid,
groupName: groupIdentifier.groupName,
});
const rulerRule = rulerRuleGroup?.rules.find((rulerRule) => rulerRule.grafana_alert.uid === ruleIdentifier.uid);
const promRule = promRuleGroup?.data.groups
.flatMap((group) => group.rules)
.find((promRule) => promRule.uid === ruleIdentifier.uid);
if (rulerRuleGroupError || promRuleGroupError) {
return <RulerRuleLoadingError ruleIdentifier={ruleIdentifier} error={rulerRuleGroupError || promRuleGroupError} />;
}
if (isRulerRuleGroupLoading || isPromRuleGroupLoading) {
return <AlertRuleListItemSkeleton />;
}
if (!rulerRule) {
return (
<Alert
title={t('alerting.rule-list.cannot-load-rule-details-for', 'Cannot load rule details for UID {{uid}}', {
uid: ruleIdentifier.uid,
})}
severity="error"
>
<Trans i18nKey="alerting.rule-list.cannot-find-rule-details-for">
Cannot find rule details for UID {{ uid: ruleIdentifier.uid ?? '<empty uid>' }}
</Trans>
</Alert>
);
}
return (
<GrafanaRuleListItem
rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier}
namespaceName={namespaceName}
/>
);
}
interface GrafanaRuleListItemProps {
rule?: GrafanaPromRuleDTO;
rulerRule: RulerGrafanaRuleDTO;
groupIdentifier: GrafanaRuleGroupIdentifier;
namespaceName: string;
operation?: RuleOperation;
showLocation?: boolean;
}
export function GrafanaRuleListItem({
rule,
rulerRule,
groupIdentifier,
namespaceName,
operation,
showLocation = true,
}: GrafanaRuleListItemProps) {
const returnTo = createReturnTo();
const {
grafana_alert: { uid, title, provenance, is_paused },
annotations = {},
labels = {},
} = rulerRule;
const commonProps: RuleListItemCommonProps = {
name: title,
rulesSource: GrafanaRulesSource,
group: groupIdentifier.groupName,
namespace: namespaceName,
href: createRelativeUrl(`/alerting/grafana/${uid}/view`, { returnTo }),
health: rule?.health,
error: rule?.lastError,
labels: labels,
isProvisioned: Boolean(provenance),
isPaused: rule?.isPaused ?? is_paused,
application: 'grafana' as const,
actions: <RuleActionsButtons rule={rulerRule} promRule={rule} groupIdentifier={groupIdentifier} compact />,
showLocation,
};
if (rulerRuleType.grafana.alertingRule(rulerRule)) {
const promAlertingRule = rule && rule.type === PromRuleType.Alerting ? rule : undefined;
const instancesCount = totalFromStats(promAlertingRule?.totals ?? {});
return (
<AlertRuleListItem
{...commonProps}
summary={annotations.summary}
state={promAlertingRule?.state}
instancesCount={instancesCount}
operation={operation}
/>
);
}
if (rulerRuleType.grafana.recordingRule(rulerRule)) {
return <RecordingRuleListItem {...commonProps} />;
}
return <UnknownRuleListItem ruleName={title} groupIdentifier={groupIdentifier} ruleDefinition={rulerRule} />;
}

@ -1,4 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { RequireAtLeastOne } from 'type-fest';
import { Trans, t } from '@grafana/i18n'; import { Trans, t } from '@grafana/i18n';
import { LinkButton, Stack } from '@grafana/ui'; import { LinkButton, Stack } from '@grafana/ui';
@ -6,24 +7,34 @@ import AlertRuleMenu from 'app/features/alerting/unified/components/rule-viewer/
import { useDeleteModal } from 'app/features/alerting/unified/components/rule-viewer/DeleteModal'; import { useDeleteModal } from 'app/features/alerting/unified/components/rule-viewer/DeleteModal';
import { RedirectToCloneRule } from 'app/features/alerting/unified/components/rules/CloneRule'; import { RedirectToCloneRule } from 'app/features/alerting/unified/components/rules/CloneRule';
import SilenceGrafanaRuleDrawer from 'app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer'; import SilenceGrafanaRuleDrawer from 'app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer';
import { Rule, RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting'; import {
EditableRuleIdentifier,
GrafanaRuleIdentifier,
Rule,
RuleGroupIdentifierV2,
RuleIdentifier,
} from 'app/types/unified-alerting';
import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { RulerRuleDTO } from 'app/types/unified-alerting-dto';
import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; import { logWarning } from '../../Analytics';
import { AlertRuleAction, skipToken, useGrafanaPromRuleAbility, useRulerRuleAbility } from '../../hooks/useAbilities';
import * as ruleId from '../../utils/rule-id'; import * as ruleId from '../../utils/rule-id';
import { isProvisionedRule, rulerRuleType } from '../../utils/rules'; import { isProvisionedPromRule, isProvisionedRule, prometheusRuleType, rulerRuleType } from '../../utils/rules';
import { createRelativeUrl } from '../../utils/url'; import { createRelativeUrl } from '../../utils/url';
interface Props { type RuleProps = RequireAtLeastOne<{
rule: RulerRuleDTO; rule?: RulerRuleDTO;
promRule?: Rule; promRule?: Rule;
}>;
type Props = RuleProps & {
groupIdentifier: RuleGroupIdentifierV2; groupIdentifier: RuleGroupIdentifierV2;
/** /**
* Should we show the buttons in a "compact" state? * Should we show the buttons in a "compact" state?
* i.e. without text and using smaller button sizes * i.e. without text and using smaller button sizes
*/ */
compact?: boolean; compact?: boolean;
} };
// For now this is just a copy of RuleActionsButtons.tsx but with the View button removed. // For now this is just a copy of RuleActionsButtons.tsx but with the View button removed.
// This is only done to keep the new list behind a feature flag and limit changes in the existing components // This is only done to keep the new list behind a feature flag and limit changes in the existing components
@ -37,16 +48,26 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }:
{ identifier: RuleIdentifier; isProvisioned: boolean } | undefined { identifier: RuleIdentifier; isProvisioned: boolean } | undefined
>(undefined); >(undefined);
const isProvisioned = isProvisionedRule(rule); const isProvisioned = getIsProvisioned(rule, promRule);
const [editRuleSupported, editRuleAllowed] = useRulerRuleAbility(rule, groupIdentifier, AlertRuleAction.Update); const [editRuleSupported, editRuleAllowed] = useRulerRuleAbility(rule, groupIdentifier, AlertRuleAction.Update);
// If the consumer of this component comes from the alert list view, we need to use promRule to check abilities and permissions,
// as we have removed all requests to the ruler API in the list view.
const [grafanaEditRuleSupported, grafanaEditRuleAllowed] = useGrafanaPromRuleAbility(
prometheusRuleType.grafana.rule(promRule) ? promRule : skipToken,
AlertRuleAction.Update
);
const canEditRule = editRuleSupported && editRuleAllowed; const canEditRule = (editRuleSupported && editRuleAllowed) || (grafanaEditRuleSupported && grafanaEditRuleAllowed);
const buttons: JSX.Element[] = []; const buttons: JSX.Element[] = [];
const buttonSize = compact ? 'sm' : 'md'; const buttonSize = compact ? 'sm' : 'md';
const identifier = ruleId.fromRulerRuleAndGroupIdentifierV2(groupIdentifier, rule); const identifier = getEditableIdentifier(groupIdentifier, rule, promRule);
if (!identifier) {
return null;
}
if (canEditRule) { if (canEditRule) {
const editURL = createRelativeUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`); const editURL = createRelativeUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`);
@ -93,3 +114,38 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }:
</Stack> </Stack>
); );
} }
function getIsProvisioned(rule?: RulerRuleDTO, promRule?: Rule): boolean {
if (rule) {
return isProvisionedRule(rule);
}
if (promRule) {
return isProvisionedPromRule(promRule);
}
return false;
}
function getEditableIdentifier(
groupIdentifier: RuleGroupIdentifierV2,
rule?: RulerRuleDTO,
promRule?: Rule
): EditableRuleIdentifier | undefined {
if (rule) {
return ruleId.fromRulerRuleAndGroupIdentifierV2(groupIdentifier, rule);
}
if (prometheusRuleType.grafana.rule(promRule)) {
return {
ruleSourceName: 'grafana',
uid: promRule.uid,
} satisfies GrafanaRuleIdentifier;
}
logWarning('Unable to construct an editable rule identifier');
// Returning undefined is safer than throwing here as it allows the component to gracefully handle
// the error by returning null instead of crashing the entire component tree
return undefined;
}

@ -4,7 +4,6 @@ import { useDispatch } from 'app/types/store';
import { DataSourceRulesSourceIdentifier, RuleHealth } from 'app/types/unified-alerting'; import { DataSourceRulesSourceIdentifier, RuleHealth } from 'app/types/unified-alerting';
import { PromAlertingRuleState, PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; import { PromAlertingRuleState, PromRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { alertRuleApi } from '../../api/alertRuleApi';
import { PromRulesResponse, prometheusApi } from '../../api/prometheusApi'; import { PromRulesResponse, prometheusApi } from '../../api/prometheusApi';
const { useLazyGetGroupsQuery, useLazyGetGrafanaGroupsQuery } = prometheusApi; const { useLazyGetGroupsQuery, useLazyGetGrafanaGroupsQuery } = prometheusApi;
@ -83,13 +82,6 @@ export function useGrafanaGroupsGenerator(hookOptions: UseGeneratorHookOptions =
// Because the user waits a bit longer for the initial load but doesn't need to wait for each group to be loaded // Because the user waits a bit longer for the initial load but doesn't need to wait for each group to be loaded
if (hookOptions.populateCache) { if (hookOptions.populateCache) {
const cacheAndRulerPreload = response.data.groups.map(async (group) => { const cacheAndRulerPreload = response.data.groups.map(async (group) => {
dispatch(
alertRuleApi.util.prefetch(
'getGrafanaRulerGroup',
{ folderUid: group.folderUid, groupName: group.name },
{ force: true }
)
);
await dispatch( await dispatch(
prometheusApi.util.upsertQueryData( prometheusApi.util.upsertQueryData(
'getGrafanaGroups', 'getGrafanaGroups',

@ -168,6 +168,10 @@ export function isProvisionedRule(rulerRule: RulerRuleDTO): boolean {
return isGrafanaRulerRule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); return isGrafanaRulerRule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance);
} }
export function isProvisionedPromRule(promRule: PromRuleDTO): boolean {
return prometheusRuleType.grafana.rule(promRule) && Boolean(promRule.provenance);
}
export function isProvisionedRuleGroup(group: RulerRuleGroupDTO): boolean { export function isProvisionedRuleGroup(group: RulerRuleGroupDTO): boolean {
return group.rules.some((rule) => isProvisionedRule(rule)); return group.rules.some((rule) => isProvisionedRule(rule));
} }

@ -27,7 +27,8 @@ import CreateNewButton from './components/CreateNewButton';
import { FolderActionsButton } from './components/FolderActionsButton'; import { FolderActionsButton } from './components/FolderActionsButton';
import { SearchView } from './components/SearchView'; import { SearchView } from './components/SearchView';
import { getFolderPermissions } from './permissions'; import { getFolderPermissions } from './permissions';
import { setAllSelection, useHasSelection } from './state'; import { useHasSelection } from './state/hooks';
import { setAllSelection } from './state/slice';
// New Browse/Manage/Search Dashboards views for nested folders // New Browse/Manage/Search Dashboards views for nested folders
const BrowseDashboardsPage = memo(() => { const BrowseDashboardsPage = memo(() => {

@ -16,7 +16,8 @@ import { RecentlyDeletedActions } from './components/RecentlyDeletedActions';
import { RecentlyDeletedEmptyState } from './components/RecentlyDeletedEmptyState'; import { RecentlyDeletedEmptyState } from './components/RecentlyDeletedEmptyState';
import { SearchView } from './components/SearchView'; import { SearchView } from './components/SearchView';
import { getFolderPermissions } from './permissions'; import { getFolderPermissions } from './permissions';
import { setAllSelection, useHasSelection } from './state'; import { useHasSelection } from './state/hooks';
import { setAllSelection } from './state/slice';
const RecentlyDeletedPage = memo(() => { const RecentlyDeletedPage = memo(() => {
const dispatch = useDispatch(); const dispatch = useDispatch();

@ -25,7 +25,7 @@ import {
SaveDashboardResponseDTO, SaveDashboardResponseDTO,
} from 'app/types'; } from 'app/types';
import { refetchChildren, refreshParents } from '../state'; import { refetchChildren, refreshParents } from '../state/actions';
import { DashboardTreeSelection } from '../types'; import { DashboardTreeSelection } from '../types';
import { isProvisionedDashboard, isProvisionedFolder } from './isProvisioned'; import { isProvisionedDashboard, isProvisionedFolder } from './isProvisioned';

@ -9,7 +9,8 @@ import { useDispatch } from 'app/types';
import { ShowModalReactEvent } from 'app/types/events'; import { ShowModalReactEvent } from 'app/types/events';
import { useDeleteItemsMutation, useMoveItemsMutation } from '../../api/browseDashboardsAPI'; import { useDeleteItemsMutation, useMoveItemsMutation } from '../../api/browseDashboardsAPI';
import { setAllSelection, useActionSelectionState } from '../../state'; import { useActionSelectionState } from '../../state/hooks';
import { setAllSelection } from '../../state/slice';
import { DashboardTreeSelection } from '../../types'; import { DashboardTreeSelection } from '../../types';
import { DeleteModal } from './DeleteModal'; import { DeleteModal } from './DeleteModal';

@ -6,17 +6,15 @@ import { DashboardViewItem } from 'app/features/search/types';
import { useDispatch } from 'app/types'; import { useDispatch } from 'app/types';
import { PAGE_SIZE } from '../api/services'; import { PAGE_SIZE } from '../api/services';
import { fetchNextChildrenPage } from '../state/actions';
import { import {
useFlatTreeState, useFlatTreeState,
useCheckboxSelectionState, useCheckboxSelectionState,
setFolderOpenState,
setItemSelectionState,
useChildrenByParentUIDState, useChildrenByParentUIDState,
setAllSelection,
useBrowseLoadingStatus, useBrowseLoadingStatus,
useLoadNextChildrenPage, useLoadNextChildrenPage,
fetchNextChildrenPage, } from '../state/hooks';
} from '../state'; import { setFolderOpenState, setItemSelectionState, setAllSelection } from '../state/slice';
import { BrowseDashboardsState, DashboardTreeSelection, SelectionState } from '../types'; import { BrowseDashboardsState, DashboardTreeSelection, SelectionState } from '../types';
import { DashboardsTree } from './DashboardsTree'; import { DashboardsTree } from './DashboardsTree';

@ -10,7 +10,7 @@ import { getIconForItem } from 'app/features/search/service/utils';
import { Indent } from '../../../core/components/Indent/Indent'; import { Indent } from '../../../core/components/Indent/Indent';
import { FolderRepo } from '../../../core/components/NestedFolderPicker/FolderRepo'; import { FolderRepo } from '../../../core/components/NestedFolderPicker/FolderRepo';
import { useChildrenByParentUIDState } from '../state'; import { useChildrenByParentUIDState } from '../state/hooks';
import { DashboardsTreeCellProps } from '../types'; import { DashboardsTreeCellProps } from '../types';
import { makeRowID } from './utils'; import { makeRowID } from './utils';

@ -12,7 +12,8 @@ import { ShowModalReactEvent } from 'app/types/events';
import { deletedDashboardsCache } from '../../search/service/deletedDashboardsCache'; import { deletedDashboardsCache } from '../../search/service/deletedDashboardsCache';
import { useListDeletedDashboardsQuery, useRestoreDashboardMutation } from '../api/browseDashboardsAPI'; import { useListDeletedDashboardsQuery, useRestoreDashboardMutation } from '../api/browseDashboardsAPI';
import { useRecentlyDeletedStateManager } from '../api/useRecentlyDeletedStateManager'; import { useRecentlyDeletedStateManager } from '../api/useRecentlyDeletedStateManager';
import { clearFolders, setAllSelection, useActionSelectionState } from '../state'; import { useActionSelectionState } from '../state/hooks';
import { clearFolders, setAllSelection } from '../state/slice';
import { RestoreModal } from './RestoreModal'; import { RestoreModal } from './RestoreModal';

@ -9,7 +9,8 @@ import { SearchStateManager } from 'app/features/search/state/SearchStateManager
import { DashboardViewItemKind, SearchState } from 'app/features/search/types'; import { DashboardViewItemKind, SearchState } from 'app/features/search/types';
import { useDispatch, useSelector } from 'app/types'; import { useDispatch, useSelector } from 'app/types';
import { setAllSelection, setItemSelectionState, useHasSelection } from '../state'; import { useHasSelection } from '../state/hooks';
import { setAllSelection, setItemSelectionState } from '../state/slice';
interface SearchViewProps { interface SearchViewProps {
height: number; height: number;

@ -1,3 +0,0 @@
export * from './slice';
export * from './actions';
export * from './hooks';

@ -7,7 +7,7 @@ import { config } from 'app/core/config';
import { BackgroundConfig, Constraint, LineConfig, Placement } from 'app/plugins/panel/canvas/panelcfg.gen'; import { BackgroundConfig, Constraint, LineConfig, Placement } from 'app/plugins/panel/canvas/panelcfg.gen';
import { LineStyleConfig } from '../../plugins/panel/canvas/editor/LineStyleEditor'; import { LineStyleConfig } from '../../plugins/panel/canvas/editor/LineStyleEditor';
import { DimensionContext } from '../dimensions'; import { DimensionContext } from '../dimensions/context';
import { StandardEditorConfig } from './types'; import { StandardEditorConfig } from './types';

@ -6,7 +6,7 @@ import { t } from '@grafana/i18n';
import { TextDimensionMode } from '@grafana/schema'; import { TextDimensionMode } from '@grafana/schema';
import { Button, Spinner, useStyles2 } from '@grafana/ui'; import { Button, Spinner, useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions/context'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor } from 'app/features/dimensions/editors'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
import { APIEditor, APIEditorConfig } from 'app/plugins/panel/canvas/editor/element/APIEditor'; import { APIEditor, APIEditorConfig } from 'app/plugins/panel/canvas/editor/element/APIEditor';
import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/editor/element/ButtonStyleEditor'; import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/editor/element/ButtonStyleEditor';

@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';

@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ScalarDimensionConfig } from '@grafana/schema'; import { ScalarDimensionConfig } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';

@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ScalarDimensionConfig } from '@grafana/schema'; import { ScalarDimensionConfig } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';

@ -4,8 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ScalarDimensionConfig } from '@grafana/schema'; import { ScalarDimensionConfig } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';

@ -6,9 +6,10 @@ import { LinkModel } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema'; import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema';
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG'; import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions';
import { DimensionContext } from 'app/features/dimensions/context'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor';
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions/resource';
import { LineConfig } from 'app/plugins/panel/canvas/panelcfg.gen'; import { LineConfig } from 'app/plugins/panel/canvas/panelcfg.gen';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';

@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';

@ -4,8 +4,9 @@ import { GrafanaTheme2, LinkModel } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema'; import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema';
import config from 'app/core/config'; import config from 'app/core/config';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor, ScalarDimensionEditor } from 'app/features/dimensions/editors'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps } from '../../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps } from '../../element';

@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';

@ -4,8 +4,8 @@ import { GrafanaTheme2, LinkModel } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ScalarDimensionConfig } from '@grafana/schema'; import { ScalarDimensionConfig } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; import { ScalarDimensionEditor } from 'app/features/dimensions/editors/ScalarDimensionEditor';
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element'; import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';

@ -15,7 +15,7 @@ import { t } from '@grafana/i18n';
import { ConfirmModal } from '@grafana/ui'; import { ConfirmModal } from '@grafana/ui';
import { LayerElement } from 'app/core/components/Layers/types'; import { LayerElement } from 'app/core/components/Layers/types';
import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { notFoundItem } from 'app/features/canvas/elements/notFound';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { import {
BackgroundImageSize, BackgroundImageSize,
Constraint, Constraint,

@ -1,7 +1,7 @@
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { notFoundItem } from 'app/features/canvas/elements/notFound';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { HorizontalConstraint, Placement, VerticalConstraint } from 'app/plugins/panel/canvas/panelcfg.gen'; import { HorizontalConstraint, Placement, VerticalConstraint } from 'app/plugins/panel/canvas/panelcfg.gen';
import { LayerActionID } from 'app/plugins/panel/canvas/types'; import { LayerActionID } from 'app/plugins/panel/canvas/types';

@ -16,7 +16,7 @@ import {
} from '@grafana/schema'; } from '@grafana/schema';
import { Portal } from '@grafana/ui'; import { Portal } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { DimensionContext } from 'app/features/dimensions'; import { DimensionContext } from 'app/features/dimensions/context';
import { import {
getColorDimensionFromData, getColorDimensionFromData,
getResourceDimensionFromData, getResourceDimensionFromData,

@ -3,14 +3,12 @@ import { Navigate, Routes, Route, useLocation } from 'react-router-dom-v5-compat
import { StoreState, useSelector } from 'app/types'; import { StoreState, useSelector } from 'app/types';
import { ROUTES } from './constants'; import { ROUTES } from './constants';
import { import { AddNewConnectionPage } from './pages/AddNewConnectionPage';
AddNewConnectionPage, import { DataSourceDashboardsPage } from './pages/DataSourceDashboardsPage';
DataSourceDashboardsPage, import { DataSourceDetailsPage } from './pages/DataSourceDetailsPage';
DataSourceDetailsPage, import { DataSourcesListPage } from './pages/DataSourcesListPage';
DataSourcesListPage, import { EditDataSourcePage } from './pages/EditDataSourcePage';
EditDataSourcePage, import { NewDataSourcePage } from './pages/NewDataSourcePage';
NewDataSourcePage,
} from './pages';
function RedirectToAddNewConnection() { function RedirectToAddNewConnection() {
const { search } = useLocation(); const { search } = useLocation();

@ -2,7 +2,7 @@ import { Page } from 'app/core/components/Page/Page';
import { AdvisorRedirectNotice } from 'app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice'; import { AdvisorRedirectNotice } from 'app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice';
import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton'; import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton';
import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList';
import { getDataSourcesCount } from 'app/features/datasources/state'; import { getDataSourcesCount } from 'app/features/datasources/state/selectors';
import { StoreState, useSelector } from 'app/types'; import { StoreState, useSelector } from 'app/types';
export function DataSourcesListPage() { export function DataSourcesListPage() {

@ -1,6 +0,0 @@
export { AddNewConnectionPage } from './AddNewConnectionPage';
export { DataSourceDetailsPage } from './DataSourceDetailsPage';
export { DataSourcesListPage } from './DataSourcesListPage';
export { DataSourceDashboardsPage } from './DataSourceDashboardsPage';
export { EditDataSourcePage } from './EditDataSourcePage';
export { NewDataSourcePage } from './NewDataSourcePage';

@ -5,12 +5,10 @@ import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { featureEnabled } from '@grafana/runtime'; import { featureEnabled } from '@grafana/runtime';
import { Card, Grid, useStyles2, Stack, Badge } from '@grafana/ui'; import { Card, Grid, useStyles2, Stack, Badge } from '@grafana/ui';
import { import { PluginDeprecatedBadge } from 'app/features/plugins/admin/components/Badges/PluginDeprecatedBadge';
PluginDeprecatedBadge, import { PluginDisabledBadge } from 'app/features/plugins/admin/components/Badges/PluginDisabledBadge';
PluginDisabledBadge, import { PluginInstalledBadge } from 'app/features/plugins/admin/components/Badges/PluginInstallBadge';
PluginInstalledBadge, import { PluginUpdateAvailableBadge } from 'app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge';
PluginUpdateAvailableBadge,
} from 'app/features/plugins/admin/components/Badges';
import { getBadgeColor } from 'app/features/plugins/admin/components/Badges/sharedStyles'; import { getBadgeColor } from 'app/features/plugins/admin/components/Badges/sharedStyles';
import { isPluginUpdatable } from 'app/features/plugins/admin/helpers'; import { isPluginUpdatable } from 'app/features/plugins/admin/helpers';
import { CatalogPlugin } from 'app/features/plugins/admin/types'; import { CatalogPlugin } from 'app/features/plugins/admin/types';

@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { PanelContainer, useStyles2 } from '@grafana/ui'; import { PanelContainer, useStyles2 } from '@grafana/ui';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { Wizard } from '../components/Wizard'; import { Wizard } from '../components/Wizard/Wizard';
import { useCorrelations } from '../useCorrelations'; import { useCorrelations } from '../useCorrelations';
import { ConfigureCorrelationBasicInfoForm } from './ConfigureCorrelationBasicInfoForm'; import { ConfigureCorrelationBasicInfoForm } from './ConfigureCorrelationBasicInfoForm';

@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Wizard } from '../components/Wizard'; import { Wizard } from '../components/Wizard/Wizard';
import { Correlation } from '../types'; import { Correlation } from '../types';
import { useCorrelations } from '../useCorrelations'; import { useCorrelations } from '../useCorrelations';

@ -1,2 +0,0 @@
export * from './Wizard';
export * from './types';

@ -59,7 +59,7 @@ import { buildGridItemForPanel, transformSaveModelToScene } from '../serializati
import { gridItemToPanel } from '../serialization/transformSceneToSaveModel'; import { gridItemToPanel } from '../serialization/transformSceneToSaveModel';
import { DecoratedRevisionModel } from '../settings/VersionsEditView'; import { DecoratedRevisionModel } from '../settings/VersionsEditView';
import { DashboardEditView } from '../settings/utils'; import { DashboardEditView } from '../settings/utils';
import { historySrv } from '../settings/version-history'; import { historySrv } from '../settings/version-history/HistorySrv';
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper'; import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
import { isInCloneChain } from '../utils/clone'; import { isInCloneChain } from '../utils/clone';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';

@ -57,17 +57,38 @@ export class DashboardGridItem
public constructor(state: DashboardGridItemState) { public constructor(state: DashboardGridItemState) {
super(state); super(state);
this.addActivationHandler(() => this.handleVariableName()); this.addActivationHandler(() => this._activationHandler());
} }
private _handleGridResize(newState: DashboardGridItemState, prevState: DashboardGridItemState) { private _activationHandler() {
const itemCount = this.state.repeatedPanels?.length ?? 1; this.handleVariableName();
const stateChange: Partial<DashboardGridItemState> = {};
return () => {
this._handleGridSizeUnsubscribe();
};
}
private _handleGridSizeSubscribe() {
if (!this._gridSizeSub) {
this._gridSizeSub = this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState));
}
}
private _handleGridSizeUnsubscribe() {
if (this._gridSizeSub) {
this._gridSizeSub.unsubscribe();
this._gridSizeSub = undefined;
}
}
private _handleGridResize(newState: DashboardGridItemState, prevState: DashboardGridItemState) {
if (newState.height === prevState.height) { if (newState.height === prevState.height) {
return; return;
} }
const itemCount = this.state.repeatedPanels?.length ?? 1;
const stateChange: Partial<DashboardGridItemState> = {};
if (this.getRepeatDirection() === 'v') { if (this.getRepeatDirection() === 'v') {
stateChange.itemHeight = Math.ceil(newState.height! / itemCount); stateChange.itemHeight = Math.ceil(newState.height! / itemCount);
} else { } else {
@ -203,16 +224,9 @@ export class DashboardGridItem
public handleVariableName() { public handleVariableName() {
if (this.state.variableName) { if (this.state.variableName) {
if (!this._gridSizeSub) { this._handleGridSizeSubscribe();
this._gridSizeSub = this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState));
this._subs.add(this._gridSizeSub);
}
} else { } else {
if (this._gridSizeSub) { this._handleGridSizeUnsubscribe();
this._gridSizeSub.unsubscribe();
this._subs.remove(this._gridSizeSub);
this._gridSizeSub = undefined;
}
} }
this.performRepeat(); this.performRepeat();

@ -11,7 +11,8 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getDashboardSceneFor } from '../utils/utils'; import { getDashboardSceneFor } from '../utils/utils';
import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync'; import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync';
import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from './annotations'; import { AnnotationSettingsEdit, newAnnotationName } from './annotations/AnnotationSettingsEdit';
import { AnnotationSettingsList } from './annotations/AnnotationSettingsList';
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
export enum MoveDirection { export enum MoveDirection {

@ -5,7 +5,7 @@ import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils'; import { activateFullSceneTree } from '../utils/test-utils';
import { VERSIONS_FETCH_LIMIT, VersionsEditView } from './VersionsEditView'; import { VERSIONS_FETCH_LIMIT, VersionsEditView } from './VersionsEditView';
import { historySrv } from './version-history'; import { historySrv } from './version-history/HistorySrv';
jest.mock('./version-history/HistorySrv'); jest.mock('./version-history/HistorySrv');

@ -11,14 +11,11 @@ import { NavToolbarActions } from '../scene/NavToolbarActions';
import { getDashboardSceneFor } from '../utils/utils'; import { getDashboardSceneFor } from '../utils/utils';
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
import { import { RevisionsModel, historySrv } from './version-history/HistorySrv';
RevisionsModel, import { VersionsHistoryButtons } from './version-history/VersionHistoryButtons';
VersionHistoryComparison, import { VersionHistoryComparison } from './version-history/VersionHistoryComparison';
VersionHistoryHeader, import { VersionHistoryHeader } from './version-history/VersionHistoryHeader';
VersionHistoryTable, import { VersionHistoryTable } from './version-history/VersionHistoryTable';
VersionsHistoryButtons,
historySrv,
} from './version-history';
export const VERSIONS_FETCH_LIMIT = 10; export const VERSIONS_FETCH_LIMIT = 10;

@ -1,2 +0,0 @@
export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit';
export { AnnotationSettingsList } from './AnnotationSettingsList';

@ -1,5 +0,0 @@
export { HistorySrv, historySrv, type RevisionsModel } from './HistorySrv';
export { VersionHistoryTable } from './VersionHistoryTable';
export { VersionHistoryHeader } from './VersionHistoryHeader';
export { VersionsHistoryButtons } from './VersionHistoryButtons';
export { VersionHistoryComparison } from './VersionHistoryComparison';

@ -1,2 +0,0 @@
export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit';
export { AnnotationSettingsList } from './AnnotationSettingsList';

@ -3,7 +3,8 @@ import { getDataSourceSrv, locationService } from '@grafana/runtime';
import { Page } from 'app/core/components/Page/Page'; import { Page } from 'app/core/components/Page/Page';
import { DashboardModel } from '../../state/DashboardModel'; import { DashboardModel } from '../../state/DashboardModel';
import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from '../AnnotationSettings'; import { AnnotationSettingsEdit, newAnnotationName } from '../AnnotationSettings/AnnotationSettingsEdit';
import { AnnotationSettingsList } from '../AnnotationSettings/AnnotationSettingsList';
import { SettingsPageProps } from './types'; import { SettingsPageProps } from './types';

@ -4,7 +4,8 @@ import { locationService } from '@grafana/runtime';
import { Page } from 'app/core/components/Page/Page'; import { Page } from 'app/core/components/Page/Page';
import { NEW_LINK } from 'app/features/dashboard-scene/settings/links/utils'; import { NEW_LINK } from 'app/features/dashboard-scene/settings/links/utils';
import { LinkSettingsEdit, LinkSettingsList } from '../LinksSettings'; import { LinkSettingsEdit } from '../LinksSettings/LinkSettingsEdit';
import { LinkSettingsList } from '../LinksSettings/LinkSettingsList';
import { SettingsPageProps } from './types'; import { SettingsPageProps } from './types';

@ -4,12 +4,9 @@ import * as React from 'react';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { Spinner, HorizontalGroup } from '@grafana/ui'; import { Spinner, HorizontalGroup } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page'; import { Page } from 'app/core/components/Page/Page';
import { import { historySrv, RevisionsModel } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
historySrv, import { VersionsHistoryButtons } from 'app/features/dashboard-scene/settings/version-history/VersionHistoryButtons';
RevisionsModel, import { VersionHistoryHeader } from 'app/features/dashboard-scene/settings/version-history/VersionHistoryHeader';
VersionHistoryHeader,
VersionsHistoryButtons,
} from 'app/features/dashboard-scene/settings/version-history';
import { VersionHistoryComparison } from '../VersionHistory/VersionHistoryComparison'; import { VersionHistoryComparison } from '../VersionHistory/VersionHistoryComparison';
import { VersionHistoryTable } from '../VersionHistory/VersionHistoryTable'; import { VersionHistoryTable } from '../VersionHistory/VersionHistoryTable';

@ -1,2 +0,0 @@
export { LinkSettingsEdit } from './LinkSettingsEdit';
export { LinkSettingsList } from './LinkSettingsList';

@ -4,7 +4,7 @@ import { useAsyncFn } from 'react-use';
import { locationUtil } from '@grafana/data'; import { locationUtil } from '@grafana/data';
import { config, locationService } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { useAppNotification } from 'app/core/copy/appNotification'; import { useAppNotification } from 'app/core/copy/appNotification';
import { historySrv } from 'app/features/dashboard-scene/settings/version-history'; import { historySrv } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
import { useSelector } from 'app/types'; import { useSelector } from 'app/types';
import { dashboardWatcher } from '../../../live/dashboard/dashboardWatcher'; import { dashboardWatcher } from '../../../live/dashboard/dashboardWatcher';

@ -1,2 +0,0 @@
export * from './utils';
export * from './constants';

@ -6,7 +6,7 @@ import { loadPluginDashboards } from 'app/features/plugins/admin/state/actions';
import { PluginDashboard, StoreState, useDispatch, useSelector } from 'app/types'; import { PluginDashboard, StoreState, useDispatch, useSelector } from 'app/types';
import DashboardTable from '../components/DashboardsTable'; import DashboardTable from '../components/DashboardsTable';
import { useInitDataSourceSettings } from '../state'; import { useInitDataSourceSettings } from '../state/hooks';
export type Props = { export type Props = {
// The UID of the data source // The UID of the data source

@ -10,7 +10,8 @@ import { contextSrv } from 'app/core/core';
import { StoreState, AccessControlAction, useSelector } from 'app/types'; import { StoreState, AccessControlAction, useSelector } from 'app/types';
import { ROUTES } from '../../connections/constants'; import { ROUTES } from '../../connections/constants';
import { getDataSources, getDataSourcesCount, useLoadDataSources } from '../state'; import { useLoadDataSources } from '../state/hooks';
import { getDataSources, getDataSourcesCount } from '../state/selectors';
import { trackDataSourcesListViewed } from '../tracking'; import { trackDataSourcesListViewed } from '../tracking';
import { DataSourcesListCard } from './DataSourcesListCard'; import { DataSourcesListCard } from './DataSourcesListCard';

@ -5,7 +5,8 @@ import { SelectableValue } from '@grafana/data';
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import { StoreState, useSelector, useDispatch } from 'app/types'; import { StoreState, useSelector, useDispatch } from 'app/types';
import { getDataSourcesSearchQuery, getDataSourcesSort, setDataSourcesSearchQuery, setIsSortAscending } from '../state'; import { setDataSourcesSearchQuery, setIsSortAscending } from '../state/reducers';
import { getDataSourcesSearchQuery, getDataSourcesSort } from '../state/selectors';
import { trackDsSearched } from '../tracking'; import { trackDsSearched } from '../tracking';
const ascendingSortValue = 'alpha-asc'; const ascendingSortValue = 'alpha-asc';

@ -16,9 +16,6 @@ import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { DataSourceSettingsState, useDispatch } from 'app/types'; import { DataSourceSettingsState, useDispatch } from 'app/types';
import { import {
dataSourceLoaded,
setDataSourceName,
setIsDefault,
useDataSource, useDataSource,
useDataSourceExploreUrl, useDataSourceExploreUrl,
useDataSourceMeta, useDataSourceMeta,
@ -28,7 +25,8 @@ import {
useInitDataSourceSettings, useInitDataSourceSettings,
useTestDataSource, useTestDataSource,
useUpdateDatasource, useUpdateDatasource,
} from '../state'; } from '../state/hooks';
import { setIsDefault, setDataSourceName, dataSourceLoaded } from '../state/reducers';
import { trackDsConfigClicked, trackDsConfigUpdated } from '../tracking'; import { trackDsConfigClicked, trackDsConfigUpdated } from '../tracking';
import { DataSourceRights } from '../types'; import { DataSourceRights } from '../types';

@ -3,7 +3,7 @@ import { config } from '@grafana/runtime';
import { LinkButton } from '@grafana/ui'; import { LinkButton } from '@grafana/ui';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { useDataSource } from '../state'; import { useDataSource } from '../state/hooks';
import { trackCreateDashboardClicked, trackDsConfigClicked, trackExploreClicked } from '../tracking'; import { trackCreateDashboardClicked, trackDsConfigClicked, trackExploreClicked } from '../tracking';
import { constructDataSourceExploreUrl } from '../utils'; import { constructDataSourceExploreUrl } from '../utils';

@ -10,12 +10,9 @@ import { DataSourcePluginCategory, StoreState, useDispatch, useSelector } from '
import { ROUTES } from '../../connections/constants'; import { ROUTES } from '../../connections/constants';
import { DataSourceCategories } from '../components/DataSourceCategories'; import { DataSourceCategories } from '../components/DataSourceCategories';
import { DataSourceTypeCardList } from '../components/DataSourceTypeCardList'; import { DataSourceTypeCardList } from '../components/DataSourceTypeCardList';
import { import { useAddDatasource, useLoadDataSourcePlugins } from '../state/hooks';
useAddDatasource, import { setDataSourceTypeSearchQuery } from '../state/reducers';
useLoadDataSourcePlugins, import { getFilteredDataSourcePlugins } from '../state/selectors';
getFilteredDataSourcePlugins,
setDataSourceTypeSearchQuery,
} from '../state';
export function NewDataSource() { export function NewDataSource() {
useLoadDataSourcePlugins(); useLoadDataSourcePlugins();

@ -15,7 +15,7 @@ import {
Icon, Icon,
ScrollContainer, ScrollContainer,
} from '@grafana/ui'; } from '@grafana/ui';
import * as DFImport from 'app/features/dataframe-import'; import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants';
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
import { getFileDropToQueryHandler } from 'app/plugins/datasource/grafana/utils'; import { getFileDropToQueryHandler } from 'app/plugins/datasource/grafana/utils';
@ -203,9 +203,9 @@ export function DataSourceModal({
readAs="readAsArrayBuffer" readAs="readAsArrayBuffer"
fileListRenderer={() => undefined} fileListRenderer={() => undefined}
options={{ options={{
maxSize: DFImport.maxFileSize, maxSize: maxFileSize,
multiple: false, multiple: false,
accept: DFImport.acceptedFiles, accept: acceptedFiles,
onDrop: onFileDrop, onDrop: onFileDrop,
}} }}
> >

@ -1,6 +0,0 @@
export * from './actions';
export * from './buildCategories';
export * from './hooks';
export * from './navModel';
export * from './reducers';
export * from './selectors';

@ -7,8 +7,8 @@ import { ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema'
import { InlineField, InlineFieldRow, RadioButtonGroup } from '@grafana/ui'; import { InlineField, InlineFieldRow, RadioButtonGroup } from '@grafana/ui';
import { FieldNamePicker } from '@grafana/ui/internal'; import { FieldNamePicker } from '@grafana/ui/internal';
import { getPublicOrAbsoluteUrl, ResourceFolderName } from '..'; import { getPublicOrAbsoluteUrl } from '../resource';
import { MediaType, ResourceDimensionOptions, ResourcePickerSize } from '../types'; import { MediaType, ResourceDimensionOptions, ResourceFolderName, ResourcePickerSize } from '../types';
import { ResourcePicker } from './ResourcePicker'; import { ResourcePicker } from './ResourcePicker';

@ -1,6 +0,0 @@
export * from './ColorDimensionEditor';
export * from './IconSelector';
export * from './ResourceDimensionEditor';
export * from './ScaleDimensionEditor';
export * from './ScalarDimensionEditor';
export * from './TextDimensionEditor';

@ -1,9 +0,0 @@
export * from './types';
export * from './color';
export * from './scale';
export * from './text';
export * from './utils';
export * from './resource';
export * from './context';
export * from './scalar';

@ -7,15 +7,13 @@ import {
ColorDimensionConfig, ColorDimensionConfig,
ScalarDimensionConfig, ScalarDimensionConfig,
} from '@grafana/schema'; } from '@grafana/schema';
import {
getColorDimension,
getScaledDimension,
getTextDimension,
getResourceDimension,
DimensionSupplier,
} from 'app/features/dimensions';
import { getColorDimension } from './color';
import { getResourceDimension } from './resource';
import { getScalarDimension } from './scalar'; import { getScalarDimension } from './scalar';
import { getScaledDimension } from './scale';
import { getTextDimension } from './text';
import { DimensionSupplier } from './types';
export function getColorDimensionFromData( export function getColorDimensionFromData(
data: PanelData | undefined, data: PanelData | undefined,

@ -27,17 +27,15 @@ import { useDispatch, useSelector } from 'app/types';
import { changePanelState } from '../state/explorePane'; import { changePanelState } from '../state/explorePane';
import {
SpanBarOptionsData,
SpanLinkFunc,
Trace,
TracePageHeader,
TraceTimelineViewer,
TTraceTimeline,
} from './components';
import memoizedTraceCriticalPath from './components/CriticalPath'; import memoizedTraceCriticalPath from './components/CriticalPath';
import { TracePageHeader } from './components/TracePageHeader';
import SpanGraph from './components/TracePageHeader/SpanGraph'; import SpanGraph from './components/TracePageHeader/SpanGraph';
import TraceTimelineViewer from './components/TraceTimelineViewer';
import { TraceFlameGraphs } from './components/TraceTimelineViewer/SpanDetail'; import { TraceFlameGraphs } from './components/TraceTimelineViewer/SpanDetail';
import { SpanBarOptionsData } from './components/settings/SpanBarSettings';
import TTraceTimeline from './components/types/TTraceTimeline';
import { SpanLinkFunc } from './components/types/links';
import { Trace } from './components/types/trace';
import { createSpanLinkFactory } from './createSpanLink'; import { createSpanLinkFactory } from './createSpanLink';
import { useChildrenState } from './useChildrenState'; import { useChildrenState } from './useChildrenState';
import { useDetailState } from './useDetailState'; import { useDetailState } from './useDetailState';

@ -14,7 +14,7 @@
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
import { CriticalPathSection, Trace, TraceSpan } from '../types'; import { TraceSpan, CriticalPathSection, Trace } from '../types/trace';
import findLastFinishingChildSpan from './utils/findLastFinishingChildSpan'; import findLastFinishingChildSpan from './utils/findLastFinishingChildSpan';
import getChildOfSpans from './utils/getChildOfSpans'; import getChildOfSpans from './utils/getChildOfSpans';

@ -27,7 +27,9 @@
Here +++++ are critical path sections Here +++++ are critical path sections
*/ */
import { Trace, TraceResponse, transformTraceData } from '../../index';
import transformTraceData from '../../model/transform-trace-data';
import { Trace, TraceResponse } from '../../types/trace';
const testTrace: TraceResponse = { const testTrace: TraceResponse = {
traceID: 'test1-trace', traceID: 'test1-trace',

@ -29,7 +29,8 @@
| |
Here ++++++ is critical path | Here ++++++ is critical path |
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const happyTrace: TraceResponse = { const happyTrace: TraceResponse = {
traceID: 'trace-123', traceID: 'trace-123',

@ -26,7 +26,8 @@ Span B will be dropped. |
span A is on critical path(+++++) | span A is on critical path(+++++) |
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const trace: TraceResponse = { const trace: TraceResponse = {
traceID: '006c3cf93508f205', traceID: '006c3cf93508f205',

@ -29,7 +29,8 @@ Both spanB and spanC will be dropped. |
span A is on critical path(+++++) | span A is on critical path(+++++) |
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const trace: TraceResponse = { const trace: TraceResponse = {
traceID: 'trace-abc', traceID: 'trace-abc',

@ -28,7 +28,8 @@
Here span B is ref-type is 'FOLLOWS_FROM' | Here span B is ref-type is 'FOLLOWS_FROM' |
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const trace: TraceResponse = { const trace: TraceResponse = {
traceID: 'trace-abc', traceID: 'trace-abc',

@ -26,7 +26,8 @@
| (parent-child tree) | (parent-child tree)
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const trace: TraceResponse = { const trace: TraceResponse = {
traceID: 'trace-abc', traceID: 'trace-abc',

@ -26,7 +26,8 @@
| |
*/ */
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
const trace: TraceResponse = { const trace: TraceResponse = {
traceID: 'trace-abc', traceID: 'trace-abc',

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
/* /*
| |

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceResponse, transformTraceData } from '../../index'; import transformTraceData from '../../model/transform-trace-data';
import { TraceResponse } from '../../types/trace';
/* /*
| |

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceSpan } from '../../types'; import { TraceSpan } from '../../types/trace';
/** /**
* @returns - Returns the span that finished last among the remaining child spans. * @returns - Returns the span that finished last among the remaining child spans.

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceSpan } from '../../types'; import { TraceSpan } from '../../types/trace';
/** /**
* Removes child spans whose refType is FOLLOWS_FROM and their descendants. * Removes child spans whose refType is FOLLOWS_FROM and their descendants.

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceSpan } from '../../types'; import { TraceSpan } from '../../types/trace';
import test3 from '../testCases/test3'; import test3 from '../testCases/test3';
import test4 from '../testCases/test4'; import test4 from '../testCases/test4';
import test6 from '../testCases/test6'; import test6 from '../testCases/test6';

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceSpan } from '../../types'; import { TraceSpan } from '../../types/trace';
/** /**
* This function resolves overflowing child spans for each span. * This function resolves overflowing child spans for each span.

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TNil } from './types'; import TNil from './types/TNil';
import { TraceSpan, TraceSpanReference, Trace } from './types/trace'; import { TraceSpan, TraceSpanReference, Trace } from './types/trace';
/** /**

@ -23,7 +23,7 @@ import { config, reportInteraction } from '@grafana/runtime';
import { Icon, PopoverContent, Tooltip, useTheme2 } from '@grafana/ui'; import { Icon, PopoverContent, Tooltip, useTheme2 } from '@grafana/ui';
import { getButtonStyles } from '@grafana/ui/internal'; import { getButtonStyles } from '@grafana/ui/internal';
import { Trace } from '../../types'; import { Trace } from '../../types/trace';
export type NextPrevResultProps = { export type NextPrevResultProps = {
trace: Trace; trace: Trace;

@ -21,7 +21,7 @@ import { Button, Switch, useStyles2 } from '@grafana/ui';
import { getButtonStyles } from '@grafana/ui/internal'; import { getButtonStyles } from '@grafana/ui/internal';
import { SearchProps } from '../../../useSearch'; import { SearchProps } from '../../../useSearch';
import { Trace } from '../../types'; import { Trace } from '../../types/trace';
import { convertTimeFilter } from '../../utils/filter-spans'; import { convertTimeFilter } from '../../utils/filter-spans';
import NextPrevResult from './NextPrevResult'; import NextPrevResult from './NextPrevResult';

@ -23,7 +23,7 @@ import { Collapse, Icon, InlineField, InlineFieldRow, Select, Stack, Tooltip, us
import { defaultFilters, SearchProps } from '../../../useSearch'; import { defaultFilters, SearchProps } from '../../../useSearch';
import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags'; import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags';
import SearchBarInput from '../../common/SearchBarInput'; import SearchBarInput from '../../common/SearchBarInput';
import { Trace } from '../../types'; import { Trace } from '../../types/trace';
import NextPrevResult from '../SearchBar/NextPrevResult'; import NextPrevResult from '../SearchBar/NextPrevResult';
import TracePageSearchBar from '../SearchBar/TracePageSearchBar'; import TracePageSearchBar from '../SearchBar/TracePageSearchBar';

@ -9,7 +9,7 @@ import { Input, Select, Stack, useStyles2 } from '@grafana/ui';
import { randomId, SearchProps, Tag } from '../../../useSearch'; import { randomId, SearchProps, Tag } from '../../../useSearch';
import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags'; import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags';
import { Trace } from '../../types'; import { Trace } from '../../types/trace';
interface Props { interface Props {
search: SearchProps; search: SearchProps;

@ -19,7 +19,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { withTheme2, stylesFactory } from '@grafana/ui'; import { withTheme2, stylesFactory } from '@grafana/ui';
import { autoColor } from '../../Theme'; import { autoColor } from '../../Theme';
import { TNil } from '../../types'; import TNil from '../../types/TNil';
import { getRgbColorByKey } from '../../utils/color-generator'; import { getRgbColorByKey } from '../../utils/color-generator';
import renderIntoCanvas from './render-into-canvas'; import renderIntoCanvas from './render-into-canvas';

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save