123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- 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<Props> = ({ rule, rulesSource }) => {
- const dispatch = useDispatch();
- const location = useLocation();
- const notifyApp = useAppNotification();
- const style = useStyles2(getStyles);
- const { namespace, group, rulerRule } = rule;
- const [ruleToDelete, setRuleToDelete] = useState<CombinedRule>();
- 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(
- <LinkButton
- className={style.button}
- size="xs"
- key="explore"
- variant="primary"
- icon="chart-line"
- target="__blank"
- href={createExploreLink(rulesSource.name, rule.query)}
- >
- See graph
- </LinkButton>
- );
- }
- if (rule.annotations[Annotation.runbookURL]) {
- leftButtons.push(
- <LinkButton
- className={style.button}
- size="xs"
- key="runbook"
- variant="primary"
- icon="book"
- target="__blank"
- href={textUtil.sanitizeUrl(rule.annotations[Annotation.runbookURL])}
- >
- View runbook
- </LinkButton>
- );
- }
- if (rule.annotations[Annotation.dashboardUID]) {
- const dashboardUID = rule.annotations[Annotation.dashboardUID];
- if (dashboardUID) {
- leftButtons.push(
- <LinkButton
- className={style.button}
- size="xs"
- key="dashboard"
- variant="primary"
- icon="apps"
- target="__blank"
- href={`d/${encodeURIComponent(dashboardUID)}`}
- >
- Go to dashboard
- </LinkButton>
- );
- const panelId = rule.annotations[Annotation.panelID];
- if (panelId) {
- leftButtons.push(
- <LinkButton
- className={style.button}
- size="xs"
- key="panel"
- variant="primary"
- icon="apps"
- target="__blank"
- href={`d/${encodeURIComponent(dashboardUID)}?viewPanel=${encodeURIComponent(panelId)}`}
- >
- Go to panel
- </LinkButton>
- );
- }
- }
- }
- if (alertmanagerSourceName && contextSrv.hasAccess(AccessControlAction.AlertingInstanceCreate, contextSrv.isEditor)) {
- leftButtons.push(
- <LinkButton
- className={style.button}
- size="xs"
- key="silence"
- icon="bell-slash"
- target="__blank"
- href={makeRuleBasedSilenceLink(alertmanagerSourceName, rule)}
- >
- Silence
- </LinkButton>
- );
- }
- if (alertId) {
- leftButtons.push(
- <Fragment key="history">
- <Button className={style.button} size="xs" icon="history" onClick={() => showStateHistoryModal()}>
- Show state history
- </Button>
- {StateHistoryModal}
- </Fragment>
- );
- }
- if (!isViewMode) {
- rightButtons.push(
- <LinkButton
- className={style.button}
- size="xs"
- key="view"
- variant="secondary"
- icon="eye"
- href={createViewLink(rulesSource, rule, returnTo)}
- >
- View
- </LinkButton>
- );
- }
- 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(
- <ClipboardButton
- key="copy"
- onClipboardCopy={() => {
- notifyApp.success('URL copied!');
- }}
- onClipboardError={(copiedText) => {
- notifyApp.error('Error while copying URL', copiedText);
- }}
- className={style.button}
- size="sm"
- getText={buildShareUrl}
- >
- Copy link to rule
- </ClipboardButton>
- );
- }
- rightButtons.push(
- <LinkButton className={style.button} size="xs" key="edit" variant="secondary" icon="pen" href={editURL}>
- Edit
- </LinkButton>
- );
- }
- if (isRemovable && rulerRule && !isFederated) {
- rightButtons.push(
- <Button
- className={style.button}
- size="xs"
- type="button"
- key="delete"
- variant="secondary"
- icon="trash-alt"
- onClick={() => setRuleToDelete(rule)}
- >
- Delete
- </Button>
- );
- }
- if (leftButtons.length || rightButtons.length) {
- return (
- <>
- <div className={style.wrapper}>
- <HorizontalGroup width="auto">{leftButtons.length ? leftButtons : <div />}</HorizontalGroup>
- <HorizontalGroup width="auto">{rightButtons.length ? rightButtons : <div />}</HorizontalGroup>
- </div>
- {!!ruleToDelete && (
- <ConfirmModal
- isOpen={true}
- title="Delete rule"
- body="Deleting this rule will permanently remove it from your alert rule list. Are you sure you want to delete this rule?"
- confirmText="Yes, delete"
- icon="exclamation-triangle"
- onConfirm={deleteRule}
- onDismiss={() => 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;
- }
|