mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Fix history rendering for DataQuery
This commit is contained in:
parent
331d419d4f
commit
bbaa5b63c8
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user