mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Use rich history local storage for autocomplete (#81386)
* move autocomplete logic * Tests * Write to correct history * Remove historyUpdatedAction and related code * add helpful comments * Add option to mute all errors/warnings for autocomplete * Add back in legacy local storage query history for transition period * Move params to an object for easier use and defaults * Do not make time filter required * fix tests * change deprecation version and add issue number
This commit is contained in:
parent
ea8b3267e5
commit
bbe9c8661a
@ -37,10 +37,16 @@ export default class RichHistoryLocalStorage implements RichHistoryStorage {
|
|||||||
const allQueries = getRichHistoryDTOs().map(fromDTO);
|
const allQueries = getRichHistoryDTOs().map(fromDTO);
|
||||||
const queries = filters.starred ? allQueries.filter((q) => q.starred === true) : allQueries;
|
const queries = filters.starred ? allQueries.filter((q) => q.starred === true) : allQueries;
|
||||||
|
|
||||||
const richHistory = filterAndSortQueries(queries, filters.sortOrder, filters.datasourceFilters, filters.search, [
|
const timeFilter: [number, number] | undefined =
|
||||||
filters.from,
|
filters.from && filters.to ? [filters.from, filters.to] : undefined;
|
||||||
filters.to,
|
|
||||||
]);
|
const richHistory = filterAndSortQueries(
|
||||||
|
queries,
|
||||||
|
filters.sortOrder,
|
||||||
|
filters.datasourceFilters,
|
||||||
|
filters.search,
|
||||||
|
timeFilter
|
||||||
|
);
|
||||||
return { richHistory, total: richHistory.length };
|
return { richHistory, total: richHistory.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,16 @@ import RichHistoryStorage from './RichHistoryStorage';
|
|||||||
const richHistoryLocalStorage = new RichHistoryLocalStorage();
|
const richHistoryLocalStorage = new RichHistoryLocalStorage();
|
||||||
const richHistoryRemoteStorage = new RichHistoryRemoteStorage();
|
const richHistoryRemoteStorage = new RichHistoryRemoteStorage();
|
||||||
|
|
||||||
|
// for query history operations
|
||||||
export const getRichHistoryStorage = (): RichHistoryStorage => {
|
export const getRichHistoryStorage = (): RichHistoryStorage => {
|
||||||
return config.queryHistoryEnabled ? richHistoryRemoteStorage : richHistoryLocalStorage;
|
return config.queryHistoryEnabled ? richHistoryRemoteStorage : richHistoryLocalStorage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for autocomplete read and write operations
|
||||||
|
export const getLocalRichHistoryStorage = (): RichHistoryStorage => {
|
||||||
|
return richHistoryLocalStorage;
|
||||||
|
};
|
||||||
|
|
||||||
interface RichHistorySupportedFeatures {
|
interface RichHistorySupportedFeatures {
|
||||||
availableFilters: SortOrder[];
|
availableFilters: SortOrder[];
|
||||||
lastUsedDataSourcesAvailable: boolean;
|
lastUsedDataSourcesAvailable: boolean;
|
||||||
|
@ -2,7 +2,6 @@ import { DataSourceApi, dateTime, ExploreUrlState, LogsSortOrder } from '@grafan
|
|||||||
import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
|
import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
|
||||||
import { DataQuery } from '@grafana/schema';
|
import { DataQuery } from '@grafana/schema';
|
||||||
import { RefreshPicker } from '@grafana/ui';
|
import { RefreshPicker } from '@grafana/ui';
|
||||||
import store from 'app/core/store';
|
|
||||||
import { DEFAULT_RANGE } from 'app/features/explore/state/utils';
|
import { DEFAULT_RANGE } from 'app/features/explore/state/utils';
|
||||||
|
|
||||||
import { DatasourceSrvMock, MockDataSourceApi } from '../../../test/mocks/datasource_srv';
|
import { DatasourceSrvMock, MockDataSourceApi } from '../../../test/mocks/datasource_srv';
|
||||||
@ -11,7 +10,6 @@ import {
|
|||||||
buildQueryTransaction,
|
buildQueryTransaction,
|
||||||
hasNonEmptyQuery,
|
hasNonEmptyQuery,
|
||||||
refreshIntervalToSortOrder,
|
refreshIntervalToSortOrder,
|
||||||
updateHistory,
|
|
||||||
getExploreUrl,
|
getExploreUrl,
|
||||||
GetExploreUrlArguments,
|
GetExploreUrlArguments,
|
||||||
getTimeRange,
|
getTimeRange,
|
||||||
@ -151,27 +149,6 @@ describe('getExploreUrl', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateHistory()', () => {
|
|
||||||
const datasourceId = 'myDatasource';
|
|
||||||
const key = `grafana.explore.history.${datasourceId}`;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store.delete(key);
|
|
||||||
expect(store.exists(key)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should save history item to localStorage', () => {
|
|
||||||
const expected = [
|
|
||||||
{
|
|
||||||
query: { refId: '1', expr: 'metric' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
expect(updateHistory([], datasourceId, [{ refId: '1', expr: 'metric' }])).toMatchObject(expected);
|
|
||||||
expect(store.exists(key)).toBeTruthy();
|
|
||||||
expect(store.getObject(key)).toMatchObject(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('hasNonEmptyQuery', () => {
|
describe('hasNonEmptyQuery', () => {
|
||||||
test('should return true if one query is non-empty', () => {
|
test('should return true if one query is non-empty', () => {
|
||||||
expect(hasNonEmptyQuery([{ refId: '1', key: '2', context: 'explore', expr: 'foo' }])).toBeTruthy();
|
expect(hasNonEmptyQuery([{ refId: '1', key: '2', context: 'explore', expr: 'foo' }])).toBeTruthy();
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
DataSourceRef,
|
DataSourceRef,
|
||||||
DefaultTimeZone,
|
DefaultTimeZone,
|
||||||
HistoryItem,
|
|
||||||
IntervalValues,
|
IntervalValues,
|
||||||
LogsDedupStrategy,
|
LogsDedupStrategy,
|
||||||
LogsSortOrder,
|
LogsSortOrder,
|
||||||
@ -37,8 +36,6 @@ export const DEFAULT_UI_STATE = {
|
|||||||
export const ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
export const ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
const nanoid = customAlphabet(ID_ALPHABET, 3);
|
const nanoid = customAlphabet(ID_ALPHABET, 3);
|
||||||
|
|
||||||
const MAX_HISTORY_ITEMS = 100;
|
|
||||||
|
|
||||||
const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
|
const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
|
||||||
const lastUsedDatasourceKeyForOrgId = (orgId: number) => `${LAST_USED_DATASOURCE_KEY}.${orgId}`;
|
const lastUsedDatasourceKeyForOrgId = (orgId: number) => `${LAST_USED_DATASOURCE_KEY}.${orgId}`;
|
||||||
export const getLastUsedDatasourceUID = (orgId: number) =>
|
export const getLastUsedDatasourceUID = (orgId: number) =>
|
||||||
@ -276,35 +273,6 @@ export function hasNonEmptyQuery<TQuery extends DataQuery>(queries: TQuery[]): b
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the query history. Side-effect: store history in local storage
|
|
||||||
*/
|
|
||||||
export function updateHistory<T extends DataQuery>(
|
|
||||||
history: Array<HistoryItem<T>>,
|
|
||||||
datasourceId: string,
|
|
||||||
queries: T[]
|
|
||||||
): Array<HistoryItem<T>> {
|
|
||||||
const ts = Date.now();
|
|
||||||
let updatedHistory = history;
|
|
||||||
queries.forEach((query) => {
|
|
||||||
updatedHistory = [{ query, ts }, ...updatedHistory];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updatedHistory.length > MAX_HISTORY_ITEMS) {
|
|
||||||
updatedHistory = updatedHistory.slice(0, MAX_HISTORY_ITEMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine all queries of a datasource type into one history
|
|
||||||
const historyKey = `grafana.explore.history.${datasourceId}`;
|
|
||||||
try {
|
|
||||||
store.setObject(historyKey, updatedHistory);
|
|
||||||
return updatedHistory;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getQueryKeys = (queries: DataQuery[]): string[] => {
|
export const getQueryKeys = (queries: DataQuery[]): string[] => {
|
||||||
const queryKeys = queries.reduce<string[]>((newQueryKeys, query, index) => {
|
const queryKeys = queries.reduce<string[]>((newQueryKeys, query, index) => {
|
||||||
const primaryKey = query.datasource?.uid || query.key;
|
const primaryKey = query.datasource?.uid || query.key;
|
||||||
|
@ -109,15 +109,13 @@ describe('richHistory', () => {
|
|||||||
|
|
||||||
it('should append query to query history', async () => {
|
it('should append query to query history', async () => {
|
||||||
Date.now = jest.fn(() => 2);
|
Date.now = jest.fn(() => 2);
|
||||||
const { limitExceeded, richHistoryStorageFull } = await addToRichHistory(
|
const { limitExceeded, richHistoryStorageFull } = await addToRichHistory({
|
||||||
mock.testDatasourceUid,
|
localOverride: false,
|
||||||
mock.testDatasourceName,
|
datasource: { uid: mock.testDatasourceUid, name: mock.testDatasourceName },
|
||||||
mock.testQueries,
|
queries: mock.testQueries,
|
||||||
mock.testStarred,
|
starred: mock.testStarred,
|
||||||
mock.testComment,
|
comment: mock.testComment,
|
||||||
true,
|
});
|
||||||
true
|
|
||||||
);
|
|
||||||
expect(limitExceeded).toBeFalsy();
|
expect(limitExceeded).toBeFalsy();
|
||||||
expect(richHistoryStorageFull).toBeFalsy();
|
expect(richHistoryStorageFull).toBeFalsy();
|
||||||
expect(richHistoryStorageMock.addToRichHistory).toBeCalledWith({
|
expect(richHistoryStorageMock.addToRichHistory).toBeCalledWith({
|
||||||
@ -142,15 +140,13 @@ describe('richHistory', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const { richHistoryStorageFull, limitExceeded } = await addToRichHistory(
|
const { richHistoryStorageFull, limitExceeded } = await addToRichHistory({
|
||||||
mock.testDatasourceUid,
|
localOverride: false,
|
||||||
mock.testDatasourceName,
|
datasource: { uid: mock.testDatasourceUid, name: mock.testDatasourceName },
|
||||||
mock.testQueries,
|
queries: mock.testQueries,
|
||||||
mock.testStarred,
|
starred: mock.testStarred,
|
||||||
mock.testComment,
|
comment: mock.testComment,
|
||||||
true,
|
});
|
||||||
true
|
|
||||||
);
|
|
||||||
expect(richHistoryStorageFull).toBeFalsy();
|
expect(richHistoryStorageFull).toBeFalsy();
|
||||||
expect(limitExceeded).toBeTruthy();
|
expect(limitExceeded).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
RichHistoryStorageWarning,
|
RichHistoryStorageWarning,
|
||||||
RichHistoryStorageWarningDetails,
|
RichHistoryStorageWarningDetails,
|
||||||
} from '../history/RichHistoryStorage';
|
} from '../history/RichHistoryStorage';
|
||||||
import { getRichHistoryStorage } from '../history/richHistoryStorageProvider';
|
import { getLocalRichHistoryStorage, getRichHistoryStorage } from '../history/richHistoryStorageProvider';
|
||||||
|
|
||||||
import { RichHistorySearchFilters, RichHistorySettings, SortOrder } from './richHistoryTypes';
|
import { RichHistorySearchFilters, RichHistorySettings, SortOrder } from './richHistoryTypes';
|
||||||
|
|
||||||
@ -26,15 +26,27 @@ export { RichHistorySearchFilters, RichHistorySettings, SortOrder };
|
|||||||
* Side-effect: store history in local storage
|
* Side-effect: store history in local storage
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type addToRichHistoryParams = {
|
||||||
|
localOverride: boolean;
|
||||||
|
datasource: { uid: string; name?: string };
|
||||||
|
queries: DataQuery[];
|
||||||
|
starred: boolean;
|
||||||
|
comment?: string;
|
||||||
|
showNotif?: {
|
||||||
|
quotaExceededError?: boolean;
|
||||||
|
limitExceededWarning?: boolean;
|
||||||
|
otherErrors?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export async function addToRichHistory(
|
export async function addToRichHistory(
|
||||||
datasourceUid: string,
|
params: addToRichHistoryParams
|
||||||
datasourceName: string | null,
|
|
||||||
queries: DataQuery[],
|
|
||||||
starred: boolean,
|
|
||||||
comment: string | null,
|
|
||||||
showQuotaExceededError: boolean,
|
|
||||||
showLimitExceededWarning: boolean
|
|
||||||
): Promise<{ richHistoryStorageFull?: boolean; limitExceeded?: boolean }> {
|
): Promise<{ richHistoryStorageFull?: boolean; limitExceeded?: boolean }> {
|
||||||
|
const { queries, localOverride, datasource, starred, comment, showNotif } = params;
|
||||||
|
// default showing of errors to true
|
||||||
|
const showQuotaExceededError = showNotif?.quotaExceededError ?? true;
|
||||||
|
const showLimitExceededWarning = showNotif?.limitExceededWarning ?? true;
|
||||||
|
const showOtherErrors = showNotif?.otherErrors ?? true;
|
||||||
/* Save only queries, that are not falsy (e.g. empty object, null, ...) */
|
/* Save only queries, that are not falsy (e.g. empty object, null, ...) */
|
||||||
const newQueriesToSave: DataQuery[] = queries && queries.filter((query) => notEmptyQuery(query));
|
const newQueriesToSave: DataQuery[] = queries && queries.filter((query) => notEmptyQuery(query));
|
||||||
|
|
||||||
@ -44,9 +56,11 @@ export async function addToRichHistory(
|
|||||||
let warning: RichHistoryStorageWarningDetails | undefined;
|
let warning: RichHistoryStorageWarningDetails | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await getRichHistoryStorage().addToRichHistory({
|
// for autocomplete we want to ensure writing to local storage
|
||||||
datasourceUid: datasourceUid,
|
const storage = localOverride ? getLocalRichHistoryStorage() : getRichHistoryStorage();
|
||||||
datasourceName: datasourceName ?? '',
|
const result = await storage.addToRichHistory({
|
||||||
|
datasourceUid: datasource.uid,
|
||||||
|
datasourceName: datasource.name ?? '',
|
||||||
queries: newQueriesToSave,
|
queries: newQueriesToSave,
|
||||||
starred,
|
starred,
|
||||||
comment: comment ?? '',
|
comment: comment ?? '',
|
||||||
@ -57,7 +71,7 @@ export async function addToRichHistory(
|
|||||||
if (error.name === RichHistoryServiceError.StorageFull) {
|
if (error.name === RichHistoryServiceError.StorageFull) {
|
||||||
richHistoryStorageFull = true;
|
richHistoryStorageFull = true;
|
||||||
showQuotaExceededError && dispatch(notifyApp(createErrorNotification(error.message)));
|
showQuotaExceededError && dispatch(notifyApp(createErrorNotification(error.message)));
|
||||||
} else if (error.name !== RichHistoryServiceError.DuplicatedEntry) {
|
} else if (showOtherErrors && error.name !== RichHistoryServiceError.DuplicatedEntry) {
|
||||||
dispatch(
|
dispatch(
|
||||||
notifyApp(
|
notifyApp(
|
||||||
createErrorNotification(
|
createErrorNotification(
|
||||||
|
@ -23,8 +23,8 @@ export type RichHistorySearchFilters = {
|
|||||||
sortOrder: SortOrder;
|
sortOrder: SortOrder;
|
||||||
/** Names of data sources (not uids) - used by local and remote storage **/
|
/** Names of data sources (not uids) - used by local and remote storage **/
|
||||||
datasourceFilters: string[];
|
datasourceFilters: string[];
|
||||||
from: number;
|
from?: number;
|
||||||
to: number;
|
to?: number;
|
||||||
starred: boolean;
|
starred: boolean;
|
||||||
page?: number;
|
page?: number;
|
||||||
};
|
};
|
||||||
|
@ -166,6 +166,10 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
|
|||||||
const mappedQueriesToHeadings = mapQueriesToHeadings(queries, richHistorySearchFilters.sortOrder);
|
const mappedQueriesToHeadings = mapQueriesToHeadings(queries, richHistorySearchFilters.sortOrder);
|
||||||
const sortOrderOptions = getSortOrderOptions();
|
const sortOrderOptions = getSortOrderOptions();
|
||||||
const partialResults = queries.length && queries.length !== totalQueries;
|
const partialResults = queries.length && queries.length !== totalQueries;
|
||||||
|
const timeFilter = [
|
||||||
|
richHistorySearchFilters.from || 0,
|
||||||
|
richHistorySearchFilters.to || richHistorySettings.retentionPeriod,
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -174,13 +178,13 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
|
|||||||
<div className={styles.labelSlider}>
|
<div className={styles.labelSlider}>
|
||||||
<Trans i18nKey="explore.rich-history-queries-tab.filter-history">Filter history</Trans>
|
<Trans i18nKey="explore.rich-history-queries-tab.filter-history">Filter history</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.labelSlider}>{mapNumbertoTimeInSlider(richHistorySearchFilters.from)}</div>
|
<div className={styles.labelSlider}>{mapNumbertoTimeInSlider(timeFilter[0])}</div>
|
||||||
<div className={styles.slider}>
|
<div className={styles.slider}>
|
||||||
<RangeSlider
|
<RangeSlider
|
||||||
tooltipAlwaysVisible={false}
|
tooltipAlwaysVisible={false}
|
||||||
min={0}
|
min={0}
|
||||||
max={richHistorySettings.retentionPeriod}
|
max={richHistorySettings.retentionPeriod}
|
||||||
value={[richHistorySearchFilters.from, richHistorySearchFilters.to]}
|
value={timeFilter}
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
formatTooltipResult={mapNumbertoTimeInSlider}
|
formatTooltipResult={mapNumbertoTimeInSlider}
|
||||||
reverse={true}
|
reverse={true}
|
||||||
@ -189,7 +193,7 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.labelSlider}>{mapNumbertoTimeInSlider(richHistorySearchFilters.to)}</div>
|
<div className={styles.labelSlider}>{mapNumbertoTimeInSlider(timeFilter[1])}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import { createAsyncThunk, ThunkResult } from 'app/types';
|
|||||||
import { ExploreItemState } from 'app/types/explore';
|
import { ExploreItemState } from 'app/types/explore';
|
||||||
|
|
||||||
import { datasourceReducer } from './datasource';
|
import { datasourceReducer } from './datasource';
|
||||||
import { historyReducer } from './history';
|
|
||||||
import { richHistorySearchFiltersUpdatedAction, richHistoryUpdatedAction } from './main';
|
import { richHistorySearchFiltersUpdatedAction, richHistoryUpdatedAction } from './main';
|
||||||
import { queryReducer, runQueries } from './query';
|
import { queryReducer, runQueries } from './query';
|
||||||
import { timeReducer, updateTime } from './time';
|
import { timeReducer, updateTime } from './time';
|
||||||
@ -214,7 +213,6 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
|
|||||||
state = queryReducer(state, action);
|
state = queryReducer(state, action);
|
||||||
state = datasourceReducer(state, action);
|
state = datasourceReducer(state, action);
|
||||||
state = timeReducer(state, action);
|
state = timeReducer(state, action);
|
||||||
state = historyReducer(state, action);
|
|
||||||
|
|
||||||
if (richHistoryUpdatedAction.match(action)) {
|
if (richHistoryUpdatedAction.match(action)) {
|
||||||
const { richHistory, total } = action.payload.richHistoryResults;
|
const { richHistory, total } = action.payload.richHistoryResults;
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import { AnyAction, createAction } from '@reduxjs/toolkit';
|
|
||||||
|
|
||||||
import { HistoryItem } from '@grafana/data';
|
|
||||||
import { DataQuery } from '@grafana/schema';
|
import { DataQuery } from '@grafana/schema';
|
||||||
import {
|
import {
|
||||||
addToRichHistory,
|
addToRichHistory,
|
||||||
@ -26,16 +23,6 @@ import {
|
|||||||
} from './main';
|
} from './main';
|
||||||
import { selectPanesEntries } from './selectors';
|
import { selectPanesEntries } from './selectors';
|
||||||
|
|
||||||
//
|
|
||||||
// Actions and Payloads
|
|
||||||
//
|
|
||||||
|
|
||||||
export interface HistoryUpdatedPayload {
|
|
||||||
exploreId: string;
|
|
||||||
history: HistoryItem[];
|
|
||||||
}
|
|
||||||
export const historyUpdatedAction = createAction<HistoryUpdatedPayload>('explore/historyUpdated');
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action creators
|
// Action creators
|
||||||
//
|
//
|
||||||
@ -74,25 +61,33 @@ const forEachExplorePane = (state: ExploreState, callback: (item: ExploreItemSta
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const addHistoryItem = (
|
export const addHistoryItem = (
|
||||||
|
localOverride: boolean,
|
||||||
datasourceUid: string,
|
datasourceUid: string,
|
||||||
datasourceName: string,
|
datasourceName: string,
|
||||||
queries: DataQuery[]
|
queries: DataQuery[],
|
||||||
|
hideAllErrorsAndWarnings: boolean
|
||||||
): ThunkResult<void> => {
|
): ThunkResult<void> => {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const { richHistoryStorageFull, limitExceeded } = await addToRichHistory(
|
const showNotif = hideAllErrorsAndWarnings
|
||||||
datasourceUid,
|
? { quotaExceededError: false, limitExceededWarning: false, otherErrors: false }
|
||||||
datasourceName,
|
: {
|
||||||
|
quotaExceededError: !getState().explore.richHistoryStorageFull,
|
||||||
|
limitExceededWarning: !getState().explore.richHistoryLimitExceededWarningShown,
|
||||||
|
};
|
||||||
|
const { richHistoryStorageFull, limitExceeded } = await addToRichHistory({
|
||||||
|
localOverride,
|
||||||
|
datasource: { uid: datasourceUid, name: datasourceName },
|
||||||
queries,
|
queries,
|
||||||
false,
|
starred: false,
|
||||||
'',
|
showNotif,
|
||||||
!getState().explore.richHistoryStorageFull,
|
});
|
||||||
!getState().explore.richHistoryLimitExceededWarningShown
|
if (!hideAllErrorsAndWarnings) {
|
||||||
);
|
if (richHistoryStorageFull) {
|
||||||
if (richHistoryStorageFull) {
|
dispatch(richHistoryStorageFullAction());
|
||||||
dispatch(richHistoryStorageFullAction());
|
}
|
||||||
}
|
if (limitExceeded) {
|
||||||
if (limitExceeded) {
|
dispatch(richHistoryLimitExceededAction());
|
||||||
dispatch(richHistoryLimitExceededAction());
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -199,13 +194,3 @@ export const updateHistorySearchFilters = (exploreId: string, filters: RichHisto
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const historyReducer = (state: ExploreItemState, action: AnyAction): ExploreItemState => {
|
|
||||||
if (historyUpdatedAction.match(action)) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
history: action.payload.history,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
|
@ -16,10 +16,12 @@ import {
|
|||||||
SupplementaryQueryType,
|
SupplementaryQueryType,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { DataQuery, DataSourceRef } from '@grafana/schema';
|
import { DataQuery, DataSourceRef } from '@grafana/schema';
|
||||||
|
import config from 'app/core/config';
|
||||||
import { queryLogsSample, queryLogsVolume } from 'app/features/logs/logsModel';
|
import { queryLogsSample, queryLogsVolume } from 'app/features/logs/logsModel';
|
||||||
import { createAsyncThunk, ExploreItemState, StoreState, ThunkDispatch } from 'app/types';
|
import { createAsyncThunk, ExploreItemState, StoreState, ThunkDispatch } from 'app/types';
|
||||||
|
|
||||||
import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
||||||
|
import * as richHistory from '../../../core/utils/richHistory';
|
||||||
import { configureStore } from '../../../store/configureStore';
|
import { configureStore } from '../../../store/configureStore';
|
||||||
import { setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv';
|
import { setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv';
|
||||||
import { makeLogs } from '../__mocks__/makeLogs';
|
import { makeLogs } from '../__mocks__/makeLogs';
|
||||||
@ -155,6 +157,11 @@ describe('runQueries', () => {
|
|||||||
} as unknown as Partial<StoreState>);
|
} as unknown as Partial<StoreState>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config.queryHistoryEnabled = false;
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should pass dataFrames to state even if there is error in response', async () => {
|
it('should pass dataFrames to state even if there is error in response', async () => {
|
||||||
const { dispatch, getState } = setupTests();
|
const { dispatch, getState } = setupTests();
|
||||||
setupQueryResponse(getState());
|
setupQueryResponse(getState());
|
||||||
@ -202,6 +209,24 @@ describe('runQueries', () => {
|
|||||||
await dispatch(saveCorrelationsAction({ exploreId: 'left', correlations: [] }));
|
await dispatch(saveCorrelationsAction({ exploreId: 'left', correlations: [] }));
|
||||||
expect(getState().explore.panes.left!.graphResult).toBeDefined();
|
expect(getState().explore.panes.left!.graphResult).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add history items to both local and remote storage with the flag enabled', async () => {
|
||||||
|
config.queryHistoryEnabled = true;
|
||||||
|
const { dispatch } = setupTests();
|
||||||
|
jest.spyOn(richHistory, 'addToRichHistory');
|
||||||
|
await dispatch(runQueries({ exploreId: 'left' }));
|
||||||
|
expect((richHistory.addToRichHistory as jest.Mock).mock.calls).toHaveLength(2);
|
||||||
|
expect((richHistory.addToRichHistory as jest.Mock).mock.calls[0][0].localOverride).toBeTruthy();
|
||||||
|
expect((richHistory.addToRichHistory as jest.Mock).mock.calls[1][0].localOverride).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add history items to local storage only with the flag disabled', async () => {
|
||||||
|
const { dispatch } = setupTests();
|
||||||
|
jest.spyOn(richHistory, 'addToRichHistory');
|
||||||
|
await dispatch(runQueries({ exploreId: 'left' }));
|
||||||
|
expect((richHistory.addToRichHistory as jest.Mock).mock.calls).toHaveLength(1);
|
||||||
|
expect((richHistory.addToRichHistory as jest.Mock).mock.calls[0][0].localOverride).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('running queries', () => {
|
describe('running queries', () => {
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
dateTimeForTimeZone,
|
dateTimeForTimeZone,
|
||||||
hasQueryExportSupport,
|
hasQueryExportSupport,
|
||||||
hasQueryImportSupport,
|
hasQueryImportSupport,
|
||||||
HistoryItem,
|
|
||||||
LoadingState,
|
LoadingState,
|
||||||
LogsVolumeType,
|
LogsVolumeType,
|
||||||
PanelEvents,
|
PanelEvents,
|
||||||
@ -35,7 +34,6 @@ import {
|
|||||||
getTimeRange,
|
getTimeRange,
|
||||||
hasNonEmptyQuery,
|
hasNonEmptyQuery,
|
||||||
stopQueryState,
|
stopQueryState,
|
||||||
updateHistory,
|
|
||||||
} from 'app/core/utils/explore';
|
} from 'app/core/utils/explore';
|
||||||
import { getShiftedTimeRange } from 'app/core/utils/timePicker';
|
import { getShiftedTimeRange } from 'app/core/utils/timePicker';
|
||||||
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
|
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
|
||||||
@ -67,7 +65,7 @@ import {
|
|||||||
|
|
||||||
import { getCorrelations } from './correlations';
|
import { getCorrelations } from './correlations';
|
||||||
import { saveCorrelationsAction } from './explorePane';
|
import { saveCorrelationsAction } from './explorePane';
|
||||||
import { addHistoryItem, historyUpdatedAction, loadRichHistory } from './history';
|
import { addHistoryItem, loadRichHistory } from './history';
|
||||||
import { changeCorrelationEditorDetails } from './main';
|
import { changeCorrelationEditorDetails } from './main';
|
||||||
import { updateTime } from './time';
|
import { updateTime } from './time';
|
||||||
import {
|
import {
|
||||||
@ -481,16 +479,18 @@ export function modifyQueries(
|
|||||||
async function handleHistory(
|
async function handleHistory(
|
||||||
dispatch: ThunkDispatch,
|
dispatch: ThunkDispatch,
|
||||||
state: ExploreState,
|
state: ExploreState,
|
||||||
history: Array<HistoryItem<DataQuery>>,
|
|
||||||
datasource: DataSourceApi,
|
datasource: DataSourceApi,
|
||||||
queries: DataQuery[],
|
queries: DataQuery[]
|
||||||
exploreId: string
|
|
||||||
) {
|
) {
|
||||||
const datasourceId = datasource.meta.id;
|
/*
|
||||||
const nextHistory = updateHistory(history, datasourceId, queries);
|
Always write to local storage. If query history is enabled, we will use local storage for autocomplete only (and want to hide errors)
|
||||||
dispatch(historyUpdatedAction({ exploreId, history: nextHistory }));
|
If query history is disabled, we will use local storage for query history as well, and will want to show errors
|
||||||
|
*/
|
||||||
dispatch(addHistoryItem(datasource.uid, datasource.name, queries));
|
dispatch(addHistoryItem(true, datasource.uid, datasource.name, queries, config.queryHistoryEnabled));
|
||||||
|
if (config.queryHistoryEnabled) {
|
||||||
|
// write to remote if flag enabled
|
||||||
|
dispatch(addHistoryItem(false, datasource.uid, datasource.name, queries, false));
|
||||||
|
}
|
||||||
|
|
||||||
// Because filtering happens in the backend we cannot add a new entry without checking if it matches currently
|
// Because filtering happens in the backend we cannot add a new entry without checking if it matches currently
|
||||||
// used filters. Instead, we refresh the query history list.
|
// used filters. Instead, we refresh the query history list.
|
||||||
@ -550,7 +550,7 @@ export const runQueries = createAsyncThunk<void, RunQueriesOptions>(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (datasourceInstance != null) {
|
if (datasourceInstance != null) {
|
||||||
handleHistory(dispatch, getState().explore, exploreItemState.history, datasourceInstance, queries, exploreId);
|
handleHistory(dispatch, getState().explore, datasourceInstance, queries);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedValue = getResultsFromCache(cache, absoluteRange);
|
const cachedValue = getResultsFromCache(cache, absoluteRange);
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
import { getLocalRichHistoryStorage } from 'app/core/history/richHistoryStorageProvider';
|
||||||
import * as exploreUtils from 'app/core/utils/explore';
|
import * as exploreUtils from 'app/core/utils/explore';
|
||||||
|
|
||||||
|
import { loadAndInitDatasource, getRange, fromURLRange, MAX_HISTORY_AUTOCOMPLETE_ITEMS } from './utils';
|
||||||
|
|
||||||
const dataSourceMock = {
|
const dataSourceMock = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
};
|
};
|
||||||
@ -8,7 +11,15 @@ jest.mock('app/features/plugins/datasource_srv', () => ({
|
|||||||
getDatasourceSrv: jest.fn(() => dataSourceMock),
|
getDatasourceSrv: jest.fn(() => dataSourceMock),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { loadAndInitDatasource, getRange, fromURLRange } from './utils';
|
const mockLocalDataStorage = {
|
||||||
|
getRichHistory: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('app/core/history/richHistoryStorageProvider', () => ({
|
||||||
|
getLocalRichHistoryStorage: jest.fn(() => {
|
||||||
|
return mockLocalDataStorage;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
const DEFAULT_DATASOURCE = { uid: 'abc123', name: 'Default' };
|
const DEFAULT_DATASOURCE = { uid: 'abc123', name: 'Default' };
|
||||||
const TEST_DATASOURCE = { uid: 'def789', name: 'Test' };
|
const TEST_DATASOURCE = { uid: 'def789', name: 'Test' };
|
||||||
@ -28,6 +39,7 @@ describe('loadAndInitDatasource', () => {
|
|||||||
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
||||||
dataSourceMock.get.mockRejectedValueOnce(new Error('Datasource not found'));
|
dataSourceMock.get.mockRejectedValueOnce(new Error('Datasource not found'));
|
||||||
dataSourceMock.get.mockResolvedValue(DEFAULT_DATASOURCE);
|
dataSourceMock.get.mockResolvedValue(DEFAULT_DATASOURCE);
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValue({ total: 0, richHistory: [] });
|
||||||
|
|
||||||
const { instance } = await loadAndInitDatasource(1, { uid: 'Unknown' });
|
const { instance } = await loadAndInitDatasource(1, { uid: 'Unknown' });
|
||||||
|
|
||||||
@ -41,14 +53,111 @@ describe('loadAndInitDatasource', () => {
|
|||||||
it('saves last loaded data source uid', async () => {
|
it('saves last loaded data source uid', async () => {
|
||||||
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
||||||
dataSourceMock.get.mockResolvedValue(TEST_DATASOURCE);
|
dataSourceMock.get.mockResolvedValue(TEST_DATASOURCE);
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValue({
|
||||||
|
total: 0,
|
||||||
|
richHistory: [],
|
||||||
|
});
|
||||||
|
|
||||||
const { instance } = await loadAndInitDatasource(1, { uid: 'Test' });
|
const { instance } = await loadAndInitDatasource(1, { uid: 'Test' });
|
||||||
|
|
||||||
expect(dataSourceMock.get).toBeCalledTimes(1);
|
expect(dataSourceMock.get).toHaveBeenCalledTimes(1);
|
||||||
expect(dataSourceMock.get).toBeCalledWith({ uid: 'Test' });
|
expect(dataSourceMock.get).toHaveBeenCalledWith({ uid: 'Test' });
|
||||||
|
expect(getLocalRichHistoryStorage).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(instance).toMatchObject(TEST_DATASOURCE);
|
expect(instance).toMatchObject(TEST_DATASOURCE);
|
||||||
expect(setLastUsedDatasourceUIDSpy).toBeCalledWith(1, TEST_DATASOURCE.uid);
|
expect(setLastUsedDatasourceUIDSpy).toBeCalledWith(1, TEST_DATASOURCE.uid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('pulls history data and returns the history by query', async () => {
|
||||||
|
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
||||||
|
dataSourceMock.get.mockResolvedValue(TEST_DATASOURCE);
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValueOnce({
|
||||||
|
total: 1,
|
||||||
|
richHistory: [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
createdAt: 0,
|
||||||
|
datasourceUid: 'Test',
|
||||||
|
datasourceName: 'Test',
|
||||||
|
starred: false,
|
||||||
|
comment: '',
|
||||||
|
queries: [{ refId: 'A' }, { refId: 'B' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { history } = await loadAndInitDatasource(1, { uid: 'Test' });
|
||||||
|
expect(getLocalRichHistoryStorage).toHaveBeenCalledTimes(1);
|
||||||
|
expect(history.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pulls history data and returns the history by query with Mixed results', async () => {
|
||||||
|
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
||||||
|
dataSourceMock.get.mockResolvedValue(TEST_DATASOURCE);
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValueOnce({
|
||||||
|
total: 1,
|
||||||
|
richHistory: [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
createdAt: 0,
|
||||||
|
datasourceUid: 'Test',
|
||||||
|
datasourceName: 'Test',
|
||||||
|
starred: false,
|
||||||
|
comment: '',
|
||||||
|
queries: [{ refId: 'A' }, { refId: 'B' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValueOnce({
|
||||||
|
total: 1,
|
||||||
|
richHistory: [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
createdAt: 0,
|
||||||
|
datasourceUid: 'Mixed',
|
||||||
|
datasourceName: 'Mixed',
|
||||||
|
starred: false,
|
||||||
|
comment: '',
|
||||||
|
queries: [
|
||||||
|
{ refId: 'A', datasource: { uid: 'def789' } },
|
||||||
|
{ refId: 'B', datasource: { uid: 'def789' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { history } = await loadAndInitDatasource(1, { uid: 'Test' });
|
||||||
|
expect(getLocalRichHistoryStorage).toHaveBeenCalledTimes(1);
|
||||||
|
expect(history.length).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pulls history data and returns only a max of MAX_HISTORY_AUTOCOMPLETE_ITEMS items', async () => {
|
||||||
|
const queryList = [...Array(MAX_HISTORY_AUTOCOMPLETE_ITEMS + 50).keys()].map((i) => {
|
||||||
|
return { refId: `ref-${i}` };
|
||||||
|
});
|
||||||
|
|
||||||
|
setLastUsedDatasourceUIDSpy = jest.spyOn(exploreUtils, 'setLastUsedDatasourceUID');
|
||||||
|
dataSourceMock.get.mockResolvedValue(TEST_DATASOURCE);
|
||||||
|
mockLocalDataStorage.getRichHistory.mockResolvedValueOnce({
|
||||||
|
total: 1,
|
||||||
|
richHistory: [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
createdAt: 0,
|
||||||
|
datasourceUid: 'Test',
|
||||||
|
datasourceName: 'Test',
|
||||||
|
starred: false,
|
||||||
|
comment: '',
|
||||||
|
queries: queryList,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { history } = await loadAndInitDatasource(1, { uid: 'Test' });
|
||||||
|
expect(getLocalRichHistoryStorage).toHaveBeenCalledTimes(1);
|
||||||
|
expect(history.length).toEqual(MAX_HISTORY_AUTOCOMPLETE_ITEMS);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getRange', () => {
|
describe('getRange', () => {
|
||||||
|
@ -21,16 +21,20 @@ import {
|
|||||||
URLRangeValue,
|
URLRangeValue,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { DataQuery, DataSourceRef, TimeZone } from '@grafana/schema';
|
import { DataQuery, DataSourceJsonData, DataSourceRef, TimeZone } from '@grafana/schema';
|
||||||
|
import { getLocalRichHistoryStorage } from 'app/core/history/richHistoryStorageProvider';
|
||||||
|
import { SortOrder } from 'app/core/utils/richHistory';
|
||||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||||
import { ExplorePanelData, StoreState } from 'app/types';
|
import { ExplorePanelData, StoreState } from 'app/types';
|
||||||
import { ExploreItemState } from 'app/types/explore';
|
import { ExploreItemState, RichHistoryQuery } from 'app/types/explore';
|
||||||
|
|
||||||
import store from '../../../core/store';
|
import store from '../../../core/store';
|
||||||
import { setLastUsedDatasourceUID } from '../../../core/utils/explore';
|
import { setLastUsedDatasourceUID } from '../../../core/utils/explore';
|
||||||
import { getDatasourceSrv } from '../../plugins/datasource_srv';
|
import { getDatasourceSrv } from '../../plugins/datasource_srv';
|
||||||
import { loadSupplementaryQueries } from '../utils/supplementaryQueries';
|
import { loadSupplementaryQueries } from '../utils/supplementaryQueries';
|
||||||
|
|
||||||
|
export const MAX_HISTORY_AUTOCOMPLETE_ITEMS = 100;
|
||||||
|
|
||||||
export const DEFAULT_RANGE = {
|
export const DEFAULT_RANGE = {
|
||||||
from: 'now-1h',
|
from: 'now-1h',
|
||||||
to: 'now',
|
to: 'now',
|
||||||
@ -100,7 +104,7 @@ export async function loadAndInitDatasource(
|
|||||||
orgId: number,
|
orgId: number,
|
||||||
datasource: DataSourceRef | string
|
datasource: DataSourceRef | string
|
||||||
): Promise<{ history: HistoryItem[]; instance: DataSourceApi }> {
|
): Promise<{ history: HistoryItem[]; instance: DataSourceApi }> {
|
||||||
let instance;
|
let instance: DataSourceApi<DataQuery, DataSourceJsonData, {}>;
|
||||||
try {
|
try {
|
||||||
// let datasource be a ref if we have the info, otherwise a name or uid will do for lookup
|
// let datasource be a ref if we have the info, otherwise a name or uid will do for lookup
|
||||||
instance = await getDatasourceSrv().get(datasource);
|
instance = await getDatasourceSrv().get(datasource);
|
||||||
@ -119,12 +123,60 @@ export async function loadAndInitDatasource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const historyKey = `grafana.explore.history.${instance.meta?.id}`;
|
let history: HistoryItem[] = [];
|
||||||
const history = store.getObject<HistoryItem[]>(historyKey, []);
|
|
||||||
// Save last-used datasource
|
|
||||||
|
|
||||||
|
const localStorageHistory = getLocalRichHistoryStorage();
|
||||||
|
|
||||||
|
const historyResults = await localStorageHistory.getRichHistory({
|
||||||
|
search: '',
|
||||||
|
sortOrder: SortOrder.Ascending,
|
||||||
|
datasourceFilters: [instance.name],
|
||||||
|
starred: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// first, fill autocomplete with query history for that datasource
|
||||||
|
if ((historyResults.total || 0) > 0) {
|
||||||
|
historyResults.richHistory.forEach((historyResult: RichHistoryQuery) => {
|
||||||
|
historyResult.queries.forEach((q) => {
|
||||||
|
history.push({ ts: parseInt(historyResult.id, 10), query: q });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (history.length < MAX_HISTORY_AUTOCOMPLETE_ITEMS) {
|
||||||
|
// check the last 100 mixed history results seperately
|
||||||
|
const historyMixedResults = await localStorageHistory.getRichHistory({
|
||||||
|
search: '',
|
||||||
|
sortOrder: SortOrder.Ascending,
|
||||||
|
datasourceFilters: [MIXED_DATASOURCE_NAME],
|
||||||
|
starred: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((historyMixedResults.total || 0) > 0) {
|
||||||
|
// second, fill autocomplete with queries for that datasource used in Mixed scenarios
|
||||||
|
historyMixedResults.richHistory.forEach((historyResult: RichHistoryQuery) => {
|
||||||
|
historyResult.queries.forEach((q) => {
|
||||||
|
if (q?.datasource?.uid === instance.uid) {
|
||||||
|
history.push({ ts: parseInt(historyResult.id, 10), query: q });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, add any legacy local storage history that might exist. To be removed in Grafana 12 #83309
|
||||||
|
if (history.length < MAX_HISTORY_AUTOCOMPLETE_ITEMS) {
|
||||||
|
const historyKey = `grafana.explore.history.${instance.meta?.id}`;
|
||||||
|
history = [...history, ...store.getObject<HistoryItem[]>(historyKey, [])];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (history.length > MAX_HISTORY_AUTOCOMPLETE_ITEMS) {
|
||||||
|
history.length = MAX_HISTORY_AUTOCOMPLETE_ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save last-used datasource
|
||||||
setLastUsedDatasourceUID(orgId, instance.uid);
|
setLastUsedDatasourceUID(orgId, instance.uid);
|
||||||
return { history, instance };
|
return { history: history, instance };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCacheKey(absRange: AbsoluteTimeRange) {
|
export function createCacheKey(absRange: AbsoluteTimeRange) {
|
||||||
|
Loading…
Reference in New Issue
Block a user