From bbaa5b63c8d49479a4b08ac2780c6ce649f706c6 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Thu, 22 Nov 2018 11:02:53 +0100 Subject: [PATCH] Fix history rendering for DataQuery --- public/app/core/utils/explore.test.ts | 45 ++++++++++++++++++- public/app/core/utils/explore.ts | 9 +++- .../logging/language_provider.test.ts | 37 ++++++++++++--- .../datasource/logging/language_provider.ts | 9 ++-- .../prometheus/language_provider.ts | 4 +- .../specs/language_provider.test.ts | 26 +++++++++++ 6 files changed, 116 insertions(+), 14 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index e12f7782a12..37d3d3bfac9 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -1,5 +1,13 @@ -import { DEFAULT_RANGE, serializeStateToUrlParam, parseUrlState } from './explore'; +import { + DEFAULT_RANGE, + serializeStateToUrlParam, + parseUrlState, + updateHistory, + clearHistory, + hasNonEmptyQuery, +} from './explore'; import { ExploreState } from 'app/types/explore'; +import store from 'app/core/store'; const DEFAULT_EXPLORE_STATE: ExploreState = { datasource: null, @@ -144,3 +152,38 @@ describe('state functions', () => { }); }); }); + +describe('updateHistory()', () => { + const datasourceId = 'myDatasource'; + const key = `grafana.explore.history.${datasourceId}`; + + beforeEach(() => { + clearHistory(datasourceId); + 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', () => { + test('should return true if one query is non-empty', () => { + expect(hasNonEmptyQuery([{ refId: '1', key: '2', expr: 'foo' }])).toBeTruthy(); + }); + + test('should return false if query is empty', () => { + expect(hasNonEmptyQuery([{ refId: '1', key: '2' }])).toBeFalsy(); + }); + + test('should return false if no queries exist', () => { + expect(hasNonEmptyQuery([])).toBeFalsy(); + }); +}); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index f0d5c6f83d5..9ecc36a192f 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -66,6 +66,8 @@ export async function getExploreUrl( return url; } +const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest; + export function parseUrlState(initial: string | undefined): ExploreUrlState { if (initial) { try { @@ -93,7 +95,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { const urlState: ExploreUrlState = { datasource: state.datasourceName, - queries: state.initialQueries.map(({ key, refId, ...rest }) => rest), + queries: state.initialQueries.map(clearQueryKeys), range: state.range, }; if (compact) { @@ -182,3 +184,8 @@ export function updateHistory(history: HistoryItem[], datasourceId: string, quer store.setObject(historyKey, history); return history; } + +export function clearHistory(datasourceId: string) { + const historyKey = `grafana.explore.history.${datasourceId}`; + store.delete(historyKey); +} diff --git a/public/app/plugins/datasource/logging/language_provider.test.ts b/public/app/plugins/datasource/logging/language_provider.test.ts index e0844cf0c7a..79f696843bb 100644 --- a/public/app/plugins/datasource/logging/language_provider.test.ts +++ b/public/app/plugins/datasource/logging/language_provider.test.ts @@ -7,12 +7,37 @@ describe('Language completion provider', () => { metadataRequest: () => ({ data: { data: [] } }), }; - it('returns default suggestions on emtpty context', () => { - const instance = new LanguageProvider(datasource); - const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] }); - expect(result.context).toBeUndefined(); - expect(result.refresher).toBeUndefined(); - expect(result.suggestions.length).toEqual(0); + describe('empty query suggestions', () => { + it('returns default suggestions on emtpty context', () => { + const instance = new LanguageProvider(datasource); + const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] }); + expect(result.context).toBeUndefined(); + expect(result.refresher).toBeUndefined(); + expect(result.suggestions.length).toEqual(0); + }); + + it('returns default suggestions with history on emtpty context when history was provided', () => { + const instance = new LanguageProvider(datasource); + const value = Plain.deserialize(''); + const history = [ + { + query: { refId: '1', expr: '{app="foo"}' }, + }, + ]; + const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }, { history }); + expect(result.context).toBeUndefined(); + expect(result.refresher).toBeUndefined(); + expect(result.suggestions).toMatchObject([ + { + label: 'History', + items: [ + { + label: '{app="foo"}', + }, + ], + }, + ]); + }); }); describe('label suggestions', () => { diff --git a/public/app/plugins/datasource/logging/language_provider.ts b/public/app/plugins/datasource/logging/language_provider.ts index 253e2ba097f..eb47b3b1e27 100644 --- a/public/app/plugins/datasource/logging/language_provider.ts +++ b/public/app/plugins/datasource/logging/language_provider.ts @@ -7,6 +7,7 @@ import { LanguageProvider, TypeaheadInput, TypeaheadOutput, + HistoryItem, } from 'app/types/explore'; import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; @@ -19,9 +20,9 @@ const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h const wrapLabel = (label: string) => ({ label }); -export function addHistoryMetadata(item: CompletionItem, history: any[]): CompletionItem { +export function addHistoryMetadata(item: CompletionItem, history: HistoryItem[]): CompletionItem { const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF; - const historyForItem = history.filter(h => h.ts > cutoffTs && h.query === item.label); + const historyForItem = history.filter(h => h.ts > cutoffTs && (h.query.expr as string) === item.label); const count = historyForItem.length; const recent = historyForItem[0]; let hint = `Queried ${count} times in the last 24h.`; @@ -96,9 +97,9 @@ export default class LoggingLanguageProvider extends LanguageProvider { if (history && history.length > 0) { const historyItems = _.chain(history) - .uniqBy('query') + .uniqBy('query.expr') .take(HISTORY_ITEM_COUNT) - .map(h => h.query) + .map(h => h.query.expr) .map(wrapLabel) .map(item => addHistoryMetadata(item, history)) .value(); diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 6e6f461d341..5fd8fcebaaf 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -125,9 +125,9 @@ export default class PromQlLanguageProvider extends LanguageProvider { if (history && history.length > 0) { const historyItems = _.chain(history) - .uniqBy('query') + .uniqBy('query.expr') .take(HISTORY_ITEM_COUNT) - .map(h => h.query) + .map(h => h.query.expr) .map(wrapLabel) .map(item => addHistoryMetadata(item, history)) .value(); diff --git a/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts b/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts index bcb8cb34082..d3eb6de3087 100644 --- a/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts @@ -36,6 +36,32 @@ describe('Language completion provider', () => { }, ]); }); + + it('returns default suggestions with history on emtpty context when history was provided', () => { + const instance = new LanguageProvider(datasource); + const value = Plain.deserialize(''); + const history = [ + { + query: { refId: '1', expr: 'metric' }, + }, + ]; + const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }, { history }); + expect(result.context).toBeUndefined(); + expect(result.refresher).toBeUndefined(); + expect(result.suggestions).toMatchObject([ + { + label: 'History', + items: [ + { + label: 'metric', + }, + ], + }, + { + label: 'Functions', + }, + ]); + }); }); describe('range suggestions', () => {