import { css, cx } from '@emotion/css'; import { indexOf } from 'lodash'; import { Component } from 'react'; import { Unsubscribable } from 'rxjs'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; import { Icon, TextLink, Themeable2, withTheme2 } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constants'; import { ShowConfirmModalEvent } from '../../../../types/events'; import { DashboardModel } from '../../state/DashboardModel'; import { PanelModel } from '../../state/PanelModel'; import { RowOptionsButton } from '../RowOptions/RowOptionsButton'; export interface DashboardRowProps extends Themeable2 { panel: PanelModel; dashboard: DashboardModel; } export class UnthemedDashboardRow extends Component { sub?: Unsubscribable; componentDidMount() { this.sub = this.props.dashboard.events.subscribe(RefreshEvent, this.onVariableUpdated); } componentWillUnmount() { if (this.sub) { this.sub.unsubscribe(); } } onVariableUpdated = () => { this.forceUpdate(); }; onToggle = () => { this.props.dashboard.toggleRow(this.props.panel); }; getWarning = () => { const panels = !!this.props.panel.panels?.length ? this.props.panel.panels : this.props.dashboard.getRowPanels(indexOf(this.props.dashboard.panels, this.props.panel)); const isAnyPanelUsingDashboardDS = panels.some((p) => p.datasource?.uid === SHARED_DASHBOARD_QUERY); if (isAnyPanelUsingDashboardDS) { return (

Panels in this row use the {SHARED_DASHBOARD_QUERY} data source. These panels will reference the panel in the original row, not the ones in the repeated rows.

Learn more
); } return undefined; }; onUpdate = (title: string, repeat?: string | null) => { this.props.panel.setProperty('title', title); this.props.panel.setProperty('repeat', repeat ?? undefined); this.props.panel.render(); this.props.dashboard.processRepeats(); this.forceUpdate(); }; onDelete = () => { appEvents.publish( new ShowConfirmModalEvent({ title: 'Delete row', text: 'Are you sure you want to remove this row and all its panels?', altActionText: 'Delete row only', icon: 'trash-alt', onConfirm: () => { this.props.dashboard.removeRow(this.props.panel, true); }, onAltAction: () => { this.props.dashboard.removeRow(this.props.panel, false); }, }) ); }; render() { const title = getTemplateSrv().replace(this.props.panel.title, this.props.panel.scopedVars, 'text'); const count = this.props.panel.panels ? this.props.panel.panels.length : 0; const panels = count === 1 ? 'panel' : 'panels'; const canEdit = this.props.dashboard.meta.canEdit === true; const collapsed = this.props.panel.collapsed; const styles = getStyles(this.props.theme); return (
{canEdit && (
)} {collapsed === true && ( /* disabling the a11y rules here as the button handles keyboard interactions */ /* this is just to provide a better experience for mouse users */ /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
 
)} {canEdit && (
)}
); } } export const DashboardRow = withTheme2(UnthemedDashboardRow); const getStyles = (theme: GrafanaTheme2) => { const actions = css({ color: theme.colors.text.secondary, opacity: 0, [theme.transitions.handleMotion('no-preference', 'reduce')]: { transition: '200ms opacity ease-in 200ms', }, button: { color: theme.colors.text.secondary, paddingLeft: theme.spacing(2), background: 'transparent', border: 'none', '&:hover': { color: theme.colors.text.maxContrast, }, }, }); return { dashboardRow: css({ display: 'flex', alignItems: 'center', height: '100%', '&:hover, &:focus-within': { [`.${actions}`]: { opacity: 1, }, }, }), dashboardRowCollapsed: css({ background: theme.components.panel.background, }), toggleTargetCollapsed: css({ flex: 1, cursor: 'pointer', marginRight: '15px', }), title: css({ flexGrow: 0, fontSize: theme.typography.h5.fontSize, fontWeight: theme.typography.fontWeightMedium, color: theme.colors.text.primary, background: 'transparent', border: 'none', '.fa': { color: theme.colors.text.secondary, fontSize: theme.typography.size.xs, padding: theme.spacing(0, 1), }, }), actions, count: css({ paddingLeft: theme.spacing(2), color: theme.colors.text.secondary, fontStyle: 'italic', fontSize: theme.typography.size.sm, fontWeight: 'normal', display: 'none', }), countCollapsed: css({ display: 'inline-block', }), dragHandle: css({ cursor: 'move', width: '16px', height: '100%', background: 'url("public/img/grab_dark.svg") no-repeat 50% 50%', backgroundSize: '8px', visibility: 'hidden', position: 'absolute', top: 0, right: 0, }), dragHandleCollapsed: css({ visibility: 'visible', opacity: 1, }), }; };