explore.test.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import {
  2. DEFAULT_RANGE,
  3. serializeStateToUrlParam,
  4. parseUrlState,
  5. updateHistory,
  6. clearHistory,
  7. hasNonEmptyQuery,
  8. instanceOfDataQueryError,
  9. getValueWithRefId,
  10. getFirstQueryErrorWithoutRefId,
  11. getRefIds,
  12. } from './explore';
  13. import { ExploreUrlState } from 'app/types/explore';
  14. import store from 'app/core/store';
  15. import { DataQueryError, LogsDedupStrategy } from '@grafana/ui';
  16. const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
  17. datasource: null,
  18. queries: [],
  19. range: DEFAULT_RANGE,
  20. ui: {
  21. showingGraph: true,
  22. showingTable: true,
  23. showingLogs: true,
  24. dedupStrategy: LogsDedupStrategy.none,
  25. },
  26. };
  27. describe('state functions', () => {
  28. describe('parseUrlState', () => {
  29. it('returns default state on empty string', () => {
  30. expect(parseUrlState('')).toMatchObject({
  31. datasource: null,
  32. queries: [],
  33. range: DEFAULT_RANGE,
  34. });
  35. });
  36. it('returns a valid Explore state from URL parameter', () => {
  37. const paramValue =
  38. '%7B"datasource":"Local","queries":%5B%7B"expr":"metric"%7D%5D,"range":%7B"from":"now-1h","to":"now"%7D%7D';
  39. expect(parseUrlState(paramValue)).toMatchObject({
  40. datasource: 'Local',
  41. queries: [{ expr: 'metric' }],
  42. range: {
  43. from: 'now-1h',
  44. to: 'now',
  45. },
  46. });
  47. });
  48. it('returns a valid Explore state from a compact URL parameter', () => {
  49. const paramValue = '%5B"now-1h","now","Local","5m",%7B"expr":"metric"%7D,"ui"%5D';
  50. expect(parseUrlState(paramValue)).toMatchObject({
  51. datasource: 'Local',
  52. queries: [{ expr: 'metric' }],
  53. range: {
  54. from: 'now-1h',
  55. to: 'now',
  56. },
  57. });
  58. });
  59. });
  60. describe('serializeStateToUrlParam', () => {
  61. it('returns url parameter value for a state object', () => {
  62. const state = {
  63. ...DEFAULT_EXPLORE_STATE,
  64. datasource: 'foo',
  65. queries: [
  66. {
  67. expr: 'metric{test="a/b"}',
  68. },
  69. {
  70. expr: 'super{foo="x/z"}',
  71. },
  72. ],
  73. range: {
  74. from: 'now-5h',
  75. to: 'now',
  76. },
  77. };
  78. expect(serializeStateToUrlParam(state)).toBe(
  79. '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
  80. '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
  81. '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}'
  82. );
  83. });
  84. it('returns url parameter value for a state object', () => {
  85. const state = {
  86. ...DEFAULT_EXPLORE_STATE,
  87. datasource: 'foo',
  88. queries: [
  89. {
  90. expr: 'metric{test="a/b"}',
  91. },
  92. {
  93. expr: 'super{foo="x/z"}',
  94. },
  95. ],
  96. range: {
  97. from: 'now-5h',
  98. to: 'now',
  99. },
  100. };
  101. expect(serializeStateToUrlParam(state, true)).toBe(
  102. '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]'
  103. );
  104. });
  105. });
  106. describe('interplay', () => {
  107. it('can parse the serialized state into the original state', () => {
  108. const state = {
  109. ...DEFAULT_EXPLORE_STATE,
  110. datasource: 'foo',
  111. queries: [
  112. {
  113. expr: 'metric{test="a/b"}',
  114. },
  115. {
  116. expr: 'super{foo="x/z"}',
  117. },
  118. ],
  119. range: {
  120. from: 'now - 5h',
  121. to: 'now',
  122. },
  123. };
  124. const serialized = serializeStateToUrlParam(state);
  125. const parsed = parseUrlState(serialized);
  126. expect(state).toMatchObject(parsed);
  127. });
  128. it('can parse the compact serialized state into the original state', () => {
  129. const state = {
  130. ...DEFAULT_EXPLORE_STATE,
  131. datasource: 'foo',
  132. queries: [
  133. {
  134. expr: 'metric{test="a/b"}',
  135. },
  136. {
  137. expr: 'super{foo="x/z"}',
  138. },
  139. ],
  140. range: {
  141. from: 'now - 5h',
  142. to: 'now',
  143. },
  144. };
  145. const serialized = serializeStateToUrlParam(state, true);
  146. const parsed = parseUrlState(serialized);
  147. expect(state).toMatchObject(parsed);
  148. });
  149. });
  150. });
  151. describe('updateHistory()', () => {
  152. const datasourceId = 'myDatasource';
  153. const key = `grafana.explore.history.${datasourceId}`;
  154. beforeEach(() => {
  155. clearHistory(datasourceId);
  156. expect(store.exists(key)).toBeFalsy();
  157. });
  158. test('should save history item to localStorage', () => {
  159. const expected = [
  160. {
  161. query: { refId: '1', expr: 'metric' },
  162. },
  163. ];
  164. expect(updateHistory([], datasourceId, [{ refId: '1', expr: 'metric' }])).toMatchObject(expected);
  165. expect(store.exists(key)).toBeTruthy();
  166. expect(store.getObject(key)).toMatchObject(expected);
  167. });
  168. });
  169. describe('hasNonEmptyQuery', () => {
  170. test('should return true if one query is non-empty', () => {
  171. expect(hasNonEmptyQuery([{ refId: '1', key: '2', context: 'explore', expr: 'foo' }])).toBeTruthy();
  172. });
  173. test('should return false if query is empty', () => {
  174. expect(hasNonEmptyQuery([{ refId: '1', key: '2', context: 'panel' }])).toBeFalsy();
  175. });
  176. test('should return false if no queries exist', () => {
  177. expect(hasNonEmptyQuery([])).toBeFalsy();
  178. });
  179. });
  180. describe('instanceOfDataQueryError', () => {
  181. describe('when called with a DataQueryError', () => {
  182. it('then it should return true', () => {
  183. const error: DataQueryError = {
  184. message: 'A message',
  185. status: '200',
  186. statusText: 'Ok',
  187. };
  188. const result = instanceOfDataQueryError(error);
  189. expect(result).toBe(true);
  190. });
  191. });
  192. describe('when called with a non DataQueryError', () => {
  193. it('then it should return false', () => {
  194. const error = {};
  195. const result = instanceOfDataQueryError(error);
  196. expect(result).toBe(false);
  197. });
  198. });
  199. });
  200. describe('hasRefId', () => {
  201. describe('when called with a null value', () => {
  202. it('then it should return null', () => {
  203. const input = null;
  204. const result = getValueWithRefId(input);
  205. expect(result).toBeNull();
  206. });
  207. });
  208. describe('when called with a non object value', () => {
  209. it('then it should return null', () => {
  210. const input = 123;
  211. const result = getValueWithRefId(input);
  212. expect(result).toBeNull();
  213. });
  214. });
  215. describe('when called with an object that has refId', () => {
  216. it('then it should return the object', () => {
  217. const input = { refId: 'A' };
  218. const result = getValueWithRefId(input);
  219. expect(result).toBe(input);
  220. });
  221. });
  222. describe('when called with an array that has refId', () => {
  223. it('then it should return the object', () => {
  224. const input = [123, null, {}, { refId: 'A' }];
  225. const result = getValueWithRefId(input);
  226. expect(result).toBe(input[3]);
  227. });
  228. });
  229. describe('when called with an object that has refId somewhere in the object tree', () => {
  230. it('then it should return the object', () => {
  231. const input: any = { data: [123, null, {}, { series: [123, null, {}, { refId: 'A' }] }] };
  232. const result = getValueWithRefId(input);
  233. expect(result).toBe(input.data[3].series[3]);
  234. });
  235. });
  236. });
  237. describe('getFirstQueryErrorWithoutRefId', () => {
  238. describe('when called with a null value', () => {
  239. it('then it should return null', () => {
  240. const errors: DataQueryError[] = null;
  241. const result = getFirstQueryErrorWithoutRefId(errors);
  242. expect(result).toBeNull();
  243. });
  244. });
  245. describe('when called with an array with only refIds', () => {
  246. it('then it should return undefined', () => {
  247. const errors: DataQueryError[] = [{ refId: 'A' }, { refId: 'B' }];
  248. const result = getFirstQueryErrorWithoutRefId(errors);
  249. expect(result).toBeUndefined();
  250. });
  251. });
  252. describe('when called with an array with and without refIds', () => {
  253. it('then it should return undefined', () => {
  254. const errors: DataQueryError[] = [
  255. { refId: 'A' },
  256. { message: 'A message' },
  257. { refId: 'B' },
  258. { message: 'B message' },
  259. ];
  260. const result = getFirstQueryErrorWithoutRefId(errors);
  261. expect(result).toBe(errors[1]);
  262. });
  263. });
  264. });
  265. describe('getRefIds', () => {
  266. describe('when called with a null value', () => {
  267. it('then it should return empty array', () => {
  268. const input = null;
  269. const result = getRefIds(input);
  270. expect(result).toEqual([]);
  271. });
  272. });
  273. describe('when called with a non object value', () => {
  274. it('then it should return empty array', () => {
  275. const input = 123;
  276. const result = getRefIds(input);
  277. expect(result).toEqual([]);
  278. });
  279. });
  280. describe('when called with an object that has refId', () => {
  281. it('then it should return an array with that refId', () => {
  282. const input = { refId: 'A' };
  283. const result = getRefIds(input);
  284. expect(result).toEqual(['A']);
  285. });
  286. });
  287. describe('when called with an array that has refIds', () => {
  288. it('then it should return an array with unique refIds', () => {
  289. const input = [123, null, {}, { refId: 'A' }, { refId: 'A' }, { refId: 'B' }];
  290. const result = getRefIds(input);
  291. expect(result).toEqual(['A', 'B']);
  292. });
  293. });
  294. describe('when called with an object that has refIds somewhere in the object tree', () => {
  295. it('then it should return return an array with unique refIds', () => {
  296. const input: any = {
  297. data: [
  298. 123,
  299. null,
  300. { refId: 'B', series: [{ refId: 'X' }] },
  301. { refId: 'B' },
  302. {},
  303. { series: [123, null, {}, { refId: 'A' }] },
  304. ],
  305. };
  306. const result = getRefIds(input);
  307. expect(result).toEqual(['B', 'X', 'A']);
  308. });
  309. });
  310. });