import { css } from '@emotion/css'; import React, { FC, Fragment, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { GrafanaTheme2, textUtil, urlUtil } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Button, ClipboardButton, ConfirmModal, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; import { RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; import { deleteRuleAction } from '../../state/actions'; import { getAlertmanagerByUid } from '../../utils/alertmanager'; import { Annotation } from '../../utils/constants'; import { getRulesSourceName, isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource'; import { createExploreLink, createViewLink, makeRuleBasedSilenceLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { isFederatedRuleGroup } from '../../utils/rules'; interface Props { rule: CombinedRule; rulesSource: RulesSource; } export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { const dispatch = useDispatch(); const location = useLocation(); const notifyApp = useAppNotification(); const style = useStyles2(getStyles); const { namespace, group, rulerRule } = rule; const [ruleToDelete, setRuleToDelete] = useState(); const alertId = isGrafanaRulerRule(rule.rulerRule) ? rule.rulerRule.grafana_alert.id ?? '' : ''; const { StateHistoryModal, showStateHistoryModal } = useStateHistoryModal(alertId); const alertmanagerSourceName = isGrafanaRulesSource(rulesSource) ? rulesSource : getAlertmanagerByUid(rulesSource.jsonData.alertmanagerUid)?.name; const rulesSourceName = getRulesSourceName(rulesSource); const hasExplorePermission = contextSrv.hasPermission(AccessControlAction.DataSourcesExplore); const leftButtons: JSX.Element[] = []; const rightButtons: JSX.Element[] = []; const isFederated = isFederatedRuleGroup(group); const { isEditable, isRemovable } = useIsRuleEditable(rulesSourceName, rulerRule); const returnTo = location.pathname + location.search; const isViewMode = inViewMode(location.pathname); const deleteRule = () => { if (ruleToDelete && ruleToDelete.rulerRule) { const identifier = ruleId.fromRulerRule( getRulesSourceName(ruleToDelete.namespace.rulesSource), ruleToDelete.namespace.name, ruleToDelete.group.name, ruleToDelete.rulerRule ); dispatch(deleteRuleAction(identifier, { navigateTo: isViewMode ? '/alerting/list' : undefined })); setRuleToDelete(undefined); } }; const buildShareUrl = () => { if (isCloudRulesSource(rulesSource)) { const { appUrl, appSubUrl } = config; const baseUrl = appSubUrl !== '' ? `${appUrl}${appSubUrl}/` : config.appUrl; const ruleUrl = `${encodeURIComponent(rulesSource.name)}/${encodeURIComponent(rule.name)}`; return `${baseUrl}alerting/${ruleUrl}/find`; } return window.location.href.split('?')[0]; }; // explore does not support grafana rule queries atm // neither do "federated rules" if (isCloudRulesSource(rulesSource) && hasExplorePermission && !isFederated) { leftButtons.push( See graph ); } if (rule.annotations[Annotation.runbookURL]) { leftButtons.push( View runbook ); } if (rule.annotations[Annotation.dashboardUID]) { const dashboardUID = rule.annotations[Annotation.dashboardUID]; if (dashboardUID) { leftButtons.push( Go to dashboard ); const panelId = rule.annotations[Annotation.panelID]; if (panelId) { leftButtons.push( Go to panel ); } } } if (alertmanagerSourceName && contextSrv.hasAccess(AccessControlAction.AlertingInstanceCreate, contextSrv.isEditor)) { leftButtons.push( Silence ); } if (alertId) { leftButtons.push( {StateHistoryModal} ); } if (!isViewMode) { rightButtons.push( View ); } if (isEditable && rulerRule && !isFederated) { const sourceName = getRulesSourceName(rulesSource); const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); const editURL = urlUtil.renderUrl( `${config.appSubUrl}/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, { returnTo, } ); if (isViewMode) { rightButtons.push( { notifyApp.success('URL copied!'); }} onClipboardError={(copiedText) => { notifyApp.error('Error while copying URL', copiedText); }} className={style.button} size="sm" getText={buildShareUrl} > Copy link to rule ); } rightButtons.push( Edit ); } if (isRemovable && rulerRule && !isFederated) { rightButtons.push( ); } if (leftButtons.length || rightButtons.length) { return ( <>
{leftButtons.length ? leftButtons :
} {rightButtons.length ? rightButtons :
}
{!!ruleToDelete && ( setRuleToDelete(undefined)} /> )} ); } return null; }; function inViewMode(pathname: string): boolean { return pathname.endsWith('/view'); } export const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css` padding: ${theme.spacing(2)} 0; display: flex; flex-direction: row; justify-content: space-between; flex-wrap: wrap; border-bottom: solid 1px ${theme.colors.border.medium}; `, button: css` height: 24px; margin-top: ${theme.spacing(1)}; font-size: ${theme.typography.size.sm}; `, }); function isGrafanaRulerRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO { if (!rule) { return false; } return (rule as RulerGrafanaRuleDTO).grafana_alert != null; }