Explore: Refactor graph component to use PanelRenderer (#38914)

* Move SplitOpenFn type to grafana-data

* Use panel renderer instead of Timeseries panel for graph in Explore

* rename splitopen props on panel context
This commit is contained in:
Zoltán Bedi
2021-09-10 18:18:22 +02:00
committed by GitHub
parent ddd110d0b2
commit 94f1173824
12 changed files with 67 additions and 75 deletions

View File

@@ -1,4 +1,5 @@
import { RawTimeRange } from './time'; import { DataQuery } from './query';
import { RawTimeRange, TimeRange } from './time';
/** @internal */ /** @internal */
export interface ExploreUrlState { export interface ExploreUrlState {
@@ -8,3 +9,10 @@ export interface ExploreUrlState {
originPanelId?: number; originPanelId?: number;
context?: string; context?: string;
} }
/**
* SplitOpen type is used in Explore and related components.
*/
export type SplitOpen = <T extends DataQuery = any>(
options?: { datasourceUid: string; query: T; range?: TimeRange } | undefined
) => void;

View File

@@ -1,4 +1,4 @@
import { EventBusSrv, EventBus, DashboardCursorSync, AnnotationEventUIModel } from '@grafana/data'; import { AnnotationEventUIModel, DashboardCursorSync, EventBus, EventBusSrv, SplitOpen } from '@grafana/data';
import React from 'react'; import React from 'react';
import { SeriesVisibilityChangeMode } from '.'; import { SeriesVisibilityChangeMode } from '.';
@@ -22,6 +22,11 @@ export interface PanelContext {
onAnnotationCreate?: (annotation: AnnotationEventUIModel) => void; onAnnotationCreate?: (annotation: AnnotationEventUIModel) => void;
onAnnotationUpdate?: (annotation: AnnotationEventUIModel) => void; onAnnotationUpdate?: (annotation: AnnotationEventUIModel) => void;
onAnnotationDelete?: (id: string) => void; onAnnotationDelete?: (id: string) => void;
/**
* onSplitOpen is used in Explore to open the split view. It can be used in panels which has intercations and used in Explore as well.
* For example TimeSeries panel.
*/
onSplitOpen?: SplitOpen;
} }
export const PanelContextRoot = React.createContext<PanelContext>({ export const PanelContextRoot = React.createContext<PanelContext>({

View File

@@ -5,7 +5,6 @@ import { connect, ConnectedProps } from 'react-redux';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { TooltipDisplayMode } from '@grafana/schema';
import { ErrorBoundaryAlert, CustomScrollbar, Collapse, withTheme2, Themeable2 } from '@grafana/ui'; import { ErrorBoundaryAlert, CustomScrollbar, Collapse, withTheme2, Themeable2 } from '@grafana/ui';
import { AbsoluteTimeRange, DataQuery, LoadingState, RawTimeRange, DataFrame, GrafanaTheme2 } from '@grafana/data'; import { AbsoluteTimeRange, DataQuery, LoadingState, RawTimeRange, DataFrame, GrafanaTheme2 } from '@grafana/data';
@@ -25,10 +24,10 @@ import { NoDataSourceCallToAction } from './NoDataSourceCallToAction';
import { getTimeZone } from '../profile/state/selectors'; import { getTimeZone } from '../profile/state/selectors';
import { SecondaryActions } from './SecondaryActions'; import { SecondaryActions } from './SecondaryActions';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types';
import { ExploreGraphNGPanel } from './ExploreGraphNGPanel';
import { NodeGraphContainer } from './NodeGraphContainer'; import { NodeGraphContainer } from './NodeGraphContainer';
import { ResponseErrorContainer } from './ResponseErrorContainer'; import { ResponseErrorContainer } from './ResponseErrorContainer';
import { TraceViewContainer } from './TraceView/TraceViewContainer'; import { TraceViewContainer } from './TraceView/TraceViewContainer';
import { ExploreGraph } from './ExploreGraph';
const getStyles = (theme: GrafanaTheme2) => { const getStyles = (theme: GrafanaTheme2) => {
return { return {
@@ -191,16 +190,15 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
const spacing = parseInt(theme.spacing(2).slice(0, -2), 10); const spacing = parseInt(theme.spacing(2).slice(0, -2), 10);
return ( return (
<Collapse label="Graph" loading={loading} isOpen> <Collapse label="Graph" loading={loading} isOpen>
<ExploreGraphNGPanel <ExploreGraph
data={graphResult!} data={graphResult!}
height={400} height={400}
width={width - spacing} width={width - spacing}
tooltipDisplayMode={TooltipDisplayMode.Single}
absoluteRange={absoluteRange} absoluteRange={absoluteRange}
timeZone={timeZone} timeZone={timeZone}
onUpdateTimeRange={this.onUpdateTimeRange}
annotations={queryResponse.annotations} annotations={queryResponse.annotations}
splitOpenFn={splitOpen} splitOpenFn={splitOpen}
loadingState={queryResponse.state}
/> />
</Collapse> </Collapse>
); );
@@ -219,11 +217,12 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
} }
renderLogsPanel(width: number) { renderLogsPanel(width: number) {
const { exploreId, syncedTimes, theme } = this.props; const { exploreId, syncedTimes, theme, queryResponse } = this.props;
const spacing = parseInt(theme.spacing(2).slice(0, -2), 10); const spacing = parseInt(theme.spacing(2).slice(0, -2), 10);
return ( return (
<LogsContainer <LogsContainer
exploreId={exploreId} exploreId={exploreId}
loadingState={queryResponse.state}
syncedTimes={syncedTimes} syncedTimes={syncedTimes}
width={width - spacing} width={width - spacing}
onClickFilterLabel={this.onClickFilterLabel} onClickFilterLabel={this.onClickFilterLabel}

View File

@@ -1,3 +1,4 @@
import { css, cx } from '@emotion/css';
import { import {
AbsoluteTimeRange, AbsoluteTimeRange,
applyFieldOverrides, applyFieldOverrides,
@@ -6,36 +7,31 @@ import {
createFieldConfigRegistry, createFieldConfigRegistry,
DataFrame, DataFrame,
dateTime, dateTime,
Field,
FieldColorModeId, FieldColorModeId,
FieldConfigSource, FieldConfigSource,
getFrameDisplayName, getFrameDisplayName,
GrafanaTheme2, GrafanaTheme2,
LoadingState,
SplitOpen,
TimeZone, TimeZone,
} from '@grafana/data'; } from '@grafana/data';
import { PanelRenderer } from '@grafana/runtime';
import { GraphDrawStyle, LegendDisplayMode, TooltipDisplayMode } from '@grafana/schema';
import { import {
Icon, Icon,
PanelContext, PanelContext,
PanelContextProvider, PanelContextProvider,
SeriesVisibilityChangeMode, SeriesVisibilityChangeMode,
TimeSeries,
TooltipPlugin,
useStyles2, useStyles2,
useTheme2, useTheme2,
ZoomPlugin,
} from '@grafana/ui'; } from '@grafana/ui';
import { LegendDisplayMode, TooltipDisplayMode, GraphDrawStyle } from '@grafana/schema';
import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config';
import { ContextMenuPlugin } from 'app/plugins/panel/timeseries/plugins/ContextMenuPlugin';
import { ExemplarsPlugin } from 'app/plugins/panel/timeseries/plugins/ExemplarsPlugin';
import { css, cx } from '@emotion/css';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { getFieldLinksForExplore } from './utils/links';
import { usePrevious } from 'react-use';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { seriesVisibilityConfigFactory } from '../dashboard/dashgrid/SeriesVisibilityConfigFactory'; import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config';
import { TimeSeriesOptions } from 'app/plugins/panel/timeseries/types';
import { identity } from 'lodash'; import { identity } from 'lodash';
import { SplitOpen } from 'app/types/explore'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { usePrevious } from 'react-use';
import { seriesVisibilityConfigFactory } from '../dashboard/dashgrid/SeriesVisibilityConfigFactory';
const MAX_NUMBER_OF_TIME_SERIES = 20; const MAX_NUMBER_OF_TIME_SERIES = 20;
@@ -43,26 +39,26 @@ interface Props {
data: DataFrame[]; data: DataFrame[];
height: number; height: number;
width: number; width: number;
annotations?: DataFrame[];
absoluteRange: AbsoluteTimeRange; absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone; timeZone: TimeZone;
onUpdateTimeRange: (absoluteRange: AbsoluteTimeRange) => void; loadingState: LoadingState;
annotations?: DataFrame[];
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void; onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
tooltipDisplayMode: TooltipDisplayMode; tooltipDisplayMode?: TooltipDisplayMode;
splitOpenFn?: SplitOpen; splitOpenFn?: SplitOpen;
} }
export function ExploreGraphNGPanel({ export function ExploreGraph({
data, data,
height, height,
width, width,
timeZone, timeZone,
absoluteRange, absoluteRange,
onUpdateTimeRange, loadingState,
annotations, annotations,
tooltipDisplayMode,
splitOpenFn,
onHiddenSeriesChanged, onHiddenSeriesChanged,
splitOpenFn,
tooltipDisplayMode = TooltipDisplayMode.Single,
}: Props) { }: Props) {
const theme = useTheme2(); const theme = useTheme2();
const [showAllTimeSeries, setShowAllTimeSeries] = useState(false); const [showAllTimeSeries, setShowAllTimeSeries] = useState(false);
@@ -128,12 +124,9 @@ export function ExploreGraphNGPanel({
const seriesToShow = showAllTimeSeries ? dataWithConfig : dataWithConfig.slice(0, MAX_NUMBER_OF_TIME_SERIES); const seriesToShow = showAllTimeSeries ? dataWithConfig : dataWithConfig.slice(0, MAX_NUMBER_OF_TIME_SERIES);
const getFieldLinks = (field: Field, rowIndex: number) => {
return getFieldLinksForExplore({ field, rowIndex, splitOpenFn, range: timeRange });
};
const panelContext: PanelContext = { const panelContext: PanelContext = {
eventBus: appEvents, eventBus: appEvents,
onSplitOpen: splitOpenFn,
onToggleSeriesVisibility(label: string, mode: SeriesVisibilityChangeMode) { onToggleSeriesVisibility(label: string, mode: SeriesVisibilityChangeMode) {
setBaseStructureRev((r) => r + 1); setBaseStructureRev((r) => r + 1);
setFieldConfig(seriesVisibilityConfigFactory(label, mode, fieldConfig, data)); setFieldConfig(seriesVisibilityConfigFactory(label, mode, fieldConfig, data));
@@ -155,33 +148,19 @@ export function ExploreGraphNGPanel({
>{`Show all ${dataWithConfig.length}`}</span> >{`Show all ${dataWithConfig.length}`}</span>
</div> </div>
)} )}
<TimeSeries <PanelRenderer
frames={seriesToShow} data={{ series: seriesToShow, timeRange, structureRev, state: loadingState, annotations }}
structureRev={structureRev} pluginId="timeseries"
title=""
width={width} width={width}
height={height} height={height}
timeRange={timeRange} options={
legend={{ displayMode: LegendDisplayMode.List, placement: 'bottom', calcs: [] }} {
timeZone={timeZone} tooltip: { mode: tooltipDisplayMode },
> legend: { displayMode: LegendDisplayMode.List, placement: 'bottom', calcs: [] },
{(config, alignedDataFrame) => { } as TimeSeriesOptions
return ( }
<> />
<ZoomPlugin config={config} onZoom={onUpdateTimeRange} />
<TooltipPlugin config={config} data={alignedDataFrame} mode={tooltipDisplayMode} timeZone={timeZone} />
<ContextMenuPlugin config={config} data={alignedDataFrame} timeZone={timeZone} />
{annotations && (
<ExemplarsPlugin
config={config}
exemplars={annotations}
timeZone={timeZone}
getFieldLinks={getFieldLinks}
/>
)}
</>
);
}}
</TimeSeries>
</PanelContextProvider> </PanelContextProvider>
); );
} }

View File

@@ -19,6 +19,7 @@ import {
DataQuery, DataQuery,
DataFrame, DataFrame,
GrafanaTheme2, GrafanaTheme2,
LoadingState,
} from '@grafana/data'; } from '@grafana/data';
import { import {
RadioButtonGroup, RadioButtonGroup,
@@ -35,7 +36,7 @@ import { dedupLogRows, filterLogLevels } from 'app/core/logs_model';
import { LogsMetaRow } from './LogsMetaRow'; import { LogsMetaRow } from './LogsMetaRow';
import LogsNavigation from './LogsNavigation'; import LogsNavigation from './LogsNavigation';
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
import { ExploreGraphNGPanel } from './ExploreGraphNGPanel'; import { ExploreGraph } from './ExploreGraph';
const SETTINGS_KEYS = { const SETTINGS_KEYS = {
showLabels: 'grafana.explore.logs.showLabels', showLabels: 'grafana.explore.logs.showLabels',
@@ -54,6 +55,7 @@ interface Props extends Themeable2 {
theme: GrafanaTheme2; theme: GrafanaTheme2;
highlighterExpressions?: string[]; highlighterExpressions?: string[];
loading: boolean; loading: boolean;
loadingState: LoadingState;
absoluteRange: AbsoluteTimeRange; absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone; timeZone: TimeZone;
scanning?: boolean; scanning?: boolean;
@@ -254,6 +256,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
visibleRange, visibleRange,
highlighterExpressions, highlighterExpressions,
loading = false, loading = false,
loadingState,
onClickFilterLabel, onClickFilterLabel,
onClickFilterOutLabel, onClickFilterOutLabel,
timeZone, timeZone,
@@ -297,14 +300,14 @@ export class UnthemedLogs extends PureComponent<Props, State> {
This datasource does not support full-range histograms. The graph is based on the logs seen in the response. This datasource does not support full-range histograms. The graph is based on the logs seen in the response.
</div> </div>
{logsSeries && logsSeries.length ? ( {logsSeries && logsSeries.length ? (
<ExploreGraphNGPanel <ExploreGraph
data={logsSeries} data={logsSeries}
height={150} height={150}
width={width} width={width}
tooltipDisplayMode={TooltipDisplayMode.Multi} tooltipDisplayMode={TooltipDisplayMode.Multi}
absoluteRange={visibleRange || absoluteRange} absoluteRange={visibleRange || absoluteRange}
timeZone={timeZone} timeZone={timeZone}
onUpdateTimeRange={onChangeTime} loadingState={loadingState}
onHiddenSeriesChanged={this.onToggleLogLevel} onHiddenSeriesChanged={this.onToggleLogLevel}
/> />
) : undefined} ) : undefined}

View File

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux'; import { connect, ConnectedProps } from 'react-redux';
import { css } from 'emotion'; import { css } from 'emotion';
import { Collapse } from '@grafana/ui'; import { Collapse } from '@grafana/ui';
import { AbsoluteTimeRange, Field, LogRowModel, RawTimeRange } from '@grafana/data'; import { AbsoluteTimeRange, Field, LoadingState, LogRowModel, RawTimeRange } from '@grafana/data';
import { ExploreId, ExploreItemState } from 'app/types/explore'; import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { splitOpen } from './state/main'; import { splitOpen } from './state/main';
@@ -20,6 +20,7 @@ interface LogsContainerProps extends PropsFromRedux {
exploreId: ExploreId; exploreId: ExploreId;
scanRange?: RawTimeRange; scanRange?: RawTimeRange;
syncedTimes: boolean; syncedTimes: boolean;
loadingState: LoadingState;
onClickFilterLabel?: (key: string, value: string) => void; onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void; onClickFilterOutLabel?: (key: string, value: string) => void;
onStartScanning: () => void; onStartScanning: () => void;
@@ -60,6 +61,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
render() { render() {
const { const {
loading, loading,
loadingState,
logsHighlighterExpressions, logsHighlighterExpressions,
logRows, logRows,
logsMeta, logsMeta,
@@ -123,6 +125,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
width={width} width={width}
highlighterExpressions={logsHighlighterExpressions} highlighterExpressions={logsHighlighterExpressions}
loading={loading} loading={loading}
loadingState={loadingState}
onChangeTime={this.onChangeTime} onChangeTime={this.onChangeTime}
onClickFilterLabel={onClickFilterLabel} onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel} onClickFilterOutLabel={onClickFilterOutLabel}

View File

@@ -1,4 +1,4 @@
import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data'; import { DataFrame, DataFrameView, SplitOpen, TraceSpanRow } from '@grafana/data';
import { colors, useTheme } from '@grafana/ui'; import { colors, useTheme } from '@grafana/ui';
import { import {
ThemeOptions, ThemeOptions,
@@ -17,7 +17,7 @@ import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getTimeZone } from 'app/features/profile/state/selectors'; import { getTimeZone } from 'app/features/profile/state/selectors';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { ExploreId, SplitOpen } from 'app/types/explore'; import { ExploreId } from 'app/types/explore';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { createSpanLinkFactory } from './createSpanLink'; import { createSpanLinkFactory } from './createSpanLink';

View File

@@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Collapse } from '@grafana/ui'; import { Collapse } from '@grafana/ui';
import { DataFrame } from '@grafana/data'; import { DataFrame, SplitOpen } from '@grafana/data';
import { TraceView } from './TraceView'; import { TraceView } from './TraceView';
import { ExploreId, SplitOpen } from '../../../types'; import { ExploreId } from 'app/types/explore';
interface Props { interface Props {
dataFrames: DataFrame[]; dataFrames: DataFrame[];

View File

@@ -1,10 +1,9 @@
import { DataLink, dateTime, Field, mapInternalLinkToExplore, rangeUtil, TimeRange } from '@grafana/data'; import { DataLink, dateTime, Field, mapInternalLinkToExplore, rangeUtil, SplitOpen, TimeRange } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { Icon } from '@grafana/ui'; import { Icon } from '@grafana/ui';
import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { SplitOpen } from 'app/types/explore';
import React from 'react'; import React from 'react';
import { LokiQuery } from '../../../plugins/datasource/loki/types'; import { LokiQuery } from '../../../plugins/datasource/loki/types';

View File

@@ -8,9 +8,9 @@ import {
ScopedVars, ScopedVars,
DataFrame, DataFrame,
getFieldDisplayValuesProxy, getFieldDisplayValuesProxy,
SplitOpen,
} from '@grafana/data'; } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { SplitOpen } from 'app/types/explore';
import { getLinkSrv } from '../../panel/panellinks/link_srv'; import { getLinkSrv } from '../../panel/panellinks/link_srv';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';

View File

@@ -23,10 +23,10 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
onChangeTimeRange, onChangeTimeRange,
replaceVariables, replaceVariables,
}) => { }) => {
const { sync, canAddAnnotations } = usePanelContext(); const { sync, canAddAnnotations, onSplitOpen } = usePanelContext();
const getFieldLinks = (field: Field, rowIndex: number) => { const getFieldLinks = (field: Field, rowIndex: number) => {
return getFieldLinksForExplore({ field, rowIndex, range: timeRange }); return getFieldLinksForExplore({ field, rowIndex, splitOpenFn: onSplitOpen, range: timeRange });
}; };
const { frames, warn } = useMemo(() => prepareGraphableFields(data?.series, config.theme2), [data]); const { frames, warn } = useMemo(() => prepareGraphableFields(data?.series, config.theme2), [data]);

View File

@@ -204,7 +204,3 @@ export interface ExplorePanelData extends PanelData {
tableResult: DataFrame | null; tableResult: DataFrame | null;
logsResult: LogsModel | null; logsResult: LogsModel | null;
} }
export type SplitOpen = <T extends DataQuery = any>(
options?: { datasourceUid: string; query: T; range?: TimeRange } | undefined
) => void;