RulesTable.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { css, cx } from '@emotion/css';
  2. import React, { FC, useMemo } from 'react';
  3. import { GrafanaTheme2 } from '@grafana/data';
  4. import { useStyles2 } from '@grafana/ui';
  5. import { CombinedRule } from 'app/types/unified-alerting';
  6. import { useHasRuler } from '../../hooks/useHasRuler';
  7. import { Annotation } from '../../utils/constants';
  8. import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
  9. import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
  10. import { RuleLocation } from '../RuleLocation';
  11. import { RuleDetails } from './RuleDetails';
  12. import { RuleHealth } from './RuleHealth';
  13. import { RuleState } from './RuleState';
  14. type RuleTableColumnProps = DynamicTableColumnProps<CombinedRule>;
  15. type RuleTableItemProps = DynamicTableItemProps<CombinedRule>;
  16. interface Props {
  17. rules: CombinedRule[];
  18. showGuidelines?: boolean;
  19. showGroupColumn?: boolean;
  20. showSummaryColumn?: boolean;
  21. emptyMessage?: string;
  22. className?: string;
  23. }
  24. export const RulesTable: FC<Props> = ({
  25. rules,
  26. className,
  27. showGuidelines = false,
  28. emptyMessage = 'No rules found.',
  29. showGroupColumn = false,
  30. showSummaryColumn = false,
  31. }) => {
  32. const styles = useStyles2(getStyles);
  33. const wrapperClass = cx(styles.wrapper, className, { [styles.wrapperMargin]: showGuidelines });
  34. const items = useMemo((): RuleTableItemProps[] => {
  35. const seenKeys: string[] = [];
  36. return rules.map((rule, ruleIdx) => {
  37. let key = JSON.stringify([rule.promRule?.type, rule.labels, rule.query, rule.name, rule.annotations]);
  38. if (seenKeys.includes(key)) {
  39. key += `-${ruleIdx}`;
  40. }
  41. seenKeys.push(key);
  42. return {
  43. id: key,
  44. data: rule,
  45. };
  46. });
  47. }, [rules]);
  48. const columns = useColumns(showSummaryColumn, showGroupColumn);
  49. if (!rules.length) {
  50. return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
  51. }
  52. const TableComponent = showGuidelines ? DynamicTableWithGuidelines : DynamicTable;
  53. return (
  54. <div className={wrapperClass} data-testid="rules-table">
  55. <TableComponent
  56. cols={columns}
  57. isExpandable={true}
  58. items={items}
  59. renderExpandedContent={({ data: rule }) => <RuleDetails rule={rule} />}
  60. />
  61. </div>
  62. );
  63. };
  64. export const getStyles = (theme: GrafanaTheme2) => ({
  65. wrapperMargin: css`
  66. ${theme.breakpoints.up('md')} {
  67. margin-left: 36px;
  68. }
  69. `,
  70. emptyMessage: css`
  71. padding: ${theme.spacing(1)};
  72. `,
  73. wrapper: css`
  74. width: auto;
  75. background-color: ${theme.colors.background.secondary};
  76. border-radius: ${theme.shape.borderRadius()};
  77. `,
  78. });
  79. function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
  80. const { hasRuler, rulerRulesLoaded } = useHasRuler();
  81. return useMemo((): RuleTableColumnProps[] => {
  82. const columns: RuleTableColumnProps[] = [
  83. {
  84. id: 'state',
  85. label: 'State',
  86. // eslint-disable-next-line react/display-name
  87. renderCell: ({ data: rule }) => {
  88. const { namespace } = rule;
  89. const { rulesSource } = namespace;
  90. const { promRule, rulerRule } = rule;
  91. const isDeleting = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && promRule && !rulerRule);
  92. const isCreating = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && rulerRule && !promRule);
  93. return <RuleState rule={rule} isDeleting={isDeleting} isCreating={isCreating} />;
  94. },
  95. size: '165px',
  96. },
  97. {
  98. id: 'name',
  99. label: 'Name',
  100. // eslint-disable-next-line react/display-name
  101. renderCell: ({ data: rule }) => rule.name,
  102. size: 5,
  103. },
  104. {
  105. id: 'health',
  106. label: 'Health',
  107. // eslint-disable-next-line react/display-name
  108. renderCell: ({ data: { promRule } }) => (promRule ? <RuleHealth rule={promRule} /> : null),
  109. size: '75px',
  110. },
  111. ];
  112. if (showSummaryColumn) {
  113. columns.push({
  114. id: 'summary',
  115. label: 'Summary',
  116. // eslint-disable-next-line react/display-name
  117. renderCell: ({ data: rule }) => rule.annotations[Annotation.summary] ?? '',
  118. size: 5,
  119. });
  120. }
  121. if (showGroupColumn) {
  122. columns.push({
  123. id: 'group',
  124. label: 'Group',
  125. // eslint-disable-next-line react/display-name
  126. renderCell: ({ data: rule }) => {
  127. const { namespace, group } = rule;
  128. // ungrouped rules are rules that are in the "default" group name
  129. const isUngrouped = group.name === 'default';
  130. const groupName = isUngrouped ? (
  131. <RuleLocation namespace={namespace.name} />
  132. ) : (
  133. <RuleLocation namespace={namespace.name} group={group.name} />
  134. );
  135. return groupName;
  136. },
  137. size: 5,
  138. });
  139. }
  140. return columns;
  141. }, [hasRuler, rulerRulesLoaded, showSummaryColumn, showGroupColumn]);
  142. }