Logs volume: Refactor (#60998)

* Rename log volume in redux to supp query to make it more generic

* Change enabled query to queries

* Small spell fix StoreSuppQueryDataProviderAction -> storeSuppQueryDataProviderAction

* WIP

* Improve

* WIP

* Rename

* Move to 1

* Small updates

* Use enum

* Unify naming

* Use SUPP_QUERY_TYPES instead of Object.keys()

* Move SuppQueryType to types/explore

* Rename suppQuery to supportingQuery

* Rename SUPP_QUERY_TYPES to SUPPORTING_QUERY_TYPES

* Rename supporting to supplementary 🙈

* Remove suppQueryData && suppQueryData.data[0] check as it is redundant

* Update public/app/features/explore/state/query.ts

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Update public/app/features/explore/state/query.ts

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Fix naming of SupplementaryQuery interface

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
Ivana Huckova 2023-01-09 18:33:48 +01:00 committed by GitHub
parent 26a3c864f3
commit ddc8beda07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 302 additions and 159 deletions

View File

@ -71,7 +71,7 @@ interface Props extends Themeable2 {
logsVolumeData: DataQueryResponse | undefined;
scrollElement?: HTMLDivElement;
onSetLogsVolumeEnabled: (enabled: boolean) => void;
loadLogsVolumeData: (exploreId: ExploreId) => void;
loadLogsVolumeData: () => void;
showContextToggle?: (row?: LogRowModel) => boolean;
onChangeTime: (range: AbsoluteTimeRange) => void;
onClickFilterLabel?: (key: string, value: string) => void;
@ -389,7 +389,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
onUpdateTimeRange={onChangeTime}
timeZone={timeZone}
splitOpen={splitOpen}
onLoadLogsVolume={() => loadLogsVolumeData(exploreId)}
onLoadLogsVolume={loadLogsVolumeData}
onHiddenSeriesChanged={this.onToggleLogLevel}
eventBus={this.logsVolumeEventBus}
/>

View File

@ -14,13 +14,13 @@ import {
} from '@grafana/data';
import { Collapse } from '@grafana/ui';
import { StoreState } from 'app/types';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { ExploreId, ExploreItemState, SupplementaryQueryType } from 'app/types/explore';
import { getTimeZone } from '../profile/state/selectors';
import { LiveLogsWithTheme } from './LiveLogs';
import { Logs } from './Logs';
import { addResultsToCache, clearCache, loadLogsVolumeData, setLogsVolumeEnabled } from './state/query';
import { addResultsToCache, clearCache, loadSupplementaryQueryData, setSupplementaryQueryEnabled } from './state/query';
import { updateTimeRange } from './state/time';
import { LiveTailControls } from './useLiveTailControls';
import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition';
@ -85,8 +85,8 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
logsMeta,
logsSeries,
logsQueries,
logsVolumeData,
loadLogsVolumeData,
loadSupplementaryQueryData,
setSupplementaryQueryEnabled,
onClickFilterLabel,
onClickFilterOutLabel,
onStartScanning,
@ -103,6 +103,7 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
addResultsToCache,
clearCache,
scrollElement,
logsVolume,
} = this.props;
if (!logRows) {
@ -134,15 +135,17 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
logRows={logRows}
logsMeta={logsMeta}
logsSeries={logsSeries}
logsVolumeEnabled={this.props.logsVolumeEnabled}
onSetLogsVolumeEnabled={(enabled) => this.props.setLogsVolumeEnabled(exploreId, enabled)}
logsVolumeData={logsVolumeData}
logsVolumeEnabled={logsVolume.enabled}
onSetLogsVolumeEnabled={(enabled) =>
setSupplementaryQueryEnabled(exploreId, enabled, SupplementaryQueryType.LogsVolume)
}
logsVolumeData={logsVolume.data}
logsQueries={logsQueries}
width={width}
splitOpen={splitOpenFn}
loading={loading}
loadingState={loadingState}
loadLogsVolumeData={loadLogsVolumeData}
loadLogsVolumeData={() => loadSupplementaryQueryData(exploreId, SupplementaryQueryType.LogsVolume)}
onChangeTime={this.onChangeTime}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
@ -180,11 +183,10 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
isPaused,
range,
absoluteRange,
logsVolumeEnabled,
logsVolumeDataProvider,
logsVolumeData,
supplementaryQueries,
} = item;
const timeZone = getTimeZone(state.user);
const logsVolume = supplementaryQueries[SupplementaryQueryType.LogsVolume];
return {
loading,
@ -200,9 +202,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
isPaused,
range,
absoluteRange,
logsVolumeEnabled,
logsVolumeDataProvider,
logsVolumeData,
logsVolume,
};
}
@ -210,8 +210,8 @@ const mapDispatchToProps = {
updateTimeRange,
addResultsToCache,
clearCache,
loadLogsVolumeData,
setLogsVolumeEnabled,
loadSupplementaryQueryData,
setSupplementaryQueryEnabled,
};
const connector = connect(mapStateToProps, mapDispatchToProps);

View File

@ -10,7 +10,7 @@ import { ExploreId } from 'app/types/explore';
import { importQueries, runQueries } from './query';
import { changeRefreshInterval } from './time';
import { createEmptyQueryResponse, loadAndInitDatasource } from './utils';
import { createEmptyQueryResponse, loadAndInitDatasource, loadSupplementaryQueries } from './utils';
//
// Actions and Payloads
@ -99,8 +99,7 @@ export const datasourceReducer = (state: ExploreItemState, action: AnyAction): E
graphResult: null,
tableResult: null,
logsResult: null,
logsVolumeDataProvider: undefined,
logsVolumeData: undefined,
supplementaryQueries: loadSupplementaryQueries(),
queryResponse: createEmptyQueryResponse(),
loading: false,
queryKeys: [],

View File

@ -1,6 +1,6 @@
import { DefaultTimeZone, toUtc } from '@grafana/data';
import { ExploreId } from '../../../types';
import { ExploreId, SupplementaryQueryType } from '../../../types';
export const createDefaultInitialState = () => {
const t = toUtc();
@ -40,7 +40,11 @@ export const createDefaultInitialState = () => {
},
cache: [],
richHistory: [],
logsVolumeEnabled: true,
supplementaryQueries: {
[SupplementaryQueryType.LogsVolume]: {
enabled: true,
},
},
},
},
};

View File

@ -13,7 +13,7 @@ import {
MutableDataFrame,
RawTimeRange,
} from '@grafana/data';
import { ExploreId, ExploreItemState, StoreState, ThunkDispatch } from 'app/types';
import { ExploreId, ExploreItemState, StoreState, SupplementaryQueryType, ThunkDispatch } from 'app/types';
import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { configureStore } from '../../../store/configureStore';
@ -26,15 +26,15 @@ import {
addResultsToCache,
cancelQueries,
cancelQueriesAction,
cleanLogsVolumeAction,
cleanSupplementaryQueryAction,
clearCache,
importQueries,
queryReducer,
runQueries,
scanStartAction,
scanStopAction,
storeLogsVolumeDataProviderAction,
setLogsVolumeEnabled,
storeSupplementaryQueryDataProviderAction,
setSupplementaryQueryEnabled,
} from './query';
import { makeExplorePaneState } from './utils';
@ -164,6 +164,7 @@ describe('running queries', () => {
querySubscription: unsubscribable,
queries: ['A'],
range: testRange,
supplementaryQueries: { [SupplementaryQueryType.LogsVolume]: { enabled: true } },
},
},
@ -179,8 +180,8 @@ describe('running queries', () => {
expect(dispatchedActions).toEqual([
scanStopAction({ exploreId }),
cancelQueriesAction({ exploreId }),
storeLogsVolumeDataProviderAction({ exploreId, logsVolumeDataProvider: undefined }),
cleanLogsVolumeAction({ exploreId }),
storeSupplementaryQueryDataProviderAction({ exploreId, type: SupplementaryQueryType.LogsVolume }),
cleanSupplementaryQueryAction({ exploreId, type: SupplementaryQueryType.LogsVolume }),
]);
});
});
@ -433,8 +434,12 @@ describe('reducer', () => {
expect(unsubscribes).toHaveLength(1);
expect(unsubscribes[0]).toBeCalled();
expect(getState().explore[ExploreId.left].logsVolumeData).toBeUndefined();
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).toBeUndefined();
});
it('should load logs volume after running the query', async () => {
@ -448,13 +453,23 @@ describe('reducer', () => {
};
await dispatch(runQueries(ExploreId.left));
expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined();
expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Loading);
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data!.state
).toBe(LoadingState.Loading);
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).toBeDefined();
await dispatch(cancelQueries(ExploreId.left));
expect(getState().explore[ExploreId.left].logsVolumeData).toBeUndefined();
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeUndefined();
});
it('keeps complete log volume data when main query is canceled', async () => {
@ -466,43 +481,69 @@ describe('reducer', () => {
};
await dispatch(runQueries(ExploreId.left));
expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined();
expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Done);
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data!.state
).toBe(LoadingState.Done);
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).toBeDefined();
await dispatch(cancelQueries(ExploreId.left));
expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined();
expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Done);
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data!.state
).toBe(LoadingState.Done);
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).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);
dispatch(setSupplementaryQueryEnabled(ExploreId.left, false, SupplementaryQueryType.LogsVolume));
expect(getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].enabled).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();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].data
).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataSubscription
).toBeUndefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).toBeDefined();
});
it('load logsVolume data when it gets enabled', async () => {
// first it is disabled
dispatch(setLogsVolumeEnabled(ExploreId.left, false));
dispatch(setSupplementaryQueryEnabled(ExploreId.left, false, SupplementaryQueryType.LogsVolume));
// runQueries sets up the logsVolume query, but does not run it
await dispatch(runQueries(ExploreId.left));
expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataProvider
).toBeDefined();
// we turn logsvolume on
await dispatch(setLogsVolumeEnabled(ExploreId.left, true));
await dispatch(setSupplementaryQueryEnabled(ExploreId.left, true, SupplementaryQueryType.LogsVolume));
// verify it was turned on
expect(getState().explore[ExploreId.left].logsVolumeEnabled).toBe(true);
expect(getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].enabled).toBe(
true
);
expect(getState().explore[ExploreId.left].logsVolumeDataSubscription).toBeDefined();
expect(
getState().explore[ExploreId.left].supplementaryQueries[SupplementaryQueryType.LogsVolume].dataSubscription
).toBeDefined();
});
});
});

View File

@ -36,7 +36,7 @@ import { getTimeZone } from 'app/features/profile/state/selectors';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
import { store } from 'app/store/store';
import { ExploreItemState, ExplorePanelData, ThunkDispatch, ThunkResult } from 'app/types';
import { ExploreId, ExploreState, QueryOptions } from 'app/types/explore';
import { ExploreId, ExploreState, QueryOptions, SupplementaryQueryType, SupplementaryQueries } from 'app/types/explore';
import { notifyApp } from '../../../core/actions';
import { createErrorNotification } from '../../../core/copy/appNotification';
@ -46,7 +46,12 @@ import { decorateData } from '../utils/decorators';
import { addHistoryItem, historyUpdatedAction, loadRichHistory } from './history';
import { stateSave } from './main';
import { updateTime } from './time';
import { createCacheKey, getResultsFromCache, storeLogsVolumeEnabled } from './utils';
import {
createCacheKey,
getResultsFromCache,
storeSupplementaryQueryEnabled,
SUPPLEMENTARY_QUERY_TYPES,
} from './utils';
//
// Actions and Payloads
@ -95,43 +100,50 @@ export const queryStoreSubscriptionAction = createAction<QueryStoreSubscriptionP
'explore/queryStoreSubscription'
);
const setLogsVolumeEnabledAction = createAction<{ exploreId: ExploreId; enabled: boolean }>(
'explore/setLogsVolumeEnabledAction'
);
export interface StoreLogsVolumeDataProvider {
const setSupplementaryQueryEnabledAction = createAction<{
exploreId: ExploreId;
logsVolumeDataProvider?: Observable<DataQueryResponse>;
type: SupplementaryQueryType;
enabled: boolean;
}>('explore/setSupplementaryQueryEnabledAction');
export interface StoreSupplementaryQueryDataProvider {
exploreId: ExploreId;
dataProvider?: Observable<DataQueryResponse>;
type: SupplementaryQueryType;
}
/**
* Stores available logs volume provider after running the query. Used internally by runQueries().
*/
export const storeLogsVolumeDataProviderAction = createAction<StoreLogsVolumeDataProvider>(
'explore/storeLogsVolumeDataProviderAction'
export const storeSupplementaryQueryDataProviderAction = createAction<StoreSupplementaryQueryDataProvider>(
'explore/storeSupplementaryQueryDataProviderAction'
);
export const cleanLogsVolumeAction = createAction<{ exploreId: ExploreId }>('explore/cleanLogsVolumeAction');
export const cleanSupplementaryQueryAction = createAction<{ exploreId: ExploreId; type: SupplementaryQueryType }>(
'explore/cleanSupplementaryQueryAction'
);
export interface StoreLogsVolumeDataSubscriptionPayload {
export interface StoreSupplementaryQueryDataSubscriptionPayload {
exploreId: ExploreId;
logsVolumeDataSubscription?: SubscriptionLike;
dataSubscription?: SubscriptionLike;
type: SupplementaryQueryType;
}
/**
* Stores current logs volume subscription for given explore pane.
*/
const storeLogsVolumeDataSubscriptionAction = createAction<StoreLogsVolumeDataSubscriptionPayload>(
'explore/storeLogsVolumeDataSubscriptionAction'
const storeSupplementaryQueryDataSubscriptionAction = createAction<StoreSupplementaryQueryDataSubscriptionPayload>(
'explore/storeSupplementaryQueryDataSubscriptionAction'
);
/**
* Stores data returned by the provider. Used internally by loadLogsVolumeData().
* Stores data returned by the provider. Used internally by loadSupplementaryQueryData().
*/
const updateLogsVolumeDataAction = createAction<{
const updateSupplementaryQueryDataAction = createAction<{
exploreId: ExploreId;
logsVolumeData: DataQueryResponse;
}>('explore/updateLogsVolumeDataAction');
type: SupplementaryQueryType;
data: DataQueryResponse;
}>('explore/updateSupplementaryQueryDataAction');
export interface QueryEndedPayload {
exploreId: ExploreId;
@ -231,15 +243,16 @@ export function cancelQueries(exploreId: ExploreId): ThunkResult<void> {
return (dispatch, getState) => {
dispatch(scanStopAction({ exploreId }));
dispatch(cancelQueriesAction({ exploreId }));
dispatch(
storeLogsVolumeDataProviderAction({
exploreId,
logsVolumeDataProvider: undefined,
})
);
// clear any incomplete data
if (getState().explore[exploreId]!.logsVolumeData?.state !== LoadingState.Done) {
dispatch(cleanLogsVolumeAction({ exploreId }));
const supplementaryQueries = getState().explore[exploreId]!.supplementaryQueries;
// Cancel all data providers
for (const type of SUPPLEMENTARY_QUERY_TYPES) {
dispatch(storeSupplementaryQueryDataProviderAction({ exploreId, dataProvider: undefined, type }));
// And clear any incomplete data
if (supplementaryQueries[type]?.data?.state !== LoadingState.Done) {
dispatch(cleanSupplementaryQueryAction({ exploreId, type }));
}
}
dispatch(stateSave());
};
@ -425,7 +438,6 @@ export const runQueries = (
refreshInterval,
absoluteRange,
cache,
logsVolumeEnabled,
} = exploreItemState;
let newQuerySub;
@ -558,13 +570,25 @@ export const runQueries = (
});
if (live) {
dispatch(
storeLogsVolumeDataProviderAction({
exploreId,
logsVolumeDataProvider: undefined,
})
);
dispatch(cleanLogsVolumeAction({ exploreId }));
for (const type of SUPPLEMENTARY_QUERY_TYPES) {
dispatch(
storeSupplementaryQueryDataProviderAction({
exploreId,
dataProvider: undefined,
type,
})
);
dispatch(cleanSupplementaryQueryAction({ exploreId, type }));
}
// In this whole part., we need to figure out
// checking the type of enabled supp queries
// then for which enabled supp queries has data source support
// and then we need to run the supp queries
// but we need to make sure that supp queries that dont work
// return undefined provider
// we should also make sure we store the type of provider that
// was last stored
} else if (hasLogsVolumeSupport(datasourceInstance)) {
// we always prepare the logsVolumeProvider,
// but we only load it, if the logs-volume-histogram is enabled.
@ -576,25 +600,29 @@ export const runQueries = (
...transaction.request,
requestId: transaction.request.requestId + '_log_volume',
};
const logsVolumeDataProvider = datasourceInstance.getLogsVolumeDataProvider(sourceRequest);
const type = SupplementaryQueryType.LogsVolume;
const dataProvider = datasourceInstance.getLogsVolumeDataProvider(sourceRequest);
dispatch(
storeLogsVolumeDataProviderAction({
storeSupplementaryQueryDataProviderAction({
exploreId,
logsVolumeDataProvider,
type,
dataProvider,
})
);
const { logsVolumeData, absoluteRange } = getState().explore[exploreId]!;
if (!canReuseLogsVolumeData(logsVolumeData, queries, absoluteRange)) {
dispatch(cleanLogsVolumeAction({ exploreId }));
if (logsVolumeEnabled) {
dispatch(loadLogsVolumeData(exploreId));
const { supplementaryQueries, absoluteRange } = getState().explore[exploreId]!;
if (!canReuseSupplementaryQueryData(supplementaryQueries[type].data, queries, absoluteRange)) {
dispatch(cleanSupplementaryQueryAction({ exploreId, type }));
if (supplementaryQueries[type].enabled) {
dispatch(loadSupplementaryQueryData(exploreId, type));
}
}
} else {
dispatch(
storeLogsVolumeDataProviderAction({
storeSupplementaryQueryDataProviderAction({
exploreId,
logsVolumeDataProvider: undefined,
dataProvider: undefined,
type: SupplementaryQueryType.LogsVolume,
})
);
}
@ -605,20 +633,20 @@ export const runQueries = (
};
/**
* Checks if after changing the time range the existing data can be used to show logs volume.
* Checks if after changing the time range the existing data can be used to show supplementary query.
* It can happen if queries are the same and new time range is within existing data time range.
*/
function canReuseLogsVolumeData(
logsVolumeData: DataQueryResponse | undefined,
function canReuseSupplementaryQueryData(
supplementaryQueryData: DataQueryResponse | undefined,
queries: DataQuery[],
selectedTimeRange: AbsoluteTimeRange
): boolean {
if (logsVolumeData && logsVolumeData.data[0]) {
if (supplementaryQueryData && supplementaryQueryData.data[0]) {
// check if queries are the same
if (!deepEqual(logsVolumeData.data[0].meta?.custom?.targets, queries)) {
if (!deepEqual(supplementaryQueryData.data[0].meta?.custom?.targets, queries)) {
return false;
}
const dataRange = logsVolumeData && logsVolumeData.data[0] && logsVolumeData.data[0].meta?.custom?.absoluteRange;
const dataRange = supplementaryQueryData.data[0].meta?.custom?.absoluteRange;
// if selected range is within loaded logs volume
if (dataRange && dataRange.from <= selectedTimeRange.from && selectedTimeRange.to <= dataRange.to) {
return true;
@ -664,7 +692,7 @@ export function addResultsToCache(exploreId: ExploreId): ThunkResult<void> {
const absoluteRange = getState().explore[exploreId]!.absoluteRange;
const cacheKey = createCacheKey(absoluteRange);
// Save results to cache only when all results recived and loading is done
// Save results to cache only when all results received and loading is done
if (queryResponse.state === LoadingState.Done) {
dispatch(addResultsToCacheAction({ exploreId, cacheKey, queryResponse }));
}
@ -680,26 +708,38 @@ export function clearCache(exploreId: ExploreId): ThunkResult<void> {
/**
* Initializes loading logs volume data and stores emitted value.
*/
export function loadLogsVolumeData(exploreId: ExploreId): ThunkResult<void> {
export function loadSupplementaryQueryData(exploreId: ExploreId, type: SupplementaryQueryType): ThunkResult<void> {
return (dispatch, getState) => {
const { logsVolumeDataProvider } = getState().explore[exploreId]!;
if (logsVolumeDataProvider) {
const logsVolumeDataSubscription = logsVolumeDataProvider.subscribe({
next: (logsVolumeData: DataQueryResponse) => {
dispatch(updateLogsVolumeDataAction({ exploreId, logsVolumeData }));
const { supplementaryQueries } = getState().explore[exploreId]!;
const dataProvider = supplementaryQueries[type].dataProvider;
if (dataProvider) {
const dataSubscription = dataProvider.subscribe({
next: (supplementaryQueryData: DataQueryResponse) => {
dispatch(updateSupplementaryQueryDataAction({ exploreId, type, data: supplementaryQueryData }));
},
});
dispatch(storeLogsVolumeDataSubscriptionAction({ exploreId, logsVolumeDataSubscription }));
dispatch(
storeSupplementaryQueryDataSubscriptionAction({
exploreId,
type,
dataSubscription,
})
);
}
};
}
export function setLogsVolumeEnabled(exploreId: ExploreId, enabled: boolean): ThunkResult<void> {
export function setSupplementaryQueryEnabled(
exploreId: ExploreId,
enabled: boolean,
type: SupplementaryQueryType
): ThunkResult<void> {
return (dispatch, getState) => {
dispatch(setLogsVolumeEnabledAction({ exploreId, enabled }));
storeLogsVolumeEnabled(enabled);
dispatch(setSupplementaryQueryEnabledAction({ exploreId, enabled, type }));
storeSupplementaryQueryEnabled(enabled, type);
if (enabled) {
dispatch(loadLogsVolumeData(exploreId));
dispatch(loadSupplementaryQueryData(exploreId, type));
}
};
}
@ -763,53 +803,87 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor
};
}
if (setLogsVolumeEnabledAction.match(action)) {
const { enabled } = action.payload;
if (!enabled && state.logsVolumeDataSubscription) {
state.logsVolumeDataSubscription.unsubscribe();
if (setSupplementaryQueryEnabledAction.match(action)) {
const { enabled, type } = action.payload;
const { supplementaryQueries } = state;
const dataSubscription = supplementaryQueries[type].dataSubscription;
if (!enabled && dataSubscription) {
dataSubscription.unsubscribe();
}
return {
...state,
logsVolumeEnabled: enabled,
const nextSupplementaryQueries: SupplementaryQueries = {
...supplementaryQueries,
// NOTE: the dataProvider is not cleared, we may need it later,
// if the user re-enables the histogram-visualization
logsVolumeData: undefined,
// if the user re-enables the supplementary query
[type]: { ...supplementaryQueries[type], enabled, data: undefined },
};
return {
...state,
supplementaryQueries: nextSupplementaryQueries,
};
}
if (storeLogsVolumeDataProviderAction.match(action)) {
let { logsVolumeDataProvider } = action.payload;
if (state.logsVolumeDataSubscription) {
state.logsVolumeDataSubscription.unsubscribe();
if (storeSupplementaryQueryDataProviderAction.match(action)) {
const { dataProvider, type } = action.payload;
const { supplementaryQueries } = state;
const supplementaryQuery = supplementaryQueries[type];
if (supplementaryQuery?.dataSubscription) {
supplementaryQuery.dataSubscription.unsubscribe();
}
const nextSupplementaryQueries = {
...supplementaryQueries,
[type]: { ...supplementaryQuery, dataProvider, dataSubscription: undefined },
};
return {
...state,
logsVolumeDataProvider,
logsVolumeDataSubscription: undefined,
supplementaryQueries: nextSupplementaryQueries,
};
}
if (cleanLogsVolumeAction.match(action)) {
if (cleanSupplementaryQueryAction.match(action)) {
const { type } = action.payload;
const { supplementaryQueries } = state;
const nextSupplementaryQueries = {
...supplementaryQueries,
[type]: { ...supplementaryQueries[type], data: undefined },
};
return {
...state,
logsVolumeData: undefined,
supplementaryQueries: nextSupplementaryQueries,
};
}
if (storeLogsVolumeDataSubscriptionAction.match(action)) {
const { logsVolumeDataSubscription } = action.payload;
if (storeSupplementaryQueryDataSubscriptionAction.match(action)) {
const { dataSubscription, type } = action.payload;
const { supplementaryQueries } = state;
const nextSupplementaryQueries = {
...supplementaryQueries,
[type]: { ...supplementaryQueries[type], dataSubscription },
};
return {
...state,
logsVolumeDataSubscription,
supplementaryQueries: nextSupplementaryQueries,
};
}
if (updateLogsVolumeDataAction.match(action)) {
let { logsVolumeData } = action.payload;
if (updateSupplementaryQueryDataAction.match(action)) {
let { data, type } = action.payload;
const { supplementaryQueries } = state;
const nextSupplementaryQueries = {
...supplementaryQueries,
[type]: { ...supplementaryQueries[type], data },
};
return {
...state,
logsVolumeData,
supplementaryQueries: nextSupplementaryQueries,
};
}

View File

@ -12,7 +12,7 @@ import {
PanelData,
} from '@grafana/data';
import { ExplorePanelData } from 'app/types';
import { ExploreItemState } from 'app/types/explore';
import { ExploreItemState, SupplementaryQueries, SupplementaryQueryType } from 'app/types/explore';
import store from '../../../core/store';
import { clearQueryKeys, lastUsedDatasourceKeyForOrgId } from '../../../core/utils/explore';
@ -20,6 +20,14 @@ import { getDatasourceSrv } from '../../plugins/datasource_srv';
import { SETTINGS_KEYS } from '../utils/logs';
import { toRawTimeRange } from '../utils/time';
export const SUPPLEMENTARY_QUERY_TYPES: SupplementaryQueryType[] = [SupplementaryQueryType.LogsVolume];
// Used to match supplementaryQueryType to corresponding local storage key
// TODO: Remove this and unify enum values with SETTINGS_KEYS.enableVolumeHistogram
const supplementaryQuerySettings: { [key in SupplementaryQueryType]: string } = {
[SupplementaryQueryType.LogsVolume]: SETTINGS_KEYS.enableVolumeHistogram,
};
export const DEFAULT_RANGE = {
from: 'now-6h',
to: 'now',
@ -30,19 +38,25 @@ export const storeGraphStyle = (graphStyle: string): void => {
store.set(GRAPH_STYLE_KEY, graphStyle);
};
const LOGS_VOLUME_ENABLED_KEY = SETTINGS_KEYS.enableVolumeHistogram;
export const storeLogsVolumeEnabled = (enabled: boolean): void => {
store.set(LOGS_VOLUME_ENABLED_KEY, enabled ? 'true' : 'false');
export const storeSupplementaryQueryEnabled = (enabled: boolean, type: SupplementaryQueryType): void => {
if (supplementaryQuerySettings[type]) {
store.set(supplementaryQuerySettings[type], enabled ? 'true' : 'false');
}
};
const loadLogsVolumeEnabled = (): boolean => {
const data = store.get(LOGS_VOLUME_ENABLED_KEY);
// we default to `enabled=true`
if (data === 'false') {
return false;
}
export const loadSupplementaryQueries = (): SupplementaryQueries => {
// We default to true for all supp queries
let supplementaryQueries: SupplementaryQueries = {
[SupplementaryQueryType.LogsVolume]: { enabled: true },
};
return true;
for (const type of SUPPLEMENTARY_QUERY_TYPES) {
// Only if "false" value in local storage, we disable it
if (store.get(supplementaryQuerySettings[type]) === 'false') {
supplementaryQueries[type] = { enabled: false };
}
}
return supplementaryQueries;
};
/**
@ -77,9 +91,7 @@ export const makeExplorePaneState = (): ExploreItemState => ({
eventBridge: null as unknown as EventBusExtended,
cache: [],
richHistory: [],
logsVolumeEnabled: loadLogsVolumeEnabled(),
logsVolumeDataProvider: undefined,
logsVolumeData: undefined,
supplementaryQueries: loadSupplementaryQueries(),
panelsState: {},
});

View File

@ -204,12 +204,10 @@ export interface ExploreItemState {
*/
cache: Array<{ key: string; value: ExplorePanelData }>;
// properties below should be more generic if we add more providers
// see also: DataSourceWithLogsVolumeSupport
logsVolumeEnabled: boolean;
logsVolumeDataProvider?: Observable<DataQueryResponse>;
logsVolumeDataSubscription?: SubscriptionLike;
logsVolumeData?: DataQueryResponse;
/**
* Supplementary queries are additional queries used in Explore, e.g. for logs volume
*/
supplementaryQueries: SupplementaryQueries;
panelsState: ExplorePanelsState;
@ -270,3 +268,18 @@ export enum TABLE_RESULTS_STYLE {
}
export const TABLE_RESULTS_STYLES = [TABLE_RESULTS_STYLE.table, TABLE_RESULTS_STYLE.raw];
export type TableResultsStyle = typeof TABLE_RESULTS_STYLES[number];
export interface SupplementaryQuery {
enabled: boolean;
dataProvider?: Observable<DataQueryResponse>;
dataSubscription?: SubscriptionLike;
data?: DataQueryResponse;
}
export type SupplementaryQueries = {
[key in SupplementaryQueryType]: SupplementaryQuery;
};
export enum SupplementaryQueryType {
LogsVolume = 'LogsVolume',
}