diff --git a/packages/grafana-data/src/events/common.ts b/packages/grafana-data/src/events/common.ts index 9021ddb7ccb..0bad1af0080 100644 --- a/packages/grafana-data/src/events/common.ts +++ b/packages/grafana-data/src/events/common.ts @@ -1,5 +1,5 @@ import { AnnotationEvent, DataFrame } from '../types'; -import { BusEventWithPayload } from './types'; +import { BusEventBase, BusEventWithPayload } from './types'; /** * When hovering over an element this will identify @@ -26,7 +26,7 @@ export class DataHoverEvent extends BusEventWithPayload { } /** @alpha */ -export class DataHoverClearEvent extends BusEventWithPayload { +export class DataHoverClearEvent extends BusEventBase { static type = 'data-hover-clear'; } diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 76582b17eba..2cb1f678d8b 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -4,9 +4,10 @@ import { Themeable2 } from '../../types'; import { findMidPointYPosition, pluginLog } from '../uPlot/utils'; import { DataFrame, + DataHoverClearEvent, + DataHoverEvent, FieldMatcherID, fieldMatchers, - LegacyGraphHoverClearEvent, LegacyGraphHoverEvent, TimeRange, TimeZone, @@ -125,42 +126,60 @@ export class GraphNG extends React.Component { return state; } + handleCursorUpdate(evt: DataHoverEvent | LegacyGraphHoverEvent) { + const time = evt.payload?.point?.time; + const u = this.plotInstance.current; + if (u && time) { + // Try finding left position on time axis + const left = u.valToPos(time, 'x'); + let top; + if (left) { + // find midpoint between points at current idx + top = findMidPointYPosition(u, u.posToIdx(left)); + } + + if (!top || !left) { + return; + } + + u.setCursor({ + left, + top, + }); + } + } + componentDidMount() { this.panelContext = this.context as PanelContext; const { eventBus } = this.panelContext; this.subscription.add( eventBus - .getStream(LegacyGraphHoverEvent) + .getStream(DataHoverEvent) .pipe(throttleTime(50)) .subscribe({ next: (evt) => { - const u = this.plotInstance.current; - if (u) { - // Try finding left position on time axis - const left = u.valToPos(evt.payload.point.time, 'x'); - let top; - if (left) { - // find midpoint between points at current idx - top = findMidPointYPosition(u, u.posToIdx(left)); - } - - if (!top || !left) { - return; - } - - u.setCursor({ - left, - top, - }); + if (eventBus === evt.origin) { + return; } + this.handleCursorUpdate(evt); }, }) ); + // Legacy events (from flot graph) + this.subscription.add( + eventBus + .getStream(LegacyGraphHoverEvent) + .pipe(throttleTime(50)) + .subscribe({ + next: (evt) => this.handleCursorUpdate(evt), + }) + ); + this.subscription.add( eventBus - .getStream(LegacyGraphHoverClearEvent) + .getStream(DataHoverClearEvent) .pipe(throttleTime(50)) .subscribe({ next: () => { diff --git a/packages/grafana-ui/src/components/Logs/LogRow.tsx b/packages/grafana-ui/src/components/Logs/LogRow.tsx index e68d932369d..5c2c2b6a9ed 100644 --- a/packages/grafana-ui/src/components/Logs/LogRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRow.tsx @@ -53,6 +53,7 @@ interface Props extends Themeable2 { showContextToggle?: (row?: LogRowModel) => boolean; onClickShowDetectedField?: (key: string) => void; onClickHideDetectedField?: (key: string) => void; + onLogRowHover?: (row?: LogRowModel) => void; } interface State { @@ -141,6 +142,7 @@ class UnThemedLogRow extends PureComponent { theme, getFieldLinks, forceEscape, + onLogRowHover, } = this.props; const { showDetails, showContext } = this.state; const style = getLogRowStyles(theme, row.logLevel); @@ -157,7 +159,16 @@ class UnThemedLogRow extends PureComponent { return ( <> - + { + onLogRowHover && onLogRowHover(row); + }} + onMouseLeave={() => { + onLogRowHover && onLogRowHover(undefined); + }} + > {showDuplicates && ( {processedRow.duplicates && processedRow.duplicates > 0 ? `${processedRow.duplicates + 1}x` : null} diff --git a/packages/grafana-ui/src/components/Logs/LogRows.tsx b/packages/grafana-ui/src/components/Logs/LogRows.tsx index 49e0acdb42a..0971ddba9ee 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.tsx @@ -33,6 +33,7 @@ export interface Props extends Themeable2 { getFieldLinks?: (field: Field, rowIndex: number) => Array>; onClickShowDetectedField?: (key: string) => void; onClickHideDetectedField?: (key: string) => void; + onLogRowHover?: (row?: LogRowModel) => void; } interface State { @@ -99,6 +100,7 @@ class UnThemedLogRows extends PureComponent { onClickShowDetectedField, onClickHideDetectedField, forceEscape, + onLogRowHover, } = this.props; const { renderAll } = this.state; const { logsRowsTable } = getLogRowStyles(theme); @@ -144,6 +146,7 @@ class UnThemedLogRows extends PureComponent { getFieldLinks={getFieldLinks} logsSortOrder={logsSortOrder} forceEscape={forceEscape} + onLogRowHover={onLogRowHover} /> ))} {hasData && @@ -170,6 +173,7 @@ class UnThemedLogRows extends PureComponent { getFieldLinks={getFieldLinks} logsSortOrder={logsSortOrder} forceEscape={forceEscape} + onLogRowHover={onLogRowHover} /> ))} {hasData && !renderAll && ( diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index 0ccbdb7638e..af1e092e96d 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -345,7 +345,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor if (x < 0 && y < 0) { payload.point[xScaleUnit] = null; payload.point[yScaleKey] = null; - eventBus.publish(new DataHoverClearEvent(payload)); + eventBus.publish(new DataHoverClearEvent()); } else { // convert the points payload.point[xScaleUnit] = src.posToVal(x, xScaleKey); diff --git a/public/app/plugins/panel/debug/CursorView.tsx b/public/app/plugins/panel/debug/CursorView.tsx index b2b2768fb40..f075666f334 100644 --- a/public/app/plugins/panel/debug/CursorView.tsx +++ b/public/app/plugins/panel/debug/CursorView.tsx @@ -5,8 +5,7 @@ import { LegacyGraphHoverClearEvent, DataHoverEvent, DataHoverClearEvent, - DataHoverPayload, - BusEventWithPayload, + BusEventBase, } from '@grafana/data'; import { Subscription } from 'rxjs'; import { CustomScrollbar } from '@grafana/ui'; @@ -17,7 +16,7 @@ interface Props { } interface State { - event?: BusEventWithPayload; + event?: BusEventBase; } export class CursorView extends Component { subscription = new Subscription(); @@ -65,9 +64,13 @@ export class CursorView extends Component {

Origin: {(origin as any)?.path}

Type: {type} -
{JSON.stringify(payload.point, null, '  ')}
- {payload.data && ( - + {Boolean(payload) && ( + <> +
{JSON.stringify(payload.point, null, '  ')}
+ {payload.data && ( + + )} + )}
); diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index eecd123ce2f..1522919d4ce 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -171,7 +171,7 @@ export class GeomapPanel extends Component { // Tooltip listener this.map.on('pointermove', this.pointerMoveListener); this.map.getViewport().addEventListener('mouseout', (evt) => { - this.props.eventBus.publish(new DataHoverClearEvent({ point: {} })); + this.props.eventBus.publish(new DataHoverClearEvent()); }); }; diff --git a/public/app/plugins/panel/graph/graph_tooltip.ts b/public/app/plugins/panel/graph/graph_tooltip.ts index 7b923de9cb0..b1281eb6588 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.ts +++ b/public/app/plugins/panel/graph/graph_tooltip.ts @@ -1,7 +1,13 @@ import $ from 'jquery'; import { appEvents } from 'app/core/core'; import { CoreEvents } from 'app/types'; -import { textUtil, systemDateFormats, LegacyGraphHoverClearEvent, LegacyGraphHoverEvent } from '@grafana/data'; +import { + textUtil, + systemDateFormats, + LegacyGraphHoverClearEvent, + LegacyGraphHoverEvent, + DataHoverClearEvent, +} from '@grafana/data'; export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) { const self = this; @@ -153,6 +159,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope } } dashboard.events.publish(new LegacyGraphHoverClearEvent()); + dashboard.events.publish(new DataHoverClearEvent()); }); elem.bind('plothover', (event: any, pos: { panelRelY: number; pageY: number }, item: any) => { diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 7446ff246f2..7df81c7b65f 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -1,7 +1,16 @@ import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { LogRows, CustomScrollbar, LogLabels, useStyles2 } from '@grafana/ui'; -import { PanelProps, Field, Labels, GrafanaTheme2, LogsSortOrder } from '@grafana/data'; +import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui'; +import { + PanelProps, + Field, + Labels, + GrafanaTheme2, + LogsSortOrder, + LogRowModel, + DataHoverClearEvent, + DataHoverEvent, +} from '@grafana/data'; import { Options } from './types'; import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model'; import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; @@ -29,6 +38,24 @@ export const LogsPanel: React.FunctionComponent = ({ const [scrollTop, setScrollTop] = useState(0); const logsContainerRef = useRef(null); + const { eventBus } = usePanelContext(); + const onLogRowHover = useCallback( + (row?: LogRowModel) => { + if (!row) { + eventBus.publish(new DataHoverClearEvent()); + } else { + eventBus.publish( + new DataHoverEvent({ + point: { + time: row.timeEpochMs, + }, + }) + ); + } + }, + [eventBus] + ); + // Important to memoize stuff here, as panel rerenders a lot for example when resizing. const [logRows, deduplicatedRows, commonLabels] = useMemo(() => { const newResults = data ? dataFrameToLogsModel(data.series, data.request?.intervalMs) : null; @@ -85,6 +112,7 @@ export const LogsPanel: React.FunctionComponent = ({ logsSortOrder={sortOrder} enableLogDetails={enableLogDetails} previewLimit={isAscending ? logRows.length : undefined} + onLogRowHover={onLogRowHover} /> {showCommonLabels && isAscending && renderCommonLabels()}