RuleList.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { css } from '@emotion/css';
  2. import React, { useEffect, useMemo, useState } from 'react';
  3. import { useDispatch } from 'react-redux';
  4. import { useLocation } from 'react-router-dom';
  5. import { GrafanaTheme2, urlUtil } from '@grafana/data';
  6. import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui';
  7. import { useQueryParams } from 'app/core/hooks/useQueryParams';
  8. import { AlertingPageWrapper } from './components/AlertingPageWrapper';
  9. import { NoRulesSplash } from './components/rules/NoRulesCTA';
  10. import { RuleListErrors } from './components/rules/RuleListErrors';
  11. import { RuleListGroupView } from './components/rules/RuleListGroupView';
  12. import { RuleListStateView } from './components/rules/RuleListStateView';
  13. import { RuleStats } from './components/rules/RuleStats';
  14. import RulesFilter from './components/rules/RulesFilter';
  15. import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces';
  16. import { useFilteredRules } from './hooks/useFilteredRules';
  17. import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
  18. import { fetchAllPromAndRulerRulesAction } from './state/actions';
  19. import { useRulesAccess } from './utils/accessControlHooks';
  20. import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants';
  21. import { getAllRulesSourceNames } from './utils/datasource';
  22. import { getFiltersFromUrlParams } from './utils/misc';
  23. const VIEWS = {
  24. groups: RuleListGroupView,
  25. state: RuleListStateView,
  26. };
  27. const RuleList = withErrorBoundary(
  28. () => {
  29. const dispatch = useDispatch();
  30. const styles = useStyles2(getStyles);
  31. const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []);
  32. const location = useLocation();
  33. const [expandAll, setExpandAll] = useState(false);
  34. const [queryParams] = useQueryParams();
  35. const filters = getFiltersFromUrlParams(queryParams);
  36. const filtersActive = Object.values(filters).some((filter) => filter !== undefined);
  37. const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess();
  38. const view = VIEWS[queryParams['view'] as keyof typeof VIEWS]
  39. ? (queryParams['view'] as keyof typeof VIEWS)
  40. : 'groups';
  41. const ViewComponent = VIEWS[view];
  42. // fetch rules, then poll every RULE_LIST_POLL_INTERVAL_MS
  43. useEffect(() => {
  44. dispatch(fetchAllPromAndRulerRulesAction());
  45. const interval = setInterval(() => dispatch(fetchAllPromAndRulerRulesAction()), RULE_LIST_POLL_INTERVAL_MS);
  46. return () => {
  47. clearInterval(interval);
  48. };
  49. }, [dispatch]);
  50. const promRuleRequests = useUnifiedAlertingSelector((state) => state.promRules);
  51. const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules);
  52. const dispatched = rulesDataSourceNames.some(
  53. (name) => promRuleRequests[name]?.dispatched || rulerRuleRequests[name]?.dispatched
  54. );
  55. const loading = rulesDataSourceNames.some(
  56. (name) => promRuleRequests[name]?.loading || rulerRuleRequests[name]?.loading
  57. );
  58. const haveResults = rulesDataSourceNames.some(
  59. (name) =>
  60. (promRuleRequests[name]?.result?.length && !promRuleRequests[name]?.error) ||
  61. (Object.keys(rulerRuleRequests[name]?.result || {}).length && !rulerRuleRequests[name]?.error)
  62. );
  63. const showNewAlertSplash = dispatched && !loading && !haveResults;
  64. const combinedNamespaces = useCombinedRuleNamespaces();
  65. const filteredNamespaces = useFilteredRules(combinedNamespaces);
  66. return (
  67. <AlertingPageWrapper pageId="alert-list" isLoading={loading && !haveResults}>
  68. <RuleListErrors />
  69. {!showNewAlertSplash && (
  70. <>
  71. <RulesFilter />
  72. <div className={styles.break} />
  73. <div className={styles.buttonsContainer}>
  74. <div className={styles.statsContainer}>
  75. {view === 'groups' && filtersActive && (
  76. <Button
  77. className={styles.expandAllButton}
  78. icon={expandAll ? 'angle-double-up' : 'angle-double-down'}
  79. variant="secondary"
  80. onClick={() => setExpandAll(!expandAll)}
  81. >
  82. {expandAll ? 'Collapse all' : 'Expand all'}
  83. </Button>
  84. )}
  85. <RuleStats showInactive={true} showRecording={true} namespaces={filteredNamespaces} />
  86. </div>
  87. {(canCreateGrafanaRules || canCreateCloudRules) && (
  88. <LinkButton
  89. href={urlUtil.renderUrl('alerting/new', { returnTo: location.pathname + location.search })}
  90. icon="plus"
  91. >
  92. New alert rule
  93. </LinkButton>
  94. )}
  95. </div>
  96. </>
  97. )}
  98. {showNewAlertSplash && <NoRulesSplash />}
  99. {haveResults && <ViewComponent expandAll={expandAll} namespaces={filteredNamespaces} />}
  100. </AlertingPageWrapper>
  101. );
  102. },
  103. { style: 'page' }
  104. );
  105. const getStyles = (theme: GrafanaTheme2) => ({
  106. break: css`
  107. width: 100%;
  108. height: 0;
  109. margin-bottom: ${theme.spacing(2)};
  110. border-bottom: solid 1px ${theme.colors.border.medium};
  111. `,
  112. buttonsContainer: css`
  113. margin-bottom: ${theme.spacing(2)};
  114. display: flex;
  115. justify-content: space-between;
  116. `,
  117. statsContainer: css`
  118. display: flex;
  119. flex-direction: row;
  120. align-items: center;
  121. `,
  122. expandAllButton: css`
  123. margin-right: ${theme.spacing(1)};
  124. `,
  125. });
  126. export default RuleList;