Fix history rendering for DataQuery

This commit is contained in:
David Kaltschmidt 2018-11-22 11:02:53 +01:00
parent 331d419d4f
commit bbaa5b63c8
6 changed files with 116 additions and 14 deletions

View File

@ -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 { ExploreState } from 'app/types/explore';
import store from 'app/core/store';
const DEFAULT_EXPLORE_STATE: ExploreState = { const DEFAULT_EXPLORE_STATE: ExploreState = {
datasource: null, 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();
});
});

View File

@ -66,6 +66,8 @@ export async function getExploreUrl(
return url; return url;
} }
const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
export function parseUrlState(initial: string | undefined): ExploreUrlState { export function parseUrlState(initial: string | undefined): ExploreUrlState {
if (initial) { if (initial) {
try { try {
@ -93,7 +95,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
const urlState: ExploreUrlState = { const urlState: ExploreUrlState = {
datasource: state.datasourceName, datasource: state.datasourceName,
queries: state.initialQueries.map(({ key, refId, ...rest }) => rest), queries: state.initialQueries.map(clearQueryKeys),
range: state.range, range: state.range,
}; };
if (compact) { if (compact) {
@ -182,3 +184,8 @@ export function updateHistory(history: HistoryItem[], datasourceId: string, quer
store.setObject(historyKey, history); store.setObject(historyKey, history);
return history; return history;
} }
export function clearHistory(datasourceId: string) {
const historyKey = `grafana.explore.history.${datasourceId}`;
store.delete(historyKey);
}

View File

@ -7,12 +7,37 @@ describe('Language completion provider', () => {
metadataRequest: () => ({ data: { data: [] } }), metadataRequest: () => ({ data: { data: [] } }),
}; };
it('returns default suggestions on emtpty context', () => { describe('empty query suggestions', () => {
const instance = new LanguageProvider(datasource); it('returns default suggestions on emtpty context', () => {
const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] }); const instance = new LanguageProvider(datasource);
expect(result.context).toBeUndefined(); const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] });
expect(result.refresher).toBeUndefined(); expect(result.context).toBeUndefined();
expect(result.suggestions.length).toEqual(0); 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', () => { describe('label suggestions', () => {

View File

@ -7,6 +7,7 @@ import {
LanguageProvider, LanguageProvider,
TypeaheadInput, TypeaheadInput,
TypeaheadOutput, TypeaheadOutput,
HistoryItem,
} from 'app/types/explore'; } from 'app/types/explore';
import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils';
import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; 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 }); 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 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 count = historyForItem.length;
const recent = historyForItem[0]; const recent = historyForItem[0];
let hint = `Queried ${count} times in the last 24h.`; let hint = `Queried ${count} times in the last 24h.`;
@ -96,9 +97,9 @@ export default class LoggingLanguageProvider extends LanguageProvider {
if (history && history.length > 0) { if (history && history.length > 0) {
const historyItems = _.chain(history) const historyItems = _.chain(history)
.uniqBy('query') .uniqBy('query.expr')
.take(HISTORY_ITEM_COUNT) .take(HISTORY_ITEM_COUNT)
.map(h => h.query) .map(h => h.query.expr)
.map(wrapLabel) .map(wrapLabel)
.map(item => addHistoryMetadata(item, history)) .map(item => addHistoryMetadata(item, history))
.value(); .value();

View File

@ -125,9 +125,9 @@ export default class PromQlLanguageProvider extends LanguageProvider {
if (history && history.length > 0) { if (history && history.length > 0) {
const historyItems = _.chain(history) const historyItems = _.chain(history)
.uniqBy('query') .uniqBy('query.expr')
.take(HISTORY_ITEM_COUNT) .take(HISTORY_ITEM_COUNT)
.map(h => h.query) .map(h => h.query.expr)
.map(wrapLabel) .map(wrapLabel)
.map(item => addHistoryMetadata(item, history)) .map(item => addHistoryMetadata(item, history))
.value(); .value();

View File

@ -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', () => { describe('range suggestions', () => {