diff --git a/.betterer.results b/.betterer.results index a7ee5f2f633..15b93c2da1f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3897,9 +3897,6 @@ exports[`better eslint`] = { "public/app/features/explore/Explore.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/features/explore/Explore.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/explore/ExploreGraph.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index aaaec6b7a93..151b6930c7a 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { AutoSizerProps } from 'react-virtualized-auto-sizer'; -import { DataSourceApi, LoadingState, CoreApp, createTheme } from '@grafana/data'; +import { DataSourceApi, LoadingState, CoreApp, createTheme, EventBusSrv } from '@grafana/data'; import { configureStore } from 'app/store/configureStore'; import { ExploreId } from 'app/types/explore'; @@ -86,6 +86,7 @@ const dummyProps: Props = { splitted: false, changeGraphStyle: () => {}, graphStyle: 'lines', + eventBus: new EventBusSrv(), }; jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 4863d04c0c0..a5f7b1ce657 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -4,7 +4,6 @@ import memoizeOne from 'memoize-one'; import React, { createRef } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { compose } from 'redux'; import { Unsubscribable } from 'rxjs'; import { @@ -14,6 +13,7 @@ import { LoadingState, QueryFixAction, RawTimeRange, + EventBus, SplitOpenOptions, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -87,6 +87,7 @@ const getStyles = (theme: GrafanaTheme2) => { export interface ExploreProps extends Themeable2 { exploreId: ExploreId; theme: GrafanaTheme2; + eventBus: EventBus; } enum ExploreDrawer { @@ -128,12 +129,16 @@ export class Explore extends React.PureComponent { scrollElement: HTMLDivElement | undefined; absoluteTimeUnsubsciber: Unsubscribable | undefined; topOfViewRef = createRef(); + graphEventBus: EventBus; + logsEventBus: EventBus; constructor(props: Props) { super(props); this.state = { openDrawer: undefined, }; + this.graphEventBus = props.eventBus.newScopedBus('graph', { onlyLocal: false }); + this.logsEventBus = props.eventBus.newScopedBus('logs', { onlyLocal: false }); } componentDidMount() { @@ -291,6 +296,7 @@ export class Explore extends React.PureComponent { splitOpenFn={this.onSplitOpen('graph')} loadingState={queryResponse.state} anchorToZero={false} + eventBus={this.graphEventBus} /> ); @@ -324,6 +330,7 @@ export class Explore extends React.PureComponent { onStartScanning={this.onStartScanning} onStopScanning={this.onStopScanning} scrollElement={this.scrollElement} + eventBus={this.logsEventBus} splitOpenFn={this.onSplitOpen('logs')} /> ); @@ -550,4 +557,4 @@ const mapDispatchToProps = { const connector = connect(mapStateToProps, mapDispatchToProps); -export default compose(connector, withTheme2)(Explore) as React.ComponentType<{ exploreId: ExploreId }>; +export default withTheme2(connector(Explore)); diff --git a/public/app/features/explore/ExploreGraph.tsx b/public/app/features/explore/ExploreGraph.tsx index 8db90298644..08cd66aeafe 100644 --- a/public/app/features/explore/ExploreGraph.tsx +++ b/public/app/features/explore/ExploreGraph.tsx @@ -18,6 +18,8 @@ import { LoadingState, SplitOpen, TimeZone, + DashboardCursorSync, + EventBus, } from '@grafana/data'; import { PanelRenderer } from '@grafana/runtime'; import { GraphDrawStyle, LegendDisplayMode, TooltipDisplayMode, SortOrder } from '@grafana/schema'; @@ -29,7 +31,6 @@ import { useStyles2, useTheme2, } from '@grafana/ui'; -import appEvents from 'app/core/app_events'; import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config'; import { TimeSeriesOptions } from 'app/plugins/panel/timeseries/types'; @@ -54,6 +55,7 @@ interface Props { onChangeTime: (timeRange: AbsoluteTimeRange) => void; graphStyle: ExploreGraphStyle; anchorToZero: boolean; + eventBus: EventBus; } export function ExploreGraph({ @@ -70,6 +72,7 @@ export function ExploreGraph({ graphStyle, tooltipDisplayMode = TooltipDisplayMode.Single, anchorToZero, + eventBus, }: Props) { const theme = useTheme2(); const [showAllTimeSeries, setShowAllTimeSeries] = useState(false); @@ -142,7 +145,8 @@ export function ExploreGraph({ const seriesToShow = showAllTimeSeries ? dataWithConfig : dataWithConfig.slice(0, MAX_NUMBER_OF_TIME_SERIES); const panelContext: PanelContext = { - eventBus: appEvents, + eventBus, + sync: () => DashboardCursorSync.Crosshair, onSplitOpen: splitOpenFn, onToggleSeriesVisibility(label: string, mode: SeriesVisibilityChangeMode) { setBaseStructureRev((r) => r + 1); diff --git a/public/app/features/explore/ExplorePaneContainer.tsx b/public/app/features/explore/ExplorePaneContainer.tsx index fb0db9616ef..15f9cafcc91 100644 --- a/public/app/features/explore/ExplorePaneContainer.tsx +++ b/public/app/features/explore/ExplorePaneContainer.tsx @@ -3,7 +3,7 @@ import memoizeOne from 'memoize-one'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { ExploreUrlState, EventBusExtended, EventBusSrv, GrafanaTheme2 } from '@grafana/data'; +import { ExploreUrlState, EventBusExtended, EventBusSrv, GrafanaTheme2, EventBus } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Themeable2, withTheme2 } from '@grafana/ui'; import { config } from 'app/core/config'; @@ -50,6 +50,7 @@ interface OwnProps extends Themeable2 { exploreId: ExploreId; urlQuery: string; split: boolean; + eventBus: EventBus; } interface Props extends OwnProps, ConnectedProps {} @@ -143,12 +144,12 @@ class ExplorePaneContainerUnconnected extends React.PureComponent { }; render() { - const { theme, split, exploreId, initialized } = this.props; + const { theme, split, exploreId, initialized, eventBus } = this.props; const styles = getStyles(theme); const exploreClass = cx(styles.explore, split && styles.exploreSplit); return (
- {initialized && } + {initialized && }
); } diff --git a/public/app/features/explore/Logs.test.tsx b/public/app/features/explore/Logs.test.tsx index 767f52cd9f3..b53ea559353 100644 --- a/public/app/features/explore/Logs.test.tsx +++ b/public/app/features/explore/Logs.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { LoadingState, LogLevel, LogRowModel, MutableDataFrame, toUtc } from '@grafana/data'; +import { LoadingState, LogLevel, LogRowModel, MutableDataFrame, toUtc, EventBusSrv } from '@grafana/data'; import { ExploreId } from 'app/types'; import { Logs } from './Logs'; @@ -37,6 +37,7 @@ describe('Logs', () => { getFieldLinks={() => { return []; }} + eventBus={new EventBusSrv()} /> ); }; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 261b2f8e9c6..c3c3978a193 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -23,6 +23,9 @@ import { SplitOpen, DataQueryResponse, CoreApp, + DataHoverEvent, + DataHoverClearEvent, + EventBus, } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { @@ -79,6 +82,7 @@ interface Props extends Themeable2 { getFieldLinks: (field: Field, rowIndex: number) => Array>; addResultsToCache: () => void; clearCache: () => void; + eventBus: EventBus; } interface State { @@ -108,6 +112,7 @@ class UnthemedLogs extends PureComponent { flipOrderTimer?: number; cancelFlippingTimer?: number; topLogsRef = createRef(); + logsVolumeEventBus: EventBus; state: State = { showLabels: store.getBool(SETTINGS_KEYS.showLabels, false), @@ -122,6 +127,11 @@ class UnthemedLogs extends PureComponent { forceEscape: false, }; + constructor(props: Props) { + super(props); + this.logsVolumeEventBus = props.eventBus.newScopedBus('logsvolume', { onlyLocal: false }); + } + componentWillUnmount() { if (this.flipOrderTimer) { window.clearTimeout(this.flipOrderTimer); @@ -132,6 +142,20 @@ class UnthemedLogs extends PureComponent { } } + onLogRowHover = (row?: LogRowModel) => { + if (!row) { + this.props.eventBus.publish(new DataHoverClearEvent()); + } else { + this.props.eventBus.publish( + new DataHoverEvent({ + point: { + time: row.timeEpochMs, + }, + }) + ); + } + }; + onChangeLogsSortOrder = () => { this.setState({ isFlipping: true }); // we are using setTimeout here to make sure that disabled button is rendered before the rendering of reordered logs @@ -367,6 +391,7 @@ class UnthemedLogs extends PureComponent { splitOpen={splitOpen} onLoadLogsVolume={() => loadLogsVolumeData(exploreId)} onHiddenSeriesChanged={this.onToggleLogLevel} + eventBus={this.logsVolumeEventBus} /> )} @@ -480,6 +505,7 @@ class UnthemedLogs extends PureComponent { onClickHideDetectedField={this.hideDetectedField} app={CoreApp.Explore} scrollElement={scrollElement} + onLogRowHover={this.onLogRowHover} /> void; onStartScanning: () => void; onStopScanning: () => void; + eventBus: EventBus; splitOpenFn: SplitOpen; } @@ -156,6 +158,7 @@ class LogsContainer extends PureComponent { addResultsToCache={() => addResultsToCache(exploreId)} clearCache={() => clearCache(exploreId)} scrollElement={scrollElement} + eventBus={this.props.eventBus} /> diff --git a/public/app/features/explore/LogsVolumePanel.test.tsx b/public/app/features/explore/LogsVolumePanel.test.tsx index 057be0a42b4..9b2970f106b 100644 --- a/public/app/features/explore/LogsVolumePanel.test.tsx +++ b/public/app/features/explore/LogsVolumePanel.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { DataQueryResponse, LoadingState } from '@grafana/data'; +import { DataQueryResponse, LoadingState, EventBusSrv } from '@grafana/data'; import { LogsVolumePanel } from './LogsVolumePanel'; @@ -25,6 +25,7 @@ function renderPanel(logsVolumeData?: DataQueryResponse) { logLinesBasedDataVisibleRange={undefined} onLoadLogsVolume={() => {}} onHiddenSeriesChanged={() => null} + eventBus={new EventBusSrv()} /> ); } diff --git a/public/app/features/explore/LogsVolumePanel.tsx b/public/app/features/explore/LogsVolumePanel.tsx index afcf626ae0a..be2a453de84 100644 --- a/public/app/features/explore/LogsVolumePanel.tsx +++ b/public/app/features/explore/LogsVolumePanel.tsx @@ -9,6 +9,7 @@ import { LoadingState, SplitOpen, TimeZone, + EventBus, } from '@grafana/data'; import { Alert, Button, Collapse, InlineField, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui'; @@ -25,6 +26,7 @@ type Props = { onUpdateTimeRange: (timeRange: AbsoluteTimeRange) => void; onLoadLogsVolume: () => void; onHiddenSeriesChanged: (hiddenSeries: string[]) => void; + eventBus: EventBus; }; const SHORT_ERROR_MESSAGE_LIMIT = 100; @@ -131,6 +133,7 @@ export function LogsVolumePanel(props: Props) { tooltipDisplayMode={TooltipDisplayMode.Multi} onHiddenSeriesChanged={onHiddenSeriesChanged} anchorToZero + eventBus={props.eventBus} /> ); } else { diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index e15639c6192..ccaf3907a03 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -1,8 +1,8 @@ import { css } from '@emotion/css'; -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { locationService } from '@grafana/runtime'; -import { ErrorBoundaryAlert } from '@grafana/ui'; +import { ErrorBoundaryAlert, usePanelContext } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useNavModel } from 'app/core/hooks/useNavModel'; @@ -38,6 +38,8 @@ function Wrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) { const navModel = useNavModel('explore'); const { get } = useCorrelations(); const { warning } = useAppNotification(); + const panelCtx = usePanelContext(); + const eventBus = useRef(panelCtx.eventBus.newScopedBus('explore', { onlyLocal: false })); useEffect(() => { //This is needed for breadcrumbs and topnav. @@ -102,11 +104,21 @@ function Wrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
- + {hasSplit && ( - + )}