MatchedSilencedRules.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { css } from '@emotion/css';
  2. import React, { useEffect, useState } from 'react';
  3. import { useFormContext } from 'react-hook-form';
  4. import { useDispatch } from 'react-redux';
  5. import { useDebounce } from 'react-use';
  6. import { dateTime, GrafanaTheme2 } from '@grafana/data';
  7. import { Badge, useStyles2 } from '@grafana/ui';
  8. import { Alert, AlertingRule } from 'app/types/unified-alerting';
  9. import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces';
  10. import { fetchAllPromAndRulerRulesAction } from '../../state/actions';
  11. import { MatcherFieldValue, SilenceFormFields } from '../../types/silence-form';
  12. import { findAlertInstancesWithMatchers } from '../../utils/matchers';
  13. import { isAlertingRule } from '../../utils/rules';
  14. import { AlertLabels } from '../AlertLabels';
  15. import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
  16. import { AlertStateTag } from '../rules/AlertStateTag';
  17. type MatchedRulesTableItemProps = DynamicTableItemProps<{
  18. matchedInstance: Alert;
  19. }>;
  20. type MatchedRulesTableColumnProps = DynamicTableColumnProps<{ matchedInstance: Alert }>;
  21. export const MatchedSilencedRules = () => {
  22. const [matchedAlertRules, setMatchedAlertRules] = useState<MatchedRulesTableItemProps[]>([]);
  23. const formApi = useFormContext<SilenceFormFields>();
  24. const dispatch = useDispatch();
  25. const { watch } = formApi;
  26. const matchers: MatcherFieldValue[] = watch('matchers');
  27. const styles = useStyles2(getStyles);
  28. const columns = useColumns();
  29. useEffect(() => {
  30. dispatch(fetchAllPromAndRulerRulesAction());
  31. }, [dispatch]);
  32. const combinedNamespaces = useCombinedRuleNamespaces();
  33. useDebounce(
  34. () => {
  35. const matchedInstances = combinedNamespaces.flatMap((namespace) => {
  36. return namespace.groups.flatMap((group) => {
  37. return group.rules
  38. .map((combinedRule) => combinedRule.promRule)
  39. .filter((rule): rule is AlertingRule => isAlertingRule(rule))
  40. .flatMap((rule) => findAlertInstancesWithMatchers(rule.alerts ?? [], matchers));
  41. });
  42. });
  43. setMatchedAlertRules(matchedInstances);
  44. },
  45. 500,
  46. [combinedNamespaces, matchers]
  47. );
  48. return (
  49. <div>
  50. <h4 className={styles.title}>
  51. Affected alert instances
  52. {matchedAlertRules.length > 0 ? (
  53. <Badge className={styles.badge} color="blue" text={matchedAlertRules.length} />
  54. ) : null}
  55. </h4>
  56. <div className={styles.table}>
  57. {matchers.every((matcher) => !matcher.value && !matcher.name) ? (
  58. <span>Add a valid matcher to see affected alerts</span>
  59. ) : (
  60. <>
  61. <DynamicTable items={matchedAlertRules.slice(0, 5) ?? []} isExpandable={false} cols={columns} />
  62. {matchedAlertRules.length > 5 && (
  63. <div className={styles.moreMatches}>and {matchedAlertRules.length - 5} more</div>
  64. )}
  65. </>
  66. )}
  67. </div>
  68. </div>
  69. );
  70. };
  71. function useColumns(): MatchedRulesTableColumnProps[] {
  72. return [
  73. {
  74. id: 'state',
  75. label: 'State',
  76. renderCell: function renderStateTag({ data: { matchedInstance } }) {
  77. return <AlertStateTag state={matchedInstance.state} />;
  78. },
  79. size: '160px',
  80. },
  81. {
  82. id: 'labels',
  83. label: 'Labels',
  84. renderCell: function renderName({ data: { matchedInstance } }) {
  85. return <AlertLabels labels={matchedInstance.labels} />;
  86. },
  87. size: '250px',
  88. },
  89. {
  90. id: 'created',
  91. label: 'Created',
  92. renderCell: function renderSummary({ data: { matchedInstance } }) {
  93. return (
  94. <>
  95. {matchedInstance.activeAt.startsWith('0001')
  96. ? '-'
  97. : dateTime(matchedInstance.activeAt).format('YYYY-MM-DD HH:mm:ss')}
  98. </>
  99. );
  100. },
  101. size: '400px',
  102. },
  103. ];
  104. }
  105. const getStyles = (theme: GrafanaTheme2) => ({
  106. table: css`
  107. max-width: ${theme.breakpoints.values.lg}px;
  108. `,
  109. moreMatches: css`
  110. margin-top: ${theme.spacing(1)};
  111. `,
  112. title: css`
  113. display: flex;
  114. align-items: center;
  115. `,
  116. badge: css`
  117. margin-left: ${theme.spacing(1)};
  118. `,
  119. });