| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- import {
- DEFAULT_RANGE,
- serializeStateToUrlParam,
- parseUrlState,
- updateHistory,
- clearHistory,
- hasNonEmptyQuery,
- getValueWithRefId,
- getFirstQueryErrorWithoutRefId,
- getRefIds,
- refreshIntervalToSortOrder,
- SortOrder,
- sortLogsResult,
- } from './explore';
- import { ExploreUrlState, ExploreMode } from 'app/types/explore';
- import store from 'app/core/store';
- import { LogsDedupStrategy, LogsModel, LogLevel } from '@grafana/data';
- import { DataQueryError } from '@grafana/ui';
- import { liveOption, offOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
- const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
- datasource: null,
- queries: [],
- range: DEFAULT_RANGE,
- mode: ExploreMode.Metrics,
- ui: {
- showingGraph: true,
- showingTable: true,
- showingLogs: true,
- dedupStrategy: LogsDedupStrategy.none,
- },
- originPanelId: undefined,
- };
- describe('state functions', () => {
- describe('parseUrlState', () => {
- it('returns default state on empty string', () => {
- expect(parseUrlState('')).toMatchObject({
- datasource: null,
- queries: [],
- range: DEFAULT_RANGE,
- });
- });
- it('returns a valid Explore state from URL parameter', () => {
- const paramValue =
- '%7B"datasource":"Local","queries":%5B%7B"expr":"metric"%7D%5D,"range":%7B"from":"now-1h","to":"now"%7D%7D';
- expect(parseUrlState(paramValue)).toMatchObject({
- datasource: 'Local',
- queries: [{ expr: 'metric' }],
- range: {
- from: 'now-1h',
- to: 'now',
- },
- });
- });
- it('returns a valid Explore state from a compact URL parameter', () => {
- const paramValue = '%5B"now-1h","now","Local","5m",%7B"expr":"metric"%7D,"ui"%5D';
- expect(parseUrlState(paramValue)).toMatchObject({
- datasource: 'Local',
- queries: [{ expr: 'metric' }],
- range: {
- from: 'now-1h',
- to: 'now',
- },
- });
- });
- });
- describe('serializeStateToUrlParam', () => {
- it('returns url parameter value for a state object', () => {
- const state = {
- ...DEFAULT_EXPLORE_STATE,
- datasource: 'foo',
- queries: [
- {
- expr: 'metric{test="a/b"}',
- },
- {
- expr: 'super{foo="x/z"}',
- },
- ],
- range: {
- from: 'now-5h',
- to: 'now',
- },
- };
- expect(serializeStateToUrlParam(state)).toBe(
- '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
- '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
- '"mode":"Metrics",' +
- '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}'
- );
- });
- it('returns url parameter value for a state object', () => {
- const state = {
- ...DEFAULT_EXPLORE_STATE,
- datasource: 'foo',
- queries: [
- {
- expr: 'metric{test="a/b"}',
- },
- {
- expr: 'super{foo="x/z"}',
- },
- ],
- range: {
- from: 'now-5h',
- to: 'now',
- },
- };
- expect(serializeStateToUrlParam(state, true)).toBe(
- '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]'
- );
- });
- });
- describe('interplay', () => {
- it('can parse the serialized state into the original state', () => {
- const state = {
- ...DEFAULT_EXPLORE_STATE,
- datasource: 'foo',
- queries: [
- {
- expr: 'metric{test="a/b"}',
- },
- {
- expr: 'super{foo="x/z"}',
- },
- ],
- range: {
- from: 'now - 5h',
- to: 'now',
- },
- };
- const serialized = serializeStateToUrlParam(state);
- const parsed = parseUrlState(serialized);
- expect(state).toMatchObject(parsed);
- });
- it('can parse the compact serialized state into the original state', () => {
- const state = {
- ...DEFAULT_EXPLORE_STATE,
- datasource: 'foo',
- queries: [
- {
- expr: 'metric{test="a/b"}',
- },
- {
- expr: 'super{foo="x/z"}',
- },
- ],
- range: {
- from: 'now - 5h',
- to: 'now',
- },
- };
- const serialized = serializeStateToUrlParam(state, true);
- const parsed = parseUrlState(serialized);
- expect(state).toMatchObject(parsed);
- });
- });
- });
- 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', context: 'explore', expr: 'foo' }])).toBeTruthy();
- });
- test('should return false if query is empty', () => {
- expect(hasNonEmptyQuery([{ refId: '1', key: '2', context: 'panel' }])).toBeFalsy();
- });
- test('should return false if no queries exist', () => {
- expect(hasNonEmptyQuery([])).toBeFalsy();
- });
- });
- describe('hasRefId', () => {
- describe('when called with a null value', () => {
- it('then it should return null', () => {
- const input: any = null;
- const result = getValueWithRefId(input);
- expect(result).toBeNull();
- });
- });
- describe('when called with a non object value', () => {
- it('then it should return null', () => {
- const input = 123;
- const result = getValueWithRefId(input);
- expect(result).toBeNull();
- });
- });
- describe('when called with an object that has refId', () => {
- it('then it should return the object', () => {
- const input = { refId: 'A' };
- const result = getValueWithRefId(input);
- expect(result).toBe(input);
- });
- });
- describe('when called with an array that has refId', () => {
- it('then it should return the object', () => {
- const input = [123, null, {}, { refId: 'A' }];
- const result = getValueWithRefId(input);
- expect(result).toBe(input[3]);
- });
- });
- describe('when called with an object that has refId somewhere in the object tree', () => {
- it('then it should return the object', () => {
- const input: any = { data: [123, null, {}, { series: [123, null, {}, { refId: 'A' }] }] };
- const result = getValueWithRefId(input);
- expect(result).toBe(input.data[3].series[3]);
- });
- });
- });
- describe('getFirstQueryErrorWithoutRefId', () => {
- describe('when called with a null value', () => {
- it('then it should return null', () => {
- const errors: DataQueryError[] = null;
- const result = getFirstQueryErrorWithoutRefId(errors);
- expect(result).toBeNull();
- });
- });
- describe('when called with an array with only refIds', () => {
- it('then it should return undefined', () => {
- const errors: DataQueryError[] = [{ refId: 'A' }, { refId: 'B' }];
- const result = getFirstQueryErrorWithoutRefId(errors);
- expect(result).toBeUndefined();
- });
- });
- describe('when called with an array with and without refIds', () => {
- it('then it should return undefined', () => {
- const errors: DataQueryError[] = [
- { refId: 'A' },
- { message: 'A message' },
- { refId: 'B' },
- { message: 'B message' },
- ];
- const result = getFirstQueryErrorWithoutRefId(errors);
- expect(result).toBe(errors[1]);
- });
- });
- });
- describe('getRefIds', () => {
- describe('when called with a null value', () => {
- it('then it should return empty array', () => {
- const input: any = null;
- const result = getRefIds(input);
- expect(result).toEqual([]);
- });
- });
- describe('when called with a non object value', () => {
- it('then it should return empty array', () => {
- const input = 123;
- const result = getRefIds(input);
- expect(result).toEqual([]);
- });
- });
- describe('when called with an object that has refId', () => {
- it('then it should return an array with that refId', () => {
- const input = { refId: 'A' };
- const result = getRefIds(input);
- expect(result).toEqual(['A']);
- });
- });
- describe('when called with an array that has refIds', () => {
- it('then it should return an array with unique refIds', () => {
- const input = [123, null, {}, { refId: 'A' }, { refId: 'A' }, { refId: 'B' }];
- const result = getRefIds(input);
- expect(result).toEqual(['A', 'B']);
- });
- });
- describe('when called with an object that has refIds somewhere in the object tree', () => {
- it('then it should return return an array with unique refIds', () => {
- const input: any = {
- data: [
- 123,
- null,
- { refId: 'B', series: [{ refId: 'X' }] },
- { refId: 'B' },
- {},
- { series: [123, null, {}, { refId: 'A' }] },
- ],
- };
- const result = getRefIds(input);
- expect(result).toEqual(['B', 'X', 'A']);
- });
- });
- });
- describe('refreshIntervalToSortOrder', () => {
- describe('when called with live option', () => {
- it('then it should return ascending', () => {
- const result = refreshIntervalToSortOrder(liveOption.value);
- expect(result).toBe(SortOrder.Ascending);
- });
- });
- describe('when called with off option', () => {
- it('then it should return descending', () => {
- const result = refreshIntervalToSortOrder(offOption.value);
- expect(result).toBe(SortOrder.Descending);
- });
- });
- describe('when called with 5s option', () => {
- it('then it should return descending', () => {
- const result = refreshIntervalToSortOrder('5s');
- expect(result).toBe(SortOrder.Descending);
- });
- });
- describe('when called with undefined', () => {
- it('then it should return descending', () => {
- const result = refreshIntervalToSortOrder(undefined);
- expect(result).toBe(SortOrder.Descending);
- });
- });
- });
- describe('sortLogsResult', () => {
- const firstRow = {
- timestamp: '2019-01-01T21:00:0.0000000Z',
- entry: '',
- hasAnsi: false,
- labels: {},
- logLevel: LogLevel.info,
- raw: '',
- timeEpochMs: 0,
- timeFromNow: '',
- timeLocal: '',
- timeUtc: '',
- };
- const sameAsFirstRow = firstRow;
- const secondRow = {
- timestamp: '2019-01-01T22:00:0.0000000Z',
- entry: '',
- hasAnsi: false,
- labels: {},
- logLevel: LogLevel.info,
- raw: '',
- timeEpochMs: 0,
- timeFromNow: '',
- timeLocal: '',
- timeUtc: '',
- };
- describe('when called with SortOrder.Descending', () => {
- it('then it should sort descending', () => {
- const logsResult: LogsModel = {
- rows: [firstRow, sameAsFirstRow, secondRow],
- hasUniqueLabels: false,
- };
- const result = sortLogsResult(logsResult, SortOrder.Descending);
- expect(result).toEqual({
- rows: [secondRow, firstRow, sameAsFirstRow],
- hasUniqueLabels: false,
- });
- });
- });
- describe('when called with SortOrder.Ascending', () => {
- it('then it should sort ascending', () => {
- const logsResult: LogsModel = {
- rows: [secondRow, firstRow, sameAsFirstRow],
- hasUniqueLabels: false,
- };
- const result = sortLogsResult(logsResult, SortOrder.Ascending);
- expect(result).toEqual({
- rows: [firstRow, sameAsFirstRow, secondRow],
- hasUniqueLabels: false,
- });
- });
- });
- });
|