The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/alerting/unified/components/rules/RulesGroup.tsx

343 lines
10 KiB

import { css } from '@emotion/css';
import pluralize from 'pluralize';
import React, { useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { logInfo } from '@grafana/runtime';
import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
import { useDispatch } from 'app/types';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import { useFolder } from '../../hooks/useFolder';
import { useHasRuler } from '../../hooks/useHasRuler';
import { deleteRulesGroupAction } from '../../state/actions';
import { useRulesAccess } from '../../utils/accessControlHooks';
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
import { makeFolderLink, makeFolderSettingsLink } from '../../utils/misc';
import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules';
import { CollapseToggle } from '../CollapseToggle';
import { RuleLocation } from '../RuleLocation';
import { ActionIcon } from './ActionIcon';
import { EditCloudGroupModal } from './EditRuleGroupModal';
import { ReorderCloudGroupModal } from './ReorderRuleGroupModal';
import { RuleStats } from './RuleStats';
import { RulesTable } from './RulesTable';
type ViewMode = 'grouped' | 'list';
interface Props {
namespace: CombinedRuleNamespace;
group: CombinedRuleGroup;
expandAll: boolean;
viewMode: ViewMode;
}
export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: Props) => {
const { rulesSource } = namespace;
const dispatch = useDispatch();
const styles = useStyles2(getStyles);
const [isEditingGroup, setIsEditingGroup] = useState(false);
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
const [isReorderingGroup, setIsReorderingGroup] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(!expandAll);
const { canEditRules } = useRulesAccess();
useEffect(() => {
setIsCollapsed(!expandAll);
}, [expandAll]);
const { hasRuler, rulerRulesLoaded } = useHasRuler();
const rulerRule = group.rules[0]?.rulerRule;
const folderUID = (rulerRule && isGrafanaRulerRule(rulerRule) && rulerRule.grafana_alert.namespace_uid) || undefined;
const { folder } = useFolder(folderUID);
// group "is deleting" if rules source has ruler, but this group has no rules that are in ruler
const isDeleting =
hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule);
const isFederated = isFederatedRuleGroup(group);
// check if group has provisioned items
const isProvisioned = group.rules.some((rule) => {
return isGrafanaRulerRule(rule.rulerRule) && rule.rulerRule.grafana_alert.provenance;
});
// check what view mode we are in
const isListView = viewMode === 'list';
const isGroupView = viewMode === 'grouped';
const deleteGroup = () => {
dispatch(deleteRulesGroupAction(namespace, group));
setIsDeletingGroup(false);
};
const actionIcons: React.ReactNode[] = [];
// for grafana, link to folder views
if (isDeleting) {
actionIcons.push(
<HorizontalGroup key="is-deleting">
<Spinner />
deleting
</HorizontalGroup>
);
} else if (rulesSource === GRAFANA_RULES_SOURCE_NAME) {
if (folderUID) {
const baseUrl = makeFolderLink(folderUID);
if (folder?.canSave) {
if (isGroupView && !isProvisioned) {
actionIcons.push(
<ActionIcon
aria-label="edit rule group"
data-testid="edit-group"
key="edit"
icon="pen"
tooltip="edit rule group"
onClick={() => setIsEditingGroup(true)}
/>
);
actionIcons.push(
<ActionIcon
aria-label="re-order rules"
data-testid="reorder-group"
key="reorder"
icon="exchange-alt"
tooltip="reorder rules"
className={styles.rotate90}
onClick={() => setIsReorderingGroup(true)}
/>
);
}
if (isListView) {
actionIcons.push(
<ActionIcon
aria-label="go to folder"
key="goto"
icon="folder-open"
tooltip="go to folder"
to={baseUrl}
target="__blank"
/>
);
}
}
if (folder?.canAdmin && isListView) {
actionIcons.push(
<ActionIcon
aria-label="manage permissions"
key="manage-perms"
icon="lock"
tooltip="manage permissions"
to={baseUrl + '/permissions'}
target="__blank"
/>
);
}
}
} else if (canEditRules(rulesSource.name) && hasRuler(rulesSource)) {
if (!isFederated) {
actionIcons.push(
<ActionIcon
aria-label="edit rule group"
data-testid="edit-group"
key="edit"
icon="pen"
tooltip="edit rule group"
onClick={() => setIsEditingGroup(true)}
/>
);
actionIcons.push(
<ActionIcon
aria-label="re-order rules"
data-testid="reorder-group"
key="reorder"
icon="exchange-alt"
tooltip="re-order rules"
className={styles.rotate90}
onClick={() => setIsReorderingGroup(true)}
/>
);
}
actionIcons.push(
<ActionIcon
aria-label="delete rule group"
data-testid="delete-group"
key="delete-group"
icon="trash-alt"
tooltip="delete rule group"
onClick={() => setIsDeletingGroup(true)}
/>
);
}
// ungrouped rules are rules that are in the "default" group name
const groupName = isListView ? (
<RuleLocation namespace={namespace.name} />
) : (
<RuleLocation namespace={namespace.name} group={group.name} />
);
const closeEditModal = (saved = false) => {
if (!saved) {
logInfo(LogMessages.leavingRuleGroupEdit);
}
setIsEditingGroup(false);
};
return (
<div className={styles.wrapper} data-testid="rule-group">
<div className={styles.header} data-testid="rule-group-header">
<CollapseToggle
size="sm"
className={styles.collapseToggle}
isCollapsed={isCollapsed}
onToggle={setIsCollapsed}
data-testid="group-collapse-toggle"
/>
<Icon name={isCollapsed ? 'folder' : 'folder-open'} />
{isCloudRulesSource(rulesSource) && (
<Tooltip content={rulesSource.name} placement="top">
<img
alt={rulesSource.meta.name}
className={styles.dataSourceIcon}
src={rulesSource.meta.info.logos.small}
/>
</Tooltip>
)}
<h6 className={styles.heading}>
{isFederated && <Badge color="purple" text="Federated" />} {groupName}
</h6>
<div className={styles.spacer} />
<div className={styles.headerStats}>
<RuleStats group={group} />
</div>
{isProvisioned && (
<>
<div className={styles.actionsSeparator}>|</div>
<div className={styles.actionIcons}>
<Badge color="purple" text="Provisioned" />
</div>
</>
)}
{!!actionIcons.length && (
<>
<div className={styles.actionsSeparator}>|</div>
<div className={styles.actionIcons}>
<Stack gap={0.5}>{actionIcons}</Stack>
</div>
</>
)}
</div>
{!isCollapsed && (
<RulesTable
showSummaryColumn={true}
className={styles.rulesTable}
showGuidelines={true}
showNextEvaluationColumn={Boolean(group.interval)}
rules={group.rules}
/>
)}
Alerting: Improve UI for making more clear that evaluation interval belongs to the group (#56397) * In GrafanaEvaluationBehaviour component : Split evaluation interval from for duration and add button to edit to allow editing it and warning * Move folder and group fields to the evaluation section in the alert form * Include 'Group behaviour' info in a card and fix 'Edit group behaviour' button onClick. * Create hook for getting groups for a particular folder * Use dropdown in group instead of input and fill it with groups that belong to the selected folder * Add evaluation interval for each group in dropdown , and show warning in case user wants to update it * Avoid saving evaluation interval when some rules in the same group would have invalid For with this change * Clear group value when reseting the drop down * Remove evaluationEvery from form values, show this as a label and add a button to edit the group * Open EditRuleGroupModal for editing evaluation interval form the alert rule form * Fix aligment in group behaviour card * compact space in evaluation behaviour card and change group drop down label * In EditgroupModal, in case of grafana managed group, show folder instead of namespcace label and disable the folder name input * Add edge case in rulesInSameGroupHaveInvalidFor method when For value is zero * Vertically align annotations input to the evaluation section in alert rule form * Fix width when editing new group * Add placeholder for group input * Make folder and group in modal readonly from alert form and disable edit group button when new group * Update texts * Don't show evaluation behaviour section until folder and group are selected * Update texts * Fix merge conflits * Fix wrong margin in evaluation label * Remove non-used isRulerGrafanaRuleDTO method * Remove negative margin to avoid overlaping on Firefox
3 years ago
{isEditingGroup && (
<EditCloudGroupModal
namespace={namespace}
group={group}
Alerting: Improve UI for making more clear that evaluation interval belongs to the group (#56397) * In GrafanaEvaluationBehaviour component : Split evaluation interval from for duration and add button to edit to allow editing it and warning * Move folder and group fields to the evaluation section in the alert form * Include 'Group behaviour' info in a card and fix 'Edit group behaviour' button onClick. * Create hook for getting groups for a particular folder * Use dropdown in group instead of input and fill it with groups that belong to the selected folder * Add evaluation interval for each group in dropdown , and show warning in case user wants to update it * Avoid saving evaluation interval when some rules in the same group would have invalid For with this change * Clear group value when reseting the drop down * Remove evaluationEvery from form values, show this as a label and add a button to edit the group * Open EditRuleGroupModal for editing evaluation interval form the alert rule form * Fix aligment in group behaviour card * compact space in evaluation behaviour card and change group drop down label * In EditgroupModal, in case of grafana managed group, show folder instead of namespcace label and disable the folder name input * Add edge case in rulesInSameGroupHaveInvalidFor method when For value is zero * Vertically align annotations input to the evaluation section in alert rule form * Fix width when editing new group * Add placeholder for group input * Make folder and group in modal readonly from alert form and disable edit group button when new group * Update texts * Don't show evaluation behaviour section until folder and group are selected * Update texts * Fix merge conflits * Fix wrong margin in evaluation label * Remove non-used isRulerGrafanaRuleDTO method * Remove negative margin to avoid overlaping on Firefox
3 years ago
onClose={() => closeEditModal()}
folderUrl={folder?.canEdit ? makeFolderSettingsLink(folder) : undefined}
Alerting: Improve UI for making more clear that evaluation interval belongs to the group (#56397) * In GrafanaEvaluationBehaviour component : Split evaluation interval from for duration and add button to edit to allow editing it and warning * Move folder and group fields to the evaluation section in the alert form * Include 'Group behaviour' info in a card and fix 'Edit group behaviour' button onClick. * Create hook for getting groups for a particular folder * Use dropdown in group instead of input and fill it with groups that belong to the selected folder * Add evaluation interval for each group in dropdown , and show warning in case user wants to update it * Avoid saving evaluation interval when some rules in the same group would have invalid For with this change * Clear group value when reseting the drop down * Remove evaluationEvery from form values, show this as a label and add a button to edit the group * Open EditRuleGroupModal for editing evaluation interval form the alert rule form * Fix aligment in group behaviour card * compact space in evaluation behaviour card and change group drop down label * In EditgroupModal, in case of grafana managed group, show folder instead of namespcace label and disable the folder name input * Add edge case in rulesInSameGroupHaveInvalidFor method when For value is zero * Vertically align annotations input to the evaluation section in alert rule form * Fix width when editing new group * Add placeholder for group input * Make folder and group in modal readonly from alert form and disable edit group button when new group * Update texts * Don't show evaluation behaviour section until folder and group are selected * Update texts * Fix merge conflits * Fix wrong margin in evaluation label * Remove non-used isRulerGrafanaRuleDTO method * Remove negative margin to avoid overlaping on Firefox
3 years ago
/>
)}
{isReorderingGroup && (
<ReorderCloudGroupModal group={group} namespace={namespace} onClose={() => setIsReorderingGroup(false)} />
)}
<ConfirmModal
isOpen={isDeletingGroup}
title="Delete group"
body={
<div>
Deleting this group will permanently remove the group
<br />
and {group.rules.length} alert {pluralize('rule', group.rules.length)} belonging to it.
<br />
Are you sure you want to delete this group?
</div>
}
onConfirm={deleteGroup}
onDismiss={() => setIsDeletingGroup(false)}
confirmText="Delete"
/>
</div>
);
});
RulesGroup.displayName = 'RulesGroup';
export const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css`
& + & {
margin-top: ${theme.spacing(2)};
}
`,
header: css`
display: flex;
flex-direction: row;
align-items: center;
padding: ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)} 0;
background-color: ${theme.colors.background.secondary};
flex-wrap: wrap;
`,
headerStats: css`
span {
vertical-align: middle;
}
${theme.breakpoints.down('sm')} {
order: 2;
width: 100%;
padding-left: ${theme.spacing(1)};
}
`,
heading: css`
margin-left: ${theme.spacing(1)};
margin-bottom: 0;
`,
spacer: css`
flex: 1;
`,
collapseToggle: css`
background: none;
border: none;
margin-top: -${theme.spacing(1)};
margin-bottom: -${theme.spacing(1)};
svg {
margin-bottom: 0;
}
`,
dataSourceIcon: css`
width: ${theme.spacing(2)};
height: ${theme.spacing(2)};
margin-left: ${theme.spacing(2)};
`,
dataSourceOrigin: css`
margin-right: 1em;
color: ${theme.colors.text.disabled};
`,
actionsSeparator: css`
margin: 0 ${theme.spacing(2)};
`,
actionIcons: css`
width: 80px;
align-items: center;
`,
rulesTable: css`
margin-top: ${theme.spacing(3)};
`,
rotate90: css`
transform: rotate(90deg);
`,
});