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 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();
});
});

View File

@ -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);
}

View File

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

View File

@ -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();

View File

@ -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();

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', () => {