Submit
Path:
~
/
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
alerting
/
unified
/
components
/
rules
/
File Content:
RulesTable.tsx
import { css, cx } from '@emotion/css'; import { isBefore, formatDuration } from 'date-fns'; import React, { useCallback, useMemo } from 'react'; import { GrafanaTheme2, addDurationToDate, isValidDate, isValidDuration, parseDuration, dateTimeFormat, dateTime, } from '@grafana/data'; import { useStyles2, Tooltip } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants'; import { useHasRuler } from '../../hooks/useHasRuler'; import { Annotation } from '../../utils/constants'; import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; import { ProvisioningBadge } from '../Provisioning'; import { RuleLocation } from '../RuleLocation'; import { Tokenize } from '../Tokenize'; import { RuleActionsButtons } from './RuleActionsButtons'; import { RuleConfigStatus } from './RuleConfigStatus'; import { RuleDetails } from './RuleDetails'; import { RuleHealth } from './RuleHealth'; import { RuleState } from './RuleState'; type RuleTableColumnProps = DynamicTableColumnProps<CombinedRule>; type RuleTableItemProps = DynamicTableItemProps<CombinedRule>; interface Props { rules: CombinedRule[]; showGuidelines?: boolean; showGroupColumn?: boolean; showSummaryColumn?: boolean; showNextEvaluationColumn?: boolean; emptyMessage?: string; className?: string; } export const RulesTable = ({ rules, className, showGuidelines = false, emptyMessage = 'No rules found.', showGroupColumn = false, showSummaryColumn = false, showNextEvaluationColumn = false, }: Props) => { const styles = useStyles2(getStyles); const wrapperClass = cx(styles.wrapper, className, { [styles.wrapperMargin]: showGuidelines }); const items = useMemo((): RuleTableItemProps[] => { return rules.map((rule, ruleIdx) => { return { id: `${rule.namespace.name}-${rule.group.name}-${rule.name}-${ruleIdx}`, data: rule, }; }); }, [rules]); const columns = useColumns(showSummaryColumn, showGroupColumn, showNextEvaluationColumn); if (!rules.length) { return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>; } const TableComponent = showGuidelines ? DynamicTableWithGuidelines : DynamicTable; return ( <div className={wrapperClass} data-testid="rules-table"> <TableComponent cols={columns} isExpandable={true} items={items} renderExpandedContent={({ data: rule }) => <RuleDetails rule={rule} />} pagination={{ itemsPerPage: DEFAULT_PER_PAGE_PAGINATION }} paginationStyles={styles.pagination} /> </div> ); }; export const getStyles = (theme: GrafanaTheme2) => ({ wrapperMargin: css` ${theme.breakpoints.up('md')} { margin-left: 36px; } `, emptyMessage: css` padding: ${theme.spacing(1)}; `, wrapper: css` width: auto; border-radius: ${theme.shape.radius.default}; `, pagination: css` display: flex; margin: 0; padding-top: ${theme.spacing(1)}; padding-bottom: ${theme.spacing(0.25)}; justify-content: center; border-left: 1px solid ${theme.colors.border.medium}; border-right: 1px solid ${theme.colors.border.medium}; border-bottom: 1px solid ${theme.colors.border.medium}; `, }); function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showNextEvaluationColumn: boolean) { const { hasRuler, rulerRulesLoaded } = useHasRuler(); const calculateNextEvaluationDate = useCallback((rule: CombinedRule) => { const isValidLastEvaluation = rule.promRule?.lastEvaluation && isValidDate(rule.promRule.lastEvaluation); const isValidIntervalDuration = rule.group.interval && isValidDuration(rule.group.interval); if (!isValidLastEvaluation || !isValidIntervalDuration || isGrafanaRulerRulePaused(rule)) { return; } const intervalDuration = parseDuration(rule.group.interval!); const lastEvaluationDate = Date.parse(rule.promRule?.lastEvaluation || ''); const nextEvaluationDate = addDurationToDate(lastEvaluationDate, intervalDuration); //when `nextEvaluationDate` is a past date it means lastEvaluation was more than one evaluation interval ago. //in this case we use the interval value to show a more generic estimate. //See https://github.com/grafana/grafana/issues/65125 const isPastDate = isBefore(nextEvaluationDate, new Date()); if (isPastDate) { return { humanized: `within ${formatDuration(intervalDuration)}`, fullDate: `within ${formatDuration(intervalDuration)}`, }; } return { humanized: `in ${dateTime(nextEvaluationDate).locale('en').fromNow(true)}`, fullDate: dateTimeFormat(nextEvaluationDate, { format: 'YYYY-MM-DD HH:mm:ss' }), }; }, []); return useMemo((): RuleTableColumnProps[] => { const columns: RuleTableColumnProps[] = [ { id: 'state', label: 'State', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { const { namespace } = rule; const { rulesSource } = namespace; const { promRule, rulerRule } = rule; const isDeleting = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && promRule && !rulerRule); const isCreating = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && rulerRule && !promRule); const isPaused = isGrafanaRulerRulePaused(rule); return <RuleState rule={rule} isDeleting={isDeleting} isCreating={isCreating} isPaused={isPaused} />; }, size: '165px', }, { id: 'name', label: 'Name', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => rule.name, size: showNextEvaluationColumn ? 4 : 5, }, { id: 'provisioned', label: '', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { const rulerRule = rule.rulerRule; const isGrafanaManagedRule = isGrafanaRulerRule(rulerRule); if (!isGrafanaManagedRule) { return null; } const provenance = rulerRule.grafana_alert.provenance; return provenance ? <ProvisioningBadge /> : null; }, size: '100px', }, { id: 'warnings', label: '', renderCell: ({ data: combinedRule }) => <RuleConfigStatus rule={combinedRule} />, size: '45px', }, { id: 'health', label: 'Health', // eslint-disable-next-line react/display-name renderCell: ({ data: { promRule, group } }) => (promRule ? <RuleHealth rule={promRule} /> : null), size: '75px', }, ]; if (showSummaryColumn) { columns.push({ id: 'summary', label: 'Summary', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { return <Tokenize input={rule.annotations[Annotation.summary] ?? ''} />; }, size: showNextEvaluationColumn ? 4 : 5, }); } if (showNextEvaluationColumn) { columns.push({ id: 'nextEvaluation', label: 'Next evaluation', renderCell: ({ data: rule }) => { const nextEvalInfo = calculateNextEvaluationDate(rule); return ( nextEvalInfo && ( <Tooltip placement="top" content={`${nextEvalInfo?.fullDate}`} theme="info"> <span>{nextEvalInfo?.humanized}</span> </Tooltip> ) ); }, size: 2, }); } if (showGroupColumn) { columns.push({ id: 'group', label: 'Group', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { const { namespace, group } = rule; // ungrouped rules are rules that are in the "default" group name const isUngrouped = group.name === 'default'; const groupName = isUngrouped ? ( <RuleLocation namespace={namespace.name} /> ) : ( <RuleLocation namespace={namespace.name} group={group.name} /> ); return groupName; }, size: 5, }); } columns.push({ id: 'actions', label: 'Actions', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { return <RuleActionsButtons rule={rule} rulesSource={rule.namespace.rulesSource} />; }, size: '200px', }); return columns; }, [ showSummaryColumn, showGroupColumn, showNextEvaluationColumn, hasRuler, rulerRulesLoaded, calculateNextEvaluationDate, ]); }
Edit
Rename
Chmod
Delete
FILE
FOLDER
INFO
Name
Size
Permission
Action
state-history
---
0755
ActionButton.tsx
656 bytes
0644
ActionIcon.tsx
1221 bytes
0644
AlertInstanceDetails.tsx
890 bytes
0644
AlertInstanceStateFilter.tsx
2279 bytes
0644
AlertInstancesTable.tsx
3137 bytes
0644
AlertStateTag.tsx
798 bytes
0644
CloneRule.tsx
2736 bytes
0644
CloudRules.tsx
4731 bytes
0644
EditRuleGroupModal.test.tsx
6785 bytes
0644
EditRuleGroupModal.tsx
13135 bytes
0644
GrafanaRules.tsx
4432 bytes
0644
MultipleDataSourcePicker.tsx
5591 bytes
0644
NoRulesCTA.tsx
1878 bytes
0644
ReorderRuleGroupModal.test.tsx
341 bytes
0644
ReorderRuleGroupModal.tsx
6809 bytes
0644
RuleActionsButtons.tsx
7169 bytes
0644
RuleConfigStatus.tsx
1410 bytes
0644
RuleDetails.test.tsx
5891 bytes
0644
RuleDetails.tsx
4376 bytes
0644
RuleDetailsActionButtons.tsx
11094 bytes
0644
RuleDetailsAnnotations.tsx
880 bytes
0644
RuleDetailsDataSources.tsx
2159 bytes
0644
RuleDetailsExpression.tsx
927 bytes
0644
RuleDetailsFederatedSources.tsx
541 bytes
0644
RuleDetailsMatchingInstances.test.tsx
6494 bytes
0644
RuleDetailsMatchingInstances.tsx
6532 bytes
0644
RuleHealth.tsx
949 bytes
0644
RuleListErrors.tsx
5247 bytes
0644
RuleListGroupView.test.tsx
4198 bytes
0644
RuleListGroupView.tsx
1485 bytes
0644
RuleListStateSection.tsx
1413 bytes
0644
RuleListStateView.tsx
2178 bytes
0644
RuleState.tsx
2363 bytes
0644
RuleStats.tsx
3651 bytes
0644
RulesFilter.test.tsx
2591 bytes
0644
RulesFilter.tsx
10765 bytes
0644
RulesGroup.test.tsx
7427 bytes
0644
RulesGroup.tsx
12418 bytes
0644
RulesTable.test.tsx
4684 bytes
0644
RulesTable.tsx
8979 bytes
0644
useCombinedGroupNamespace.tsx
378 bytes
0644
N4ST4R_ID | Naxtarrr