diff --git a/public/app/features/explore/Logs.test.tsx b/public/app/features/explore/Logs.test.tsx index 5efc23bf0db..767f52cd9f3 100644 --- a/public/app/features/explore/Logs.test.tsx +++ b/public/app/features/explore/Logs.test.tsx @@ -18,6 +18,8 @@ describe('Logs', () => { undefined} + logsVolumeEnabled={true} + onSetLogsVolumeEnabled={() => null} logsVolumeData={undefined} loadLogsVolumeData={() => undefined} logRows={rows} diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 55f6c387cb5..716294a979e 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -32,6 +32,7 @@ import { InlineSwitch, withTheme2, Themeable2, + Collapse, } from '@grafana/ui'; import { dedupLogRows, filterLogLevels } from 'app/core/logsModel'; import store from 'app/core/store'; @@ -43,14 +44,7 @@ import { LogRows } from '../logs/components/LogRows'; import { LogsMetaRow } from './LogsMetaRow'; import LogsNavigation from './LogsNavigation'; import { LogsVolumePanel } from './LogsVolumePanel'; - -const SETTINGS_KEYS = { - showLabels: 'grafana.explore.logs.showLabels', - showTime: 'grafana.explore.logs.showTime', - wrapLogMessage: 'grafana.explore.logs.wrapLogMessage', - prettifyLogMessage: 'grafana.explore.logs.prettifyLogMessage', - logsSortOrder: 'grafana.explore.logs.sortOrder', -}; +import { SETTINGS_KEYS } from './utils/logs'; interface Props extends Themeable2 { width: number; @@ -69,7 +63,9 @@ interface Props extends Themeable2 { scanRange?: RawTimeRange; exploreId: ExploreId; datasourceType?: string; + logsVolumeEnabled: boolean; logsVolumeData: DataQueryResponse | undefined; + onSetLogsVolumeEnabled: (enabled: boolean) => void; loadLogsVolumeData: (exploreId: ExploreId) => void; showContextToggle?: (row?: LogRowModel) => boolean; onChangeTime: (range: AbsoluteTimeRange) => void; @@ -96,6 +92,16 @@ interface State { forceEscape: boolean; } +// We need to override css overflow of divs in Collapse element to enable sticky Logs navigation +const styleOverridesForStickyNavigation = css` + & > div { + overflow: visible; + & > div { + overflow: visible; + } + } +`; + class UnthemedLogs extends PureComponent { flipOrderTimer?: number; cancelFlippingTimer?: number; @@ -201,6 +207,14 @@ class UnthemedLogs extends PureComponent { this.setState({ hiddenLogLevels }); }; + onToggleLogsVolumeCollapse = (isOpen: boolean) => { + this.props.onSetLogsVolumeEnabled(isOpen); + reportInteraction('grafana_explore_logs_histogram_toggle_clicked', { + datasourceType: this.props.datasourceType, + type: isOpen ? 'open' : 'close', + }); + }; + onClickScan = (event: React.SyntheticEvent) => { event.preventDefault(); if (this.props.onStartScanning) { @@ -284,6 +298,7 @@ class UnthemedLogs extends PureComponent { logsMeta, logsSeries, visibleRange, + logsVolumeEnabled, logsVolumeData, loadLogsVolumeData, loading = false, @@ -329,163 +344,169 @@ class UnthemedLogs extends PureComponent { return ( <> - loadLogsVolumeData(exploreId)} - onHiddenSeriesChanged={this.onToggleLogLevel} - /> -
- - - - - - - - - - - - - - - ({ - label: capitalize(dedupType), - value: dedupType, - description: LogsDedupDescription[dedupType], - }))} - value={dedupStrategy} - onChange={this.onChangeDedup} - className={styles.radioButtons} - /> - - -
- - - -
-
- -
-
- + {logsVolumeEnabled && ( + loadLogsVolumeData(exploreId)} + onHiddenSeriesChanged={this.onToggleLogLevel} + /> + )} + + +
+ + + + + + + + + + + + + + + ({ + label: capitalize(dedupType), + value: dedupType, + description: LogsDedupDescription[dedupType], + }))} + value={dedupStrategy} + onChange={this.onChangeDedup} + className={styles.radioButtons} + /> + + +
+ + + +
+
+ +
+
+ +
+
- -
- {!loading && !hasData && !scanning && ( -
- No logs found. - -
- )} - {scanning && ( -
- {scanText} - -
- )} + {!loading && !hasData && !scanning && ( +
+ No logs found. + +
+ )} + {scanning && ( +
+ {scanText} + +
+ )} + ); } @@ -508,7 +529,7 @@ const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean) => { background-color: ${theme.colors.background.primary}; padding: ${theme.spacing(1, 2)}; border-radius: ${theme.shape.borderRadius()}; - margin: ${theme.spacing(2, 0, 1)}; + margin: ${theme.spacing(0, 0, 1)}; border: 1px solid ${theme.colors.border.medium}; `, headerButton: css` diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 7cb00b56c4d..817ff482335 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -1,4 +1,3 @@ -import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; @@ -19,7 +18,7 @@ import { getTimeZone } from '../profile/state/selectors'; import { LiveLogsWithTheme } from './LiveLogs'; import { Logs } from './Logs'; import { splitOpen } from './state/main'; -import { addResultsToCache, clearCache, loadLogsVolumeData } from './state/query'; +import { addResultsToCache, clearCache, loadLogsVolumeData, setLogsVolumeEnabled } from './state/query'; import { updateTimeRange } from './state/time'; import { LiveTailControls } from './useLiveTailControls'; import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition'; @@ -104,16 +103,6 @@ class LogsContainer extends PureComponent { return null; } - // We need to override css overflow of divs in Collapse element to enable sticky Logs navigation - const styleOverridesForStickyNavigation = css` - & > div { - overflow: visible; - & > div { - overflow: visible; - } - } - `; - return ( <> @@ -133,37 +122,37 @@ class LogsContainer extends PureComponent { - - addResultsToCache(exploreId)} - clearCache={() => clearCache(exploreId)} - /> - + this.props.setLogsVolumeEnabled(exploreId, enabled)} + logsVolumeData={logsVolumeData} + logsQueries={logsQueries} + width={width} + splitOpen={splitOpen} + loading={loading} + loadingState={loadingState} + loadLogsVolumeData={loadLogsVolumeData} + onChangeTime={this.onChangeTime} + onClickFilterLabel={onClickFilterLabel} + onClickFilterOutLabel={onClickFilterOutLabel} + onStartScanning={onStartScanning} + onStopScanning={onStopScanning} + absoluteRange={absoluteRange} + visibleRange={visibleRange} + timeZone={timeZone} + scanning={scanning} + scanRange={range.raw} + showContextToggle={this.showContextToggle} + getRowContext={this.getLogRowContext} + getFieldLinks={this.getFieldLinks} + addResultsToCache={() => addResultsToCache(exploreId)} + clearCache={() => clearCache(exploreId)} + /> ); @@ -183,6 +172,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } isPaused, range, absoluteRange, + logsVolumeEnabled, logsVolumeDataProvider, logsVolumeData, } = item; @@ -202,6 +192,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } isPaused, range, absoluteRange, + logsVolumeEnabled, logsVolumeDataProvider, logsVolumeData, }; @@ -213,6 +204,7 @@ const mapDispatchToProps = { addResultsToCache, clearCache, loadLogsVolumeData, + setLogsVolumeEnabled, }; const connector = connect(mapStateToProps, mapDispatchToProps); diff --git a/public/app/features/explore/state/helpers.ts b/public/app/features/explore/state/helpers.ts index 2a9e39f46a6..b6fb357bc61 100644 --- a/public/app/features/explore/state/helpers.ts +++ b/public/app/features/explore/state/helpers.ts @@ -40,6 +40,7 @@ export const createDefaultInitialState = () => { }, cache: [], richHistory: [], + logsVolumeEnabled: true, }, }, }; diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index f2fda93ffae..1b90e58a7d4 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -35,6 +35,7 @@ import { scanStartAction, scanStopAction, storeLogsVolumeDataProviderAction, + setLogsVolumeEnabled, } from './query'; import { makeExplorePaneState } from './utils'; @@ -465,5 +466,34 @@ describe('reducer', () => { expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Done); expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined(); }); + + it('do not load logsVolume data when disabled', async () => { + // turn logsvolume off + dispatch(setLogsVolumeEnabled(ExploreId.left, false)); + expect(getState().explore[ExploreId.left].logsVolumeEnabled).toBe(false); + + // verify that if we run a query, it will not do logsvolume, but the Provider will still be set + await dispatch(runQueries(ExploreId.left)); + expect(getState().explore[ExploreId.left].logsVolumeData).toBeUndefined(); + expect(getState().explore[ExploreId.left].logsVolumeDataSubscription).toBeUndefined(); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined(); + }); + + it('load logsVolume data when it gets enabled', async () => { + // first it is disabled + dispatch(setLogsVolumeEnabled(ExploreId.left, false)); + + // runQueries sets up the logsVolume query, but does not run it + await dispatch(runQueries(ExploreId.left)); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined(); + + // we turn logsvolume on + await dispatch(setLogsVolumeEnabled(ExploreId.left, true)); + + // verify it was turned on + expect(getState().explore[ExploreId.left].logsVolumeEnabled).toBe(true); + + expect(getState().explore[ExploreId.left].logsVolumeDataSubscription).toBeDefined(); + }); }); }); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index c10b2a1318f..a12bffb5150 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -45,7 +45,7 @@ import { decorateData } from '../utils/decorators'; import { addHistoryItem, historyUpdatedAction, loadRichHistory } from './history'; import { stateSave } from './main'; import { updateTime } from './time'; -import { createCacheKey, getResultsFromCache } from './utils'; +import { createCacheKey, getResultsFromCache, storeLogsVolumeEnabled } from './utils'; // // Actions and Payloads @@ -109,6 +109,10 @@ export const queryStoreSubscriptionAction = createAction( + 'explore/setLogsVolumeEnabledAction' +); + export interface StoreLogsVolumeDataProvider { exploreId: ExploreId; logsVolumeDataProvider?: Observable; @@ -415,6 +419,7 @@ export const runQueries = ( refreshInterval, absoluteRange, cache, + logsVolumeEnabled, } = exploreItemState; let newQuerySub; @@ -550,6 +555,12 @@ export const runQueries = ( ); dispatch(cleanLogsVolumeAction({ exploreId })); } else if (hasLogsVolumeSupport(datasourceInstance)) { + // we always prepare the logsVolumeProvider, + // but we only load it, if the logs-volume-histogram is enabled. + // (we need to have the logsVolumeProvider always actual, + // even when the visuals are disabled, because when the user + // enables the visuals again, we need to load the histogram, + // so we need the provider) const sourceRequest = { ...transaction.request, requestId: transaction.request.requestId + '_log_volume', @@ -564,7 +575,9 @@ export const runQueries = ( const { logsVolumeData, absoluteRange } = getState().explore[exploreId]!; if (!canReuseLogsVolumeData(logsVolumeData, queries, absoluteRange)) { dispatch(cleanLogsVolumeAction({ exploreId })); - dispatch(loadLogsVolumeData(exploreId)); + if (logsVolumeEnabled) { + dispatch(loadLogsVolumeData(exploreId)); + } } } else { dispatch( @@ -670,6 +683,16 @@ export function loadLogsVolumeData(exploreId: ExploreId): ThunkResult { }; } +export function setLogsVolumeEnabled(exploreId: ExploreId, enabled: boolean): ThunkResult { + return (dispatch, getState) => { + dispatch(setLogsVolumeEnabledAction({ exploreId, enabled })); + storeLogsVolumeEnabled(enabled); + if (enabled) { + dispatch(loadLogsVolumeData(exploreId)); + } + }; +} + // // Reducer // @@ -757,6 +780,20 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor }; } + if (setLogsVolumeEnabledAction.match(action)) { + const { enabled } = action.payload; + if (!enabled && state.logsVolumeDataSubscription) { + state.logsVolumeDataSubscription.unsubscribe(); + } + return { + ...state, + logsVolumeEnabled: enabled, + // NOTE: the dataProvider is not cleared, we may need it later, + // if the user re-enables the histogram-visualization + logsVolumeData: undefined, + }; + } + if (storeLogsVolumeDataProviderAction.match(action)) { let { logsVolumeDataProvider } = action.payload; if (state.logsVolumeDataSubscription) { diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index 5a28d9ed7ae..73058097d2e 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -17,6 +17,7 @@ import { ExploreGraphStyle, ExploreItemState } from 'app/types/explore'; import store from '../../../core/store'; import { clearQueryKeys, lastUsedDatasourceKeyForOrgId, toGraphStyle } from '../../../core/utils/explore'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; +import { SETTINGS_KEYS } from '../utils/logs'; import { toRawTimeRange } from '../utils/time'; export const DEFAULT_RANGE = { @@ -34,6 +35,21 @@ const loadGraphStyle = (): ExploreGraphStyle => { 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'); +}; + +const loadLogsVolumeEnabled = (): boolean => { + const data = store.get(LOGS_VOLUME_ENABLED_KEY); + // we default to `enabled=true` + if (data === 'false') { + return false; + } + + return true; +}; + /** * Returns a fresh Explore area state */ @@ -65,6 +81,7 @@ export const makeExplorePaneState = (): ExploreItemState => ({ eventBridge: null as unknown as EventBusExtended, cache: [], richHistory: [], + logsVolumeEnabled: loadLogsVolumeEnabled(), logsVolumeDataProvider: undefined, logsVolumeData: undefined, graphStyle: loadGraphStyle(), diff --git a/public/app/features/explore/utils/logs.ts b/public/app/features/explore/utils/logs.ts new file mode 100644 index 00000000000..0ead558c14a --- /dev/null +++ b/public/app/features/explore/utils/logs.ts @@ -0,0 +1,8 @@ +export const SETTINGS_KEYS = { + showLabels: 'grafana.explore.logs.showLabels', + showTime: 'grafana.explore.logs.showTime', + wrapLogMessage: 'grafana.explore.logs.wrapLogMessage', + prettifyLogMessage: 'grafana.explore.logs.prettifyLogMessage', + logsSortOrder: 'grafana.explore.logs.sortOrder', + enableVolumeHistogram: 'grafana.explore.logs.enableVolumeHistogram', +}; diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 66c59b618c5..061037b59d2 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -192,6 +192,7 @@ export interface ExploreItemState { // properties below should be more generic if we add more providers // see also: DataSourceWithLogsVolumeSupport + logsVolumeEnabled: boolean; logsVolumeDataProvider?: Observable; logsVolumeDataSubscription?: SubscriptionLike; logsVolumeData?: DataQueryResponse;