diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 5607898732f..dce6c2e66ad 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -30,7 +30,7 @@ import { RefreshPicker } from '@grafana/ui'; import store from 'app/core/store'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { PanelModel } from 'app/features/dashboard/state'; -import { EXPLORE_GRAPH_STYLES, ExploreGraphStyle, ExploreId, QueryOptions, QueryTransaction } from 'app/types/explore'; +import { ExploreId, QueryOptions, QueryTransaction } from 'app/types/explore'; import { config } from '../config'; @@ -205,21 +205,6 @@ export const safeStringifyValue = (value: any, space?: number) => { return ''; }; -const DEFAULT_GRAPH_STYLE: ExploreGraphStyle = 'lines'; -// we use this function to take any kind of data we loaded -// from an external source (URL, localStorage, whatever), -// and extract the graph-style from it, or return the default -// graph-style if we are not able to do that. -// it is important that this function is able to take any form of data, -// (be it objects, or arrays, or booleans or whatever), -// and produce a best-effort graphStyle. -// note that typescript makes sure we make no mistake in this function. -// we do not rely on ` as ` or ` any `. -export const toGraphStyle = (data: unknown): ExploreGraphStyle => { - const found = EXPLORE_GRAPH_STYLES.find((v) => v === data); - return found ?? DEFAULT_GRAPH_STYLE; -}; - export function parseUrlState(initial: string | undefined): ExploreUrlState { const parsed = safeParseJson(initial); const errorResult: any = { diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index 151b6930c7a..619ef022261 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -84,8 +84,6 @@ const dummyProps: Props = { showFlameGraph: true, splitOpen: (() => {}) as any, splitted: false, - changeGraphStyle: () => {}, - graphStyle: 'lines', eventBus: new EventBusSrv(), }; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index a5f7b1ce657..e4b3f488fac 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -18,7 +18,7 @@ import { } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; -import { Collapse, CustomScrollbar, ErrorBoundaryAlert, Themeable2, withTheme2, PanelContainer } from '@grafana/ui'; +import { CustomScrollbar, ErrorBoundaryAlert, Themeable2, withTheme2, PanelContainer } from '@grafana/ui'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types'; import appEvents from 'app/core/app_events'; import { supportedFeatures } from 'app/core/history/richHistoryStorageProvider'; @@ -26,15 +26,14 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou import { getNodeGraphDataFrames } from 'app/plugins/panel/nodeGraph/utils'; import { StoreState } from 'app/types'; import { AbsoluteTimeEvent } from 'app/types/events'; -import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; import { getTimeZone } from '../profile/state/selectors'; -import { ExploreGraph } from './ExploreGraph'; -import { ExploreGraphLabel } from './ExploreGraphLabel'; import ExploreQueryInspector from './ExploreQueryInspector'; import { ExploreToolbar } from './ExploreToolbar'; import { FlameGraphExploreContainer } from './FlameGraphExploreContainer'; +import { GraphContainer } from './Graph/GraphContainer'; import LogsContainer from './LogsContainer'; import { NoData } from './NoData'; import { NoDataSourceCallToAction } from './NoDataSourceCallToAction'; @@ -45,7 +44,7 @@ import RichHistoryContainer from './RichHistory/RichHistoryContainer'; import { SecondaryActions } from './SecondaryActions'; import TableContainer from './TableContainer'; import { TraceViewContainer } from './TraceView/TraceViewContainer'; -import { changeSize, changeGraphStyle } from './state/explorePane'; +import { changeSize } from './state/explorePane'; import { splitOpen } from './state/main'; import { addQueryRow, modifyQueries, scanStart, scanStopAction, setQueries } from './state/query'; import { isSplit } from './state/selectors'; @@ -222,11 +221,6 @@ export class Explore extends React.PureComponent { updateTimeRange({ exploreId, absoluteRange }); }; - onChangeGraphStyle = (graphStyle: ExploreGraphStyle) => { - const { exploreId, changeGraphStyle } = this.props; - changeGraphStyle(exploreId, graphStyle); - }; - toggleShowRichHistory = () => { this.setState((state) => { return { @@ -277,28 +271,22 @@ export class Explore extends React.PureComponent { } renderGraphPanel(width: number) { - const { graphResult, absoluteRange, timeZone, queryResponse, loading, theme, graphStyle, showFlameGraph } = - this.props; - const spacing = parseInt(theme.spacing(2).slice(0, -2), 10); - const label = ; + const { graphResult, absoluteRange, timeZone, queryResponse, loading, showFlameGraph } = this.props; return ( - - - + ); } @@ -515,7 +503,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { showNodeGraph, showFlameGraph, loading, - graphStyle, } = item; return { @@ -538,13 +525,11 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { showFlameGraph, splitted: isSplit(state), loading, - graphStyle, }; } const mapDispatchToProps = { changeSize, - changeGraphStyle, modifyQueries, scanStart, scanStopAction, diff --git a/public/app/features/explore/ExploreGraph.tsx b/public/app/features/explore/Graph/ExploreGraph.tsx similarity index 87% rename from public/app/features/explore/ExploreGraph.tsx rename to public/app/features/explore/Graph/ExploreGraph.tsx index fc3613c59e8..f8a0e02f0d4 100644 --- a/public/app/features/explore/ExploreGraph.tsx +++ b/public/app/features/explore/Graph/ExploreGraph.tsx @@ -31,9 +31,9 @@ import { } from '@grafana/ui'; import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config'; import { TimeSeriesOptions } from 'app/plugins/panel/timeseries/types'; +import { ExploreGraphStyle } from 'app/types'; -import { ExploreGraphStyle } from '../../types'; -import { seriesVisibilityConfigFactory } from '../dashboard/dashgrid/SeriesVisibilityConfigFactory'; +import { seriesVisibilityConfigFactory } from '../../dashboard/dashgrid/SeriesVisibilityConfigFactory'; import { applyGraphStyle } from './exploreGraphStyleUtils'; @@ -52,7 +52,7 @@ interface Props { splitOpenFn: SplitOpen; onChangeTime: (timeRange: AbsoluteTimeRange) => void; graphStyle: ExploreGraphStyle; - anchorToZero: boolean; + anchorToZero?: boolean; eventBus: EventBus; } @@ -69,13 +69,14 @@ export function ExploreGraph({ splitOpenFn, graphStyle, tooltipDisplayMode = TooltipDisplayMode.Single, - anchorToZero, + anchorToZero = false, eventBus, }: Props) { const theme = useTheme2(); const style = useStyles2(getStyles); const [showAllTimeSeries, setShowAllTimeSeries] = useState(false); - const [structureRev, { inc: incrementStructureRev }] = useCounter(1); + const [structureRev, { inc }] = useCounter(0); + const fieldConfigRegistry = useMemo( () => createFieldConfigRegistry(getGraphFieldConfig(defaultGraphConfig), 'Explore'), [] @@ -118,12 +119,10 @@ export function ExploreGraph({ }); }, [fieldConfigRegistry, data, timeZone, theme, styledFieldConfig]); - // structureRev should be incremented when either the number of series or the config changes. - // like useEffect, but runs before rendering. - // TODO: while this works as it is supposed to, we are forced to do this now because of the way - // ExploreGraph is implemented. We should refactor it to a single component that handles structureRev increments - // when a user changes the viz style and not react to the value change itself. - useMemo(incrementStructureRev, [dataWithConfig.length, styledFieldConfig, incrementStructureRev]); + // We need to increment structureRev when the number of series changes. + // the function passed to useMemo runs during rendering, so when we get a different + // amount of data, structureRev is incremented before we render it + useMemo(inc, [dataWithConfig.length, styledFieldConfig, inc]); useEffect(() => { if (onHiddenSeriesChanged) { diff --git a/public/app/features/explore/ExploreGraphLabel.tsx b/public/app/features/explore/Graph/ExploreGraphLabel.tsx similarity index 71% rename from public/app/features/explore/ExploreGraphLabel.tsx rename to public/app/features/explore/Graph/ExploreGraphLabel.tsx index 6075ea08f92..576a55e10e5 100644 --- a/public/app/features/explore/ExploreGraphLabel.tsx +++ b/public/app/features/explore/Graph/ExploreGraphLabel.tsx @@ -1,10 +1,8 @@ -import { css } from '@emotion/css'; import React from 'react'; import { SelectableValue } from '@grafana/data'; -import { RadioButtonGroup } from '@grafana/ui'; - -import { EXPLORE_GRAPH_STYLES, ExploreGraphStyle } from '../../types'; +import { RadioButtonGroup, HorizontalGroup } from '@grafana/ui'; +import { EXPLORE_GRAPH_STYLES, ExploreGraphStyle } from 'app/types'; const ALL_GRAPH_STYLE_OPTIONS: Array> = EXPLORE_GRAPH_STYLES.map((style) => ({ value: style, @@ -12,11 +10,6 @@ const ALL_GRAPH_STYLE_OPTIONS: Array> = EXPLO label: style[0].toUpperCase() + style.slice(1).replace(/_/, ' '), })); -const spacing = css({ - display: 'flex', - justifyContent: 'space-between', -}); - type Props = { graphStyle: ExploreGraphStyle; onChangeGraphStyle: (style: ExploreGraphStyle) => void; @@ -25,9 +18,9 @@ type Props = { export function ExploreGraphLabel(props: Props) { const { graphStyle, onChangeGraphStyle } = props; return ( -
+ Graph -
+ ); } diff --git a/public/app/features/explore/Graph/GraphContainer.tsx b/public/app/features/explore/Graph/GraphContainer.tsx new file mode 100644 index 00000000000..cbbbbc8d166 --- /dev/null +++ b/public/app/features/explore/Graph/GraphContainer.tsx @@ -0,0 +1,70 @@ +import React, { useCallback, useState } from 'react'; + +import { DataFrame, EventBus, AbsoluteTimeRange, TimeZone, SplitOpen, LoadingState } from '@grafana/data'; +import { Collapse, useTheme2 } from '@grafana/ui'; +import { ExploreGraphStyle } from 'app/types'; + +import { storeGraphStyle } from '../state/utils'; + +import { ExploreGraph } from './ExploreGraph'; +import { ExploreGraphLabel } from './ExploreGraphLabel'; +import { loadGraphStyle } from './utils'; + +interface Props { + loading: boolean; + data: DataFrame[]; + annotations?: DataFrame[]; + eventBus: EventBus; + height: number; + width: number; + absoluteRange: AbsoluteTimeRange; + timeZone: TimeZone; + onChangeTime: (absoluteRange: AbsoluteTimeRange) => void; + splitOpenFn: SplitOpen; + loadingState: LoadingState; +} + +export const GraphContainer = ({ + loading, + data, + eventBus, + height, + width, + absoluteRange, + timeZone, + annotations, + onChangeTime, + splitOpenFn, + loadingState, +}: Props) => { + const [graphStyle, setGraphStyle] = useState(loadGraphStyle); + const theme = useTheme2(); + const spacing = parseInt(theme.spacing(2).slice(0, -2), 10); + + const onGraphStyleChange = useCallback((graphStyle: ExploreGraphStyle) => { + storeGraphStyle(graphStyle); + setGraphStyle(graphStyle); + }, []); + + return ( + } + loading={loading} + isOpen + > + + + ); +}; diff --git a/public/app/features/explore/exploreGraphStyleUtils.ts b/public/app/features/explore/Graph/exploreGraphStyleUtils.ts similarity index 97% rename from public/app/features/explore/exploreGraphStyleUtils.ts rename to public/app/features/explore/Graph/exploreGraphStyleUtils.ts index 6da223c91b9..f48c746254e 100644 --- a/public/app/features/explore/exploreGraphStyleUtils.ts +++ b/public/app/features/explore/Graph/exploreGraphStyleUtils.ts @@ -2,8 +2,7 @@ import produce from 'immer'; import { FieldConfigSource } from '@grafana/data'; import { GraphDrawStyle, GraphFieldConfig, StackingMode } from '@grafana/schema'; - -import { ExploreGraphStyle } from '../../types'; +import { ExploreGraphStyle } from 'app/types'; export type FieldConfig = FieldConfigSource; diff --git a/public/app/features/explore/Graph/utils.ts b/public/app/features/explore/Graph/utils.ts new file mode 100644 index 00000000000..3b42e342c1f --- /dev/null +++ b/public/app/features/explore/Graph/utils.ts @@ -0,0 +1,26 @@ +import store from 'app/core/store'; +import { ExploreGraphStyle, EXPLORE_GRAPH_STYLES } from 'app/types'; + +const GRAPH_STYLE_KEY = 'grafana.explore.style.graph'; +export const storeGraphStyle = (graphStyle: string): void => { + store.set(GRAPH_STYLE_KEY, graphStyle); +}; + +export const loadGraphStyle = (): ExploreGraphStyle => { + return toGraphStyle(store.get(GRAPH_STYLE_KEY)); +}; + +const DEFAULT_GRAPH_STYLE: ExploreGraphStyle = 'lines'; +// we use this function to take any kind of data we loaded +// from an external source (URL, localStorage, whatever), +// and extract the graph-style from it, or return the default +// graph-style if we are not able to do that. +// it is important that this function is able to take any form of data, +// (be it objects, or arrays, or booleans or whatever), +// and produce a best-effort graphStyle. +// note that typescript makes sure we make no mistake in this function. +// we do not rely on ` as ` or ` any `. +export const toGraphStyle = (data: unknown): ExploreGraphStyle => { + const found = EXPLORE_GRAPH_STYLES.find((v) => v === data); + return found ?? DEFAULT_GRAPH_STYLE; +}; diff --git a/public/app/features/explore/LogsVolumePanel.test.tsx b/public/app/features/explore/LogsVolumePanel.test.tsx index 9b2970f106b..3dbb25728c4 100644 --- a/public/app/features/explore/LogsVolumePanel.test.tsx +++ b/public/app/features/explore/LogsVolumePanel.test.tsx @@ -5,7 +5,7 @@ import { DataQueryResponse, LoadingState, EventBusSrv } from '@grafana/data'; import { LogsVolumePanel } from './LogsVolumePanel'; -jest.mock('./ExploreGraph', () => { +jest.mock('./Graph/ExploreGraph', () => { const ExploreGraph = () => ExploreGraph; return { ExploreGraph, diff --git a/public/app/features/explore/LogsVolumePanel.tsx b/public/app/features/explore/LogsVolumePanel.tsx index be2a453de84..eecda2d8807 100644 --- a/public/app/features/explore/LogsVolumePanel.tsx +++ b/public/app/features/explore/LogsVolumePanel.tsx @@ -13,7 +13,7 @@ import { } from '@grafana/data'; import { Alert, Button, Collapse, InlineField, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui'; -import { ExploreGraph } from './ExploreGraph'; +import { ExploreGraph } from './Graph/ExploreGraph'; type Props = { logsVolumeData: DataQueryResponse | undefined; @@ -125,7 +125,7 @@ export function LogsVolumePanel(props: Props) { loadingState={LoadingState.Done} data={logsVolumeData.data} height={height} - width={width - spacing} + width={width - spacing * 2} absoluteRange={range} onChangeTime={onUpdateTimeRange} timeZone={timeZone} diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index 58dc80a408e..ecbfa669b7f 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -24,7 +24,7 @@ import { } from 'app/core/utils/explore'; import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; import { ThunkResult } from 'app/types'; -import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; import { datasourceReducer } from './datasource'; import { historyReducer } from './history'; @@ -36,7 +36,6 @@ import { loadAndInitDatasource, createEmptyQueryResponse, getUrlStateFromPaneState, - storeGraphStyle, } from './utils'; // Types @@ -118,20 +117,6 @@ export function changeSize( return changeSizeAction({ exploreId, height, width }); } -interface ChangeGraphStylePayload { - exploreId: ExploreId; - graphStyle: ExploreGraphStyle; -} - -const changeGraphStyleAction = createAction('explore/changeGraphStyle'); - -export function changeGraphStyle(exploreId: ExploreId, graphStyle: ExploreGraphStyle): ThunkResult { - return async (dispatch, getState) => { - storeGraphStyle(graphStyle); - dispatch(changeGraphStyleAction({ exploreId, graphStyle })); - }; -} - /** * Initialize Explore state with state from the URL and the React component. * Call this only on components for with the Explore state has not been initialized. @@ -281,11 +266,6 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac return { ...state, containerWidth }; } - if (changeGraphStyleAction.match(action)) { - const { graphStyle } = action.payload; - return { ...state, graphStyle }; - } - if (changePanelsStateAction.match(action)) { const { panelsState } = action.payload; return { ...state, panelsState }; diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index d4a4977253b..f980cadd52e 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -12,10 +12,10 @@ import { PanelData, } from '@grafana/data'; import { ExplorePanelData } from 'app/types'; -import { ExploreGraphStyle, ExploreItemState } from 'app/types/explore'; +import { ExploreItemState } from 'app/types/explore'; import store from '../../../core/store'; -import { clearQueryKeys, lastUsedDatasourceKeyForOrgId, toGraphStyle } from '../../../core/utils/explore'; +import { clearQueryKeys, lastUsedDatasourceKeyForOrgId } from '../../../core/utils/explore'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { SETTINGS_KEYS } from '../utils/logs'; import { toRawTimeRange } from '../utils/time'; @@ -30,11 +30,6 @@ export const storeGraphStyle = (graphStyle: string): void => { store.set(GRAPH_STYLE_KEY, graphStyle); }; -const loadGraphStyle = (): ExploreGraphStyle => { - const data = store.get(GRAPH_STYLE_KEY); - return toGraphStyle(data); -}; - const LOGS_VOLUME_ENABLED_KEY = SETTINGS_KEYS.enableVolumeHistogram; export const storeLogsVolumeEnabled = (enabled: boolean): void => { store.set(LOGS_VOLUME_ENABLED_KEY, enabled ? 'true' : 'false'); @@ -84,7 +79,6 @@ export const makeExplorePaneState = (): ExploreItemState => ({ logsVolumeEnabled: loadLogsVolumeEnabled(), logsVolumeDataProvider: undefined, logsVolumeData: undefined, - graphStyle: loadGraphStyle(), panelsState: {}, }); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index f0117e42106..439b39dac2c 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -187,8 +187,6 @@ export interface ExploreItemState { logsVolumeDataSubscription?: SubscriptionLike; logsVolumeData?: DataQueryResponse; - /* explore graph style */ - graphStyle: ExploreGraphStyle; panelsState: ExplorePanelsState; }