import React, { PureComponent } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { EventBusSrv } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { AngularComponent, config, getAngularLoader, getDataSourceSrv } from '@grafana/runtime'; import { Alert, Button, ConfirmModal, Container, CustomScrollbar, HorizontalGroup, IconName, Modal } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; import { AppNotificationSeverity, StoreState } from 'app/types'; import { AlertState } from '../../plugins/datasource/alertmanager/types'; import { PanelNotSupported } from '../dashboard/components/PanelEditor/PanelNotSupported'; import { DashboardModel } from '../dashboard/state/DashboardModel'; import { PanelModel } from '../dashboard/state/PanelModel'; import StateHistory from './StateHistory'; import { TestRuleResult } from './TestRuleResult'; import { getAlertingValidationMessage } from './getAlertingValidationMessage'; interface AngularPanelController { _enableAlert: () => void; alertState: AlertState | null; render: () => void; refresh: () => void; } interface OwnProps { dashboard: DashboardModel; panel: PanelModel; } interface ConnectedProps { angularPanelComponent?: AngularComponent | null; } interface DispatchProps {} export type Props = OwnProps & ConnectedProps & DispatchProps; interface State { validationMessage: string; showStateHistory: boolean; showDeleteConfirmation: boolean; showTestRule: boolean; } class UnConnectedAlertTab extends PureComponent { element?: HTMLDivElement | null; component?: AngularComponent; panelCtrl?: AngularPanelController; state: State = { validationMessage: '', showStateHistory: false, showDeleteConfirmation: false, showTestRule: false, }; async componentDidMount() { if (config.angularSupportEnabled) { await import(/* webpackChunkName: "AlertTabCtrl" */ 'app/features/alerting/AlertTabCtrl'); this.loadAlertTab(); } else { // TODO probably need to migrate AlertTab to react alert('Angular support disabled, legacy alerting cannot function without angular support'); } } onAngularPanelUpdated = () => { this.forceUpdate(); }; componentDidUpdate(prevProps: Props) { this.loadAlertTab(); } componentWillUnmount() { if (this.component) { this.component.destroy(); } } async loadAlertTab() { const { panel, angularPanelComponent } = this.props; if (!this.element || this.component) { return; } if (angularPanelComponent) { const scope = angularPanelComponent.getScope(); // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet if (!scope.$$childHead) { setTimeout(() => { this.forceUpdate(); }); return; } this.panelCtrl = scope.$$childHead.ctrl; } else { this.panelCtrl = this.getReactAlertPanelCtrl(); } const loader = getAngularLoader(); const template = ''; const scopeProps = { ctrl: this.panelCtrl }; this.component = loader.load(this.element, scopeProps, template); const validationMessage = await getAlertingValidationMessage( panel.transformations, panel.targets, getDataSourceSrv(), panel.datasource ); if (validationMessage) { this.setState({ validationMessage }); } } getReactAlertPanelCtrl() { return { panel: this.props.panel, events: new EventBusSrv(), render: () => { this.props.panel.render(); }, } as any; } onAddAlert = () => { this.panelCtrl?._enableAlert(); this.component?.digest(); this.forceUpdate(); }; onToggleModal = (prop: keyof Omit) => { const value = this.state[prop]; this.setState({ ...this.state, [prop]: !value }); }; renderTestRule = () => { if (!this.state.showTestRule) { return null; } const { panel, dashboard } = this.props; const onDismiss = () => this.onToggleModal('showTestRule'); return ( ); }; renderDeleteConfirmation = () => { if (!this.state.showDeleteConfirmation) { return null; } const { panel } = this.props; const onDismiss = () => this.onToggleModal('showDeleteConfirmation'); return ( Are you sure you want to delete this alert rule?
You need to save dashboard for the delete to take effect. } confirmText="Delete alert" onDismiss={onDismiss} onConfirm={() => { delete panel.alert; panel.thresholds = []; if (this.panelCtrl) { this.panelCtrl.alertState = null; this.panelCtrl.render(); } this.component?.digest(); onDismiss(); }} /> ); }; renderStateHistory = () => { if (!this.state.showStateHistory) { return null; } const { panel, dashboard } = this.props; const onDismiss = () => this.onToggleModal('showStateHistory'); return ( this.panelCtrl?.refresh()} /> ); }; render() { const { alert, transformations } = this.props.panel; const { validationMessage } = this.state; const hasTransformations = transformations && transformations.length > 0; if (!alert && validationMessage) { return ; } const model = { title: 'Panel has no alert rule defined', buttonIcon: 'bell' as IconName, onClick: this.onAddAlert, buttonTitle: 'Create Alert', }; return ( <>
{alert && hasTransformations && ( )}
(this.element = element)} /> {alert && ( )} {!alert && !validationMessage && }
{this.renderTestRule()} {this.renderDeleteConfirmation()} {this.renderStateHistory()} ); } } const mapStateToProps: MapStateToProps = (state, props) => { return { angularPanelComponent: getPanelStateForModel(state, props.panel)?.angularComponent, }; }; const mapDispatchToProps: MapDispatchToProps = {}; export const AlertTab = connect(mapStateToProps, mapDispatchToProps)(UnConnectedAlertTab);