groupBy(groups, 'file'), [groups]);
return (
@@ -73,6 +79,13 @@ export function PaginatedDataSourceLoader({ rulesSourceIdentifier, application }
)}
+ {hasNoRules && (
+
+
+ No rules found
+
+
+ )}
);
@@ -106,3 +119,9 @@ function RuleGroupListItem({ rulesSourceIdentifier, group, namespaceName }: Rule
);
}
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ noRules: css({
+ margin: theme.spacing(1.5, 0, 0.5, 4),
+ }),
+});
diff --git a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
index 4896cf5675d..67110157ea8 100644
--- a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
+++ b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
@@ -1,4 +1,4 @@
-import { groupBy } from 'lodash';
+import { groupBy, isEmpty } from 'lodash';
import { useEffect, useMemo, useRef } from 'react';
import { Trans } from '@grafana/i18n';
@@ -8,6 +8,7 @@ import { GrafanaRuleGroupIdentifier, GrafanaRulesSourceSymbol } from 'app/types/
import { GrafanaPromRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { FolderBulkActionsButton } from '../components/folder-actions/FolderActionsButton';
+import { GrafanaNoRulesCTA } from '../components/rules/NoRulesCTA';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { makeFolderLink } from '../utils/misc';
import { groups } from '../utils/navigation';
@@ -40,6 +41,7 @@ export function PaginatedGrafanaLoader() {
);
const groupsByFolder = useMemo(() => groupBy(groups, 'folderUid'), [groups]);
+ const hasNoRules = isEmpty(groups) && !isLoading;
const isFolderBulkActionsEnabled = config.featureToggles.alertingBulkActionsInUI;
@@ -81,6 +83,7 @@ export function PaginatedGrafanaLoader() {
);
})}
+ {hasNoRules && }
{hasMoreGroups && (
// this div will make the button not stretch
diff --git a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx
index 1579cc236d5..6fad0fa1d7f 100644
--- a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx
@@ -54,6 +54,7 @@ export const DataSourceSection = ({
}
return `/connections/datasources/edit/${String(uid)}`;
})();
+
return (
@@ -73,10 +74,9 @@ export const DataSourceSection = ({
{name}
{description && (
- <>
- {'·'}
- {description}
- >
+
+ {'·'} {description}
+
)}
{showImportLink && (
diff --git a/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx b/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx
new file mode 100644
index 00000000000..fc61542eca7
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-list/components/LazyPagination.tsx
@@ -0,0 +1,18 @@
+import { useTranslate } from '@grafana/i18n';
+import { Button } from '@grafana/ui';
+
+interface LazyPaginationProps {
+ loadMore: () => void;
+ disabled?: boolean;
+}
+
+export function LazyPagination({ loadMore, disabled = false }: LazyPaginationProps) {
+ const { t } = useTranslate();
+ const label = t('alerting.rule-list.pagination.next-page', 'Show more…');
+
+ return (
+
+ );
+}
diff --git a/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx b/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx
new file mode 100644
index 00000000000..8110a871d3a
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-list/hooks/usePaginatedPrometheusGroups.tsx
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { useEffectOnce } from 'react-use';
+
+import { PromRuleGroupDTO } from 'app/types/unified-alerting-dto';
+
+import { isLoading as isLoadingState, isUninitialized as isUninitializedState, useAsync } from '../../hooks/useAsync';
+
+/**
+ * Provides pagination functionality for rule groups with lazy loading.
+ * Instead of loading all groups at once, it uses a generator to fetch them in batches as needed,
+ * which helps with performance when dealing with large numbers of rules.
+ *
+ * @param groupsGenerator - An async generator that yields rule groups in batches
+ * @param pageSize - Number of groups to display per page
+ * @returns Groups loaded so far and controls for navigating through rule groups
+ */
+export function useLazyLoadPrometheusGroups(
+ groupsGenerator: AsyncIterator,
+ pageSize: number
+) {
+ const [groups, setGroups] = useState([]);
+ const [hasMoreGroups, setHasMoreGroups] = useState(true);
+
+ const [{ execute: fetchMoreGroups }, groupsRequestState] = useAsync(async () => {
+ let done = false;
+ const currentGroups: TGroup[] = [];
+
+ while (currentGroups.length < pageSize) {
+ const generatorResult = await groupsGenerator.next();
+ if (generatorResult.done) {
+ done = true;
+ break;
+ }
+ const group = generatorResult.value;
+ currentGroups.push(group);
+ }
+
+ if (done) {
+ setHasMoreGroups(false);
+ }
+
+ setGroups((groups) => groups.concat(currentGroups));
+ });
+
+ // make sure we only load the initial group exactly once
+ useEffectOnce(() => {
+ fetchMoreGroups();
+ });
+
+ const isLoading = isLoadingState(groupsRequestState);
+ const isUninitialized = isUninitializedState(groupsRequestState);
+
+ return {
+ isLoading,
+ groups,
+ hasMoreGroups: !isUninitialized && hasMoreGroups,
+ fetchMoreGroups,
+ };
+}
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 88dcc32fb55..a23feb9ab67 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -731,6 +731,7 @@
"edit": "Edit",
"export": "Export",
"export-all": "Export all",
+ "learn-more": "Learn more",
"loading": "Loading...",
"search-by-matchers": "Search by matchers",
"titles": {
@@ -1402,11 +1403,15 @@
},
"list-view": {
"empty": {
+ "ds-no-rules": "This data source has no rules configured",
"new-alert-rule": "New alert rule",
+ "new-ds-managed-alerting-rule": "New data source-managed alerting rule",
"new-ds-managed-recording-rule": "New data source-managed recording rule",
- "new-grafana-recording-rule": "New Grafana-managed recording rule",
+ "new-grafana-alerting-rule": "New alert rule",
+ "new-grafana-recording-rule": "New recording rule",
"new-recording-rule": "New recording rule",
- "provisioning": "You can also define rules through file provisioning or Terraform. <2>Learn more2>"
+ "no-rules-created": "You haven't created any rules yet",
+ "provisioning": "You can also define rules through file provisioning or Terraform"
},
"no-prom-or-loki-rules": "There are no Prometheus or Loki data sources configured",
"no-rules": "No rules found.",
@@ -2048,6 +2053,7 @@
"description": "Check the data source configuration. Does the data source support Prometheus API?",
"title": "Unable to load rules from this data source"
},
+ "empty-data-source": "No rules found",
"filter-view": {
"cancel-search": "Cancel search",
"no-more-results": "No more results – found {{numberOfRules}} rules",