123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- 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<Props, State> {
- 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 = '<alert-tab />';
- 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<State, 'validationMessage'>) => {
- 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 (
- <Modal isOpen={true} icon="bug" title="Testing rule" onDismiss={onDismiss} onClickBackdrop={onDismiss}>
- <TestRuleResult panel={panel} dashboard={dashboard} />
- </Modal>
- );
- };
- renderDeleteConfirmation = () => {
- if (!this.state.showDeleteConfirmation) {
- return null;
- }
- const { panel } = this.props;
- const onDismiss = () => this.onToggleModal('showDeleteConfirmation');
- return (
- <ConfirmModal
- isOpen={true}
- icon="trash-alt"
- title="Delete"
- body={
- <div>
- Are you sure you want to delete this alert rule?
- <br />
- <small>You need to save dashboard for the delete to take effect.</small>
- </div>
- }
- 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 (
- <Modal isOpen={true} icon="history" title="State history" onDismiss={onDismiss} onClickBackdrop={onDismiss}>
- <StateHistory dashboard={dashboard} panelId={panel.id} onRefresh={() => this.panelCtrl?.refresh()} />
- </Modal>
- );
- };
- render() {
- const { alert, transformations } = this.props.panel;
- const { validationMessage } = this.state;
- const hasTransformations = transformations && transformations.length > 0;
- if (!alert && validationMessage) {
- return <PanelNotSupported message={validationMessage} />;
- }
- const model = {
- title: 'Panel has no alert rule defined',
- buttonIcon: 'bell' as IconName,
- onClick: this.onAddAlert,
- buttonTitle: 'Create Alert',
- };
- return (
- <>
- <CustomScrollbar autoHeightMin="100%">
- <Container padding="md">
- <div aria-label={selectors.components.AlertTab.content}>
- {alert && hasTransformations && (
- <Alert
- severity={AppNotificationSeverity.Error}
- title="Transformations are not supported in alert queries"
- />
- )}
- <div ref={(element) => (this.element = element)} />
- {alert && (
- <HorizontalGroup>
- <Button onClick={() => this.onToggleModal('showStateHistory')} variant="secondary">
- State history
- </Button>
- <Button onClick={() => this.onToggleModal('showTestRule')} variant="secondary">
- Test rule
- </Button>
- <Button onClick={() => this.onToggleModal('showDeleteConfirmation')} variant="destructive">
- Delete
- </Button>
- </HorizontalGroup>
- )}
- {!alert && !validationMessage && <EmptyListCTA {...model} />}
- </div>
- </Container>
- </CustomScrollbar>
- {this.renderTestRule()}
- {this.renderDeleteConfirmation()}
- {this.renderStateHistory()}
- </>
- );
- }
- }
- const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (state, props) => {
- return {
- angularPanelComponent: getPanelStateForModel(state, props.panel)?.angularComponent,
- };
- };
- const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {};
- export const AlertTab = connect(mapStateToProps, mapDispatchToProps)(UnConnectedAlertTab);
|