query_builder.test.ts 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. import { gte, lt } from 'semver';
  2. import { ElasticQueryBuilder } from '../query_builder';
  3. import { ElasticsearchQuery } from '../types';
  4. describe('ElasticQueryBuilder', () => {
  5. const builder = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '2.0.0' });
  6. const builder5x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '5.0.0' });
  7. const builder56 = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '5.6.0' });
  8. const builder6x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '6.0.0' });
  9. const builder7x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '7.0.0' });
  10. const builder77 = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '7.7.0' });
  11. const builder8 = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '8.0.0' });
  12. const allBuilders = [builder, builder5x, builder56, builder6x, builder7x, builder77, builder8];
  13. allBuilders.forEach((builder) => {
  14. describe(`version ${builder.esVersion}`, () => {
  15. it('should return query with defaults', () => {
  16. const query = builder.build({
  17. refId: 'A',
  18. metrics: [{ type: 'count', id: '0' }],
  19. timeField: '@timestamp',
  20. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
  21. });
  22. expect(query.query.bool.filter[0].range['@timestamp'].gte).toBe('$timeFrom');
  23. expect(query.aggs['1'].date_histogram.extended_bounds.min).toBe('$timeFrom');
  24. });
  25. it('should clean settings from null values', () => {
  26. const query = builder.build({
  27. refId: 'A',
  28. // The following `missing: null as any` is because previous versions of the DS where
  29. // storing null in the query model when inputting an empty string,
  30. // which were then removed in the query builder.
  31. // The new version doesn't store empty strings at all. This tests ensures backward compatinility.
  32. metrics: [{ type: 'avg', id: '0', settings: { missing: null as any, script: '1' } }],
  33. timeField: '@timestamp',
  34. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
  35. });
  36. expect(query.aggs['1'].aggs['0'].avg.missing).not.toBeDefined();
  37. expect(query.aggs['1'].aggs['0'].avg.script).toBeDefined();
  38. });
  39. it('with multiple bucket aggs', () => {
  40. const query = builder.build({
  41. refId: 'A',
  42. metrics: [{ type: 'count', id: '1' }],
  43. timeField: '@timestamp',
  44. bucketAggs: [
  45. { type: 'terms', field: '@host', id: '2' },
  46. { type: 'date_histogram', field: '@timestamp', id: '3' },
  47. ],
  48. });
  49. expect(query.aggs['2'].terms.field).toBe('@host');
  50. expect(query.aggs['2'].aggs['3'].date_histogram.field).toBe('@timestamp');
  51. });
  52. it('with select field', () => {
  53. const query = builder.build(
  54. {
  55. refId: 'A',
  56. metrics: [{ type: 'avg', field: '@value', id: '1' }],
  57. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
  58. },
  59. 100
  60. );
  61. const aggs = query.aggs['2'].aggs;
  62. expect(aggs['1'].avg.field).toBe('@value');
  63. });
  64. it('term agg and order by term', () => {
  65. const target: ElasticsearchQuery = {
  66. refId: 'A',
  67. metrics: [
  68. { type: 'count', id: '1' },
  69. { type: 'avg', field: '@value', id: '5' },
  70. ],
  71. bucketAggs: [
  72. {
  73. type: 'terms',
  74. field: '@host',
  75. settings: { size: '5', order: 'asc', orderBy: '_term' },
  76. id: '2',
  77. },
  78. { type: 'date_histogram', field: '@timestamp', id: '3' },
  79. ],
  80. };
  81. const query = builder.build(target, 100);
  82. const firstLevel = query.aggs['2'];
  83. if (gte(builder.esVersion, '6.0.0')) {
  84. expect(firstLevel.terms.order._key).toBe('asc');
  85. } else {
  86. expect(firstLevel.terms.order._term).toBe('asc');
  87. }
  88. });
  89. it('with term agg and order by metric agg', () => {
  90. const query = builder.build(
  91. {
  92. refId: 'A',
  93. metrics: [
  94. { type: 'count', id: '1' },
  95. { type: 'avg', field: '@value', id: '5' },
  96. ],
  97. bucketAggs: [
  98. {
  99. type: 'terms',
  100. field: '@host',
  101. settings: { size: '5', order: 'asc', orderBy: '5' },
  102. id: '2',
  103. },
  104. { type: 'date_histogram', field: '@timestamp', id: '3' },
  105. ],
  106. },
  107. 100
  108. );
  109. const firstLevel = query.aggs['2'];
  110. const secondLevel = firstLevel.aggs['3'];
  111. expect(firstLevel.aggs['5'].avg.field).toBe('@value');
  112. expect(secondLevel.aggs['5'].avg.field).toBe('@value');
  113. });
  114. it('with term agg and order by count agg', () => {
  115. const query = builder.build(
  116. {
  117. refId: 'A',
  118. metrics: [
  119. { type: 'count', id: '1' },
  120. { type: 'avg', field: '@value', id: '5' },
  121. ],
  122. bucketAggs: [
  123. {
  124. type: 'terms',
  125. field: '@host',
  126. settings: { size: '5', order: 'asc', orderBy: '1' },
  127. id: '2',
  128. },
  129. { type: 'date_histogram', field: '@timestamp', id: '3' },
  130. ],
  131. },
  132. 100
  133. );
  134. expect(query.aggs['2'].terms.order._count).toEqual('asc');
  135. expect(query.aggs['2'].aggs).not.toHaveProperty('1');
  136. });
  137. it('with term agg and order by extended_stats agg', () => {
  138. const query = builder.build(
  139. {
  140. refId: 'A',
  141. metrics: [{ type: 'extended_stats', id: '1', field: '@value', meta: { std_deviation: true } }],
  142. bucketAggs: [
  143. {
  144. type: 'terms',
  145. field: '@host',
  146. settings: { size: '5', order: 'asc', orderBy: '1[std_deviation]' },
  147. id: '2',
  148. },
  149. { type: 'date_histogram', field: '@timestamp', id: '3' },
  150. ],
  151. },
  152. 100
  153. );
  154. const firstLevel = query.aggs['2'];
  155. const secondLevel = firstLevel.aggs['3'];
  156. expect(firstLevel.aggs['1'].extended_stats.field).toBe('@value');
  157. expect(secondLevel.aggs['1'].extended_stats.field).toBe('@value');
  158. });
  159. it('with term agg and order by percentiles agg', () => {
  160. const query = builder.build(
  161. {
  162. refId: 'A',
  163. metrics: [{ type: 'percentiles', id: '1', field: '@value', settings: { percents: ['95', '99'] } }],
  164. bucketAggs: [
  165. {
  166. type: 'terms',
  167. field: '@host',
  168. settings: { size: '5', order: 'asc', orderBy: '1[95.0]' },
  169. id: '2',
  170. },
  171. { type: 'date_histogram', field: '@timestamp', id: '3' },
  172. ],
  173. },
  174. 100
  175. );
  176. const firstLevel = query.aggs['2'];
  177. const secondLevel = firstLevel.aggs['3'];
  178. expect(firstLevel.aggs['1'].percentiles.field).toBe('@value');
  179. expect(secondLevel.aggs['1'].percentiles.field).toBe('@value');
  180. });
  181. it('with term agg and valid min_doc_count', () => {
  182. const query = builder.build(
  183. {
  184. refId: 'A',
  185. metrics: [{ type: 'count', id: '1' }],
  186. bucketAggs: [
  187. {
  188. type: 'terms',
  189. field: '@host',
  190. settings: { min_doc_count: '1' },
  191. id: '2',
  192. },
  193. { type: 'date_histogram', field: '@timestamp', id: '3' },
  194. ],
  195. },
  196. 100
  197. );
  198. const firstLevel = query.aggs['2'];
  199. expect(firstLevel.terms.min_doc_count).toBe(1);
  200. });
  201. it('with term agg and variable as min_doc_count', () => {
  202. const query = builder.build(
  203. {
  204. refId: 'A',
  205. metrics: [{ type: 'count', id: '1' }],
  206. bucketAggs: [
  207. {
  208. type: 'terms',
  209. field: '@host',
  210. settings: { min_doc_count: '$min_doc_count' },
  211. id: '2',
  212. },
  213. { type: 'date_histogram', field: '@timestamp', id: '3' },
  214. ],
  215. },
  216. 100
  217. );
  218. const firstLevel = query.aggs['2'];
  219. expect(firstLevel.terms.min_doc_count).toBe('$min_doc_count');
  220. });
  221. it('with metric percentiles', () => {
  222. const percents = ['1', '2', '3', '4'];
  223. const field = '@load_time';
  224. const query = builder.build(
  225. {
  226. refId: 'A',
  227. metrics: [
  228. {
  229. id: '1',
  230. type: 'percentiles',
  231. field,
  232. settings: {
  233. percents,
  234. },
  235. },
  236. ],
  237. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  238. },
  239. 100
  240. );
  241. const firstLevel = query.aggs['3'];
  242. expect(firstLevel.aggs['1'].percentiles.field).toBe(field);
  243. expect(firstLevel.aggs['1'].percentiles.percents).toEqual(percents);
  244. });
  245. it('with filters aggs', () => {
  246. const query = builder.build({
  247. refId: 'A',
  248. metrics: [{ type: 'count', id: '1' }],
  249. timeField: '@timestamp',
  250. bucketAggs: [
  251. {
  252. id: '2',
  253. type: 'filters',
  254. settings: {
  255. filters: [
  256. { query: '@metric:cpu', label: '' },
  257. { query: '@metric:logins.count', label: '' },
  258. ],
  259. },
  260. },
  261. { type: 'date_histogram', field: '@timestamp', id: '4' },
  262. ],
  263. });
  264. expect(query.aggs['2'].filters.filters['@metric:cpu'].query_string.query).toBe('@metric:cpu');
  265. expect(query.aggs['2'].filters.filters['@metric:logins.count'].query_string.query).toBe('@metric:logins.count');
  266. expect(query.aggs['2'].aggs['4'].date_histogram.field).toBe('@timestamp');
  267. });
  268. it('should return correct query for raw_document metric', () => {
  269. const target: ElasticsearchQuery = {
  270. refId: 'A',
  271. metrics: [{ type: 'raw_document', id: '1', settings: {} }],
  272. timeField: '@timestamp',
  273. bucketAggs: [] as any[],
  274. };
  275. const query = builder.build(target);
  276. expect(query).toMatchObject({
  277. size: 500,
  278. query: {
  279. bool: {
  280. filter: [
  281. {
  282. range: {
  283. '@timestamp': {
  284. format: 'epoch_millis',
  285. gte: '$timeFrom',
  286. lte: '$timeTo',
  287. },
  288. },
  289. },
  290. ],
  291. },
  292. },
  293. sort: [{ '@timestamp': { order: 'desc', unmapped_type: 'boolean' } }, { _doc: { order: 'desc' } }],
  294. script_fields: {},
  295. });
  296. });
  297. it('should set query size from settings when raw_documents', () => {
  298. const query = builder.build({
  299. refId: 'A',
  300. metrics: [{ type: 'raw_document', id: '1', settings: { size: '1337' } }],
  301. timeField: '@timestamp',
  302. bucketAggs: [],
  303. });
  304. expect(query.size).toBe(1337);
  305. });
  306. it('with moving average', () => {
  307. const query = builder.build({
  308. refId: 'A',
  309. metrics: [
  310. {
  311. id: '3',
  312. type: 'sum',
  313. field: '@value',
  314. },
  315. {
  316. id: '2',
  317. type: 'moving_avg',
  318. field: '3',
  319. },
  320. ],
  321. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  322. });
  323. const firstLevel = query.aggs['3'];
  324. expect(firstLevel.aggs['2']).not.toBe(undefined);
  325. expect(firstLevel.aggs['2'].moving_avg).not.toBe(undefined);
  326. expect(firstLevel.aggs['2'].moving_avg.buckets_path).toBe('3');
  327. });
  328. it('with moving average doc count', () => {
  329. const query = builder.build({
  330. refId: 'A',
  331. metrics: [
  332. {
  333. id: '3',
  334. type: 'count',
  335. },
  336. {
  337. id: '2',
  338. type: 'moving_avg',
  339. field: '3',
  340. },
  341. ],
  342. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '4' }],
  343. });
  344. const firstLevel = query.aggs['4'];
  345. expect(firstLevel.aggs['2']).not.toBe(undefined);
  346. expect(firstLevel.aggs['2'].moving_avg).not.toBe(undefined);
  347. expect(firstLevel.aggs['2'].moving_avg.buckets_path).toBe('_count');
  348. });
  349. it('with broken moving average', () => {
  350. const query = builder.build({
  351. refId: 'A',
  352. metrics: [
  353. {
  354. id: '3',
  355. type: 'sum',
  356. field: '@value',
  357. },
  358. {
  359. id: '2',
  360. type: 'moving_avg',
  361. field: '3',
  362. },
  363. {
  364. id: '4',
  365. type: 'moving_avg',
  366. },
  367. ],
  368. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  369. });
  370. const firstLevel = query.aggs['3'];
  371. expect(firstLevel.aggs['2']).not.toBe(undefined);
  372. expect(firstLevel.aggs['2'].moving_avg).not.toBe(undefined);
  373. expect(firstLevel.aggs['2'].moving_avg.buckets_path).toBe('3');
  374. expect(firstLevel.aggs['4']).toBe(undefined);
  375. });
  376. it('with top_metrics', () => {
  377. const query = builder.build({
  378. refId: 'A',
  379. metrics: [
  380. {
  381. id: '2',
  382. type: 'top_metrics',
  383. settings: {
  384. order: 'desc',
  385. orderBy: '@timestamp',
  386. metrics: ['@value'],
  387. },
  388. },
  389. ],
  390. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  391. });
  392. const firstLevel = query.aggs['3'];
  393. expect(firstLevel.aggs['2']).not.toBe(undefined);
  394. expect(firstLevel.aggs['2'].top_metrics).not.toBe(undefined);
  395. expect(firstLevel.aggs['2'].top_metrics.metrics).not.toBe(undefined);
  396. expect(firstLevel.aggs['2'].top_metrics.size).not.toBe(undefined);
  397. expect(firstLevel.aggs['2'].top_metrics.sort).not.toBe(undefined);
  398. expect(firstLevel.aggs['2'].top_metrics.metrics.length).toBe(1);
  399. expect(firstLevel.aggs['2'].top_metrics.metrics).toEqual([{ field: '@value' }]);
  400. expect(firstLevel.aggs['2'].top_metrics.sort).toEqual([{ '@timestamp': 'desc' }]);
  401. expect(firstLevel.aggs['2'].top_metrics.size).toBe(1);
  402. });
  403. it('with derivative', () => {
  404. const query = builder.build({
  405. refId: 'A',
  406. metrics: [
  407. {
  408. id: '3',
  409. type: 'sum',
  410. field: '@value',
  411. },
  412. {
  413. id: '2',
  414. type: 'derivative',
  415. field: '3',
  416. },
  417. ],
  418. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  419. });
  420. const firstLevel = query.aggs['3'];
  421. expect(firstLevel.aggs['2']).not.toBe(undefined);
  422. expect(firstLevel.aggs['2'].derivative).not.toBe(undefined);
  423. expect(firstLevel.aggs['2'].derivative.buckets_path).toBe('3');
  424. });
  425. it('with derivative doc count', () => {
  426. const query = builder.build({
  427. refId: 'A',
  428. metrics: [
  429. {
  430. id: '3',
  431. type: 'count',
  432. },
  433. {
  434. id: '2',
  435. type: 'derivative',
  436. field: '3',
  437. },
  438. ],
  439. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '4' }],
  440. });
  441. const firstLevel = query.aggs['4'];
  442. expect(firstLevel.aggs['2']).not.toBe(undefined);
  443. expect(firstLevel.aggs['2'].derivative).not.toBe(undefined);
  444. expect(firstLevel.aggs['2'].derivative.buckets_path).toBe('_count');
  445. });
  446. it('with serial_diff', () => {
  447. const query = builder.build({
  448. refId: 'A',
  449. metrics: [
  450. {
  451. id: '3',
  452. type: 'max',
  453. field: '@value',
  454. },
  455. {
  456. id: '2',
  457. type: 'serial_diff',
  458. field: '3',
  459. settings: {
  460. lag: '5',
  461. },
  462. },
  463. ],
  464. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  465. });
  466. const firstLevel = query.aggs['3'];
  467. expect(firstLevel.aggs['2']).not.toBe(undefined);
  468. expect(firstLevel.aggs['2'].serial_diff).not.toBe(undefined);
  469. expect(firstLevel.aggs['2'].serial_diff.buckets_path).toBe('3');
  470. expect(firstLevel.aggs['2'].serial_diff.lag).toBe(5);
  471. });
  472. it('with bucket_script', () => {
  473. const query = builder.build({
  474. refId: 'A',
  475. metrics: [
  476. {
  477. id: '1',
  478. type: 'sum',
  479. field: '@value',
  480. },
  481. {
  482. id: '3',
  483. type: 'max',
  484. field: '@value',
  485. },
  486. {
  487. id: '4',
  488. pipelineVariables: [
  489. {
  490. name: 'var1',
  491. pipelineAgg: '1',
  492. },
  493. {
  494. name: 'var2',
  495. pipelineAgg: '3',
  496. },
  497. ],
  498. settings: {
  499. script: 'params.var1 * params.var2',
  500. },
  501. type: 'bucket_script',
  502. },
  503. ],
  504. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
  505. });
  506. const firstLevel = query.aggs['2'];
  507. expect(firstLevel.aggs['4']).not.toBe(undefined);
  508. expect(firstLevel.aggs['4'].bucket_script).not.toBe(undefined);
  509. expect(firstLevel.aggs['4'].bucket_script.buckets_path).toMatchObject({ var1: '1', var2: '3' });
  510. });
  511. it('with bucket_script doc count', () => {
  512. const query = builder.build({
  513. refId: 'A',
  514. metrics: [
  515. {
  516. id: '3',
  517. type: 'count',
  518. },
  519. {
  520. id: '4',
  521. pipelineVariables: [
  522. {
  523. name: 'var1',
  524. pipelineAgg: '3',
  525. },
  526. ],
  527. settings: {
  528. script: 'params.var1 * 1000',
  529. },
  530. type: 'bucket_script',
  531. },
  532. ],
  533. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
  534. });
  535. const firstLevel = query.aggs['2'];
  536. expect(firstLevel.aggs['4']).not.toBe(undefined);
  537. expect(firstLevel.aggs['4'].bucket_script).not.toBe(undefined);
  538. expect(firstLevel.aggs['4'].bucket_script.buckets_path).toMatchObject({ var1: '_count' });
  539. });
  540. it('with histogram', () => {
  541. const query = builder.build({
  542. refId: 'A',
  543. metrics: [{ id: '1', type: 'count' }],
  544. bucketAggs: [
  545. {
  546. type: 'histogram',
  547. field: 'bytes',
  548. id: '3',
  549. settings: {
  550. interval: '10',
  551. min_doc_count: '2',
  552. },
  553. },
  554. ],
  555. });
  556. const firstLevel = query.aggs['3'];
  557. expect(firstLevel.histogram.field).toBe('bytes');
  558. expect(firstLevel.histogram.interval).toBe('10');
  559. expect(firstLevel.histogram.min_doc_count).toBe('2');
  560. });
  561. it('with adhoc filters', () => {
  562. const query = builder.build(
  563. {
  564. refId: 'A',
  565. metrics: [{ type: 'count', id: '0' }],
  566. timeField: '@timestamp',
  567. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '3' }],
  568. },
  569. [
  570. { key: 'key1', operator: '=', value: 'value1' },
  571. { key: 'key2', operator: '=', value: 'value2' },
  572. { key: 'key2', operator: '!=', value: 'value2' },
  573. { key: 'key3', operator: '<', value: 'value3' },
  574. { key: 'key4', operator: '>', value: 'value4' },
  575. { key: 'key5', operator: '=~', value: 'value5' },
  576. { key: 'key6', operator: '!~', value: 'value6' },
  577. ]
  578. );
  579. expect(query.query.bool.must[0].match_phrase['key1'].query).toBe('value1');
  580. expect(query.query.bool.must[1].match_phrase['key2'].query).toBe('value2');
  581. expect(query.query.bool.must_not[0].match_phrase['key2'].query).toBe('value2');
  582. expect(query.query.bool.filter[1].range['key3'].lt).toBe('value3');
  583. expect(query.query.bool.filter[2].range['key4'].gt).toBe('value4');
  584. expect(query.query.bool.filter[3].regexp['key5']).toBe('value5');
  585. expect(query.query.bool.filter[4].bool.must_not.regexp['key6']).toBe('value6');
  586. });
  587. describe('getTermsQuery', () => {
  588. function testGetTermsQuery(queryDef: any) {
  589. const query = builder.getTermsQuery(queryDef);
  590. return query.aggs['1'].terms.order;
  591. }
  592. function checkSort(order: any, expected: string) {
  593. if (lt(builder.esVersion, '6.0.0')) {
  594. expect(order._term).toBe(expected);
  595. expect(order._key).toBeUndefined();
  596. } else {
  597. expect(order._term).toBeUndefined();
  598. expect(order._key).toBe(expected);
  599. }
  600. }
  601. it('should set correct default sorting', () => {
  602. const order = testGetTermsQuery({});
  603. checkSort(order, 'asc');
  604. expect(order._count).toBeUndefined();
  605. });
  606. it('should set correct explicit sorting', () => {
  607. const order = testGetTermsQuery({ order: 'desc' });
  608. checkSort(order, 'desc');
  609. expect(order._count).toBeUndefined();
  610. });
  611. it('getTermsQuery(orderBy:doc_count) should set desc sorting on _count', () => {
  612. const query = builder.getTermsQuery({ orderBy: 'doc_count' });
  613. expect(query.aggs['1'].terms.order._term).toBeUndefined();
  614. expect(query.aggs['1'].terms.order._key).toBeUndefined();
  615. expect(query.aggs['1'].terms.order._count).toBe('desc');
  616. });
  617. it('getTermsQuery(orderBy:doc_count, order:asc) should set asc sorting on _count', () => {
  618. const query = builder.getTermsQuery({ orderBy: 'doc_count', order: 'asc' });
  619. expect(query.aggs['1'].terms.order._term).toBeUndefined();
  620. expect(query.aggs['1'].terms.order._key).toBeUndefined();
  621. expect(query.aggs['1'].terms.order._count).toBe('asc');
  622. });
  623. describe('lucene query', () => {
  624. it('should add query_string filter when query is not empty', () => {
  625. const luceneQuery = 'foo';
  626. const query = builder.getTermsQuery({ orderBy: 'doc_count', order: 'asc', query: luceneQuery });
  627. expect(query.query.bool.filter).toContainEqual({
  628. query_string: { analyze_wildcard: true, query: luceneQuery },
  629. });
  630. });
  631. it('should not add query_string filter when query is empty', () => {
  632. const query = builder.getTermsQuery({ orderBy: 'doc_count', order: 'asc' });
  633. expect(
  634. query.query.bool.filter.find((filter: any) => Object.keys(filter).includes('query_string'))
  635. ).toBeFalsy();
  636. });
  637. });
  638. });
  639. describe('lucene query', () => {
  640. it('should add query_string filter when query is not empty', () => {
  641. const luceneQuery = 'foo';
  642. const query = builder.build({ refId: 'A', query: luceneQuery });
  643. expect(query.query.bool.filter).toContainEqual({
  644. query_string: { analyze_wildcard: true, query: luceneQuery },
  645. });
  646. });
  647. it('should not add query_string filter when query is empty', () => {
  648. const query = builder.build({ refId: 'A' });
  649. expect(
  650. query.query.bool.filter.find((filter: any) => Object.keys(filter).includes('query_string'))
  651. ).toBeFalsy();
  652. });
  653. });
  654. describe('getLogsQuery', () => {
  655. it('should return query with defaults', () => {
  656. const query = builder.getLogsQuery({ refId: 'A' }, 500, null);
  657. expect(query.size).toEqual(500);
  658. const expectedQuery = {
  659. bool: {
  660. filter: [{ range: { '@timestamp': { gte: '$timeFrom', lte: '$timeTo', format: 'epoch_millis' } } }],
  661. },
  662. };
  663. expect(query.query).toEqual(expectedQuery);
  664. expect(query.sort).toEqual([
  665. { '@timestamp': { order: 'desc', unmapped_type: 'boolean' } },
  666. { _doc: { order: 'desc' } },
  667. ]);
  668. const expectedAggs: any = {
  669. // FIXME: It's pretty weak to include this '1' in the test as it's not part of what we are testing here and
  670. // might change as a cause of unrelated changes
  671. 1: {
  672. aggs: {},
  673. date_histogram: {
  674. extended_bounds: { max: '$timeTo', min: '$timeFrom' },
  675. field: '@timestamp',
  676. format: 'epoch_millis',
  677. interval: '$__interval',
  678. min_doc_count: 0,
  679. },
  680. },
  681. };
  682. if (gte(builder.esVersion, '8.0.0')) {
  683. expectedAggs['1'].date_histogram.fixed_interval = expectedAggs['1'].date_histogram.interval;
  684. delete expectedAggs['1'].date_histogram.interval;
  685. }
  686. expect(query.aggs).toMatchObject(expectedAggs);
  687. });
  688. describe('lucene query', () => {
  689. it('should add query_string filter when query is not empty', () => {
  690. const luceneQuery = 'foo';
  691. const query = builder.getLogsQuery({ refId: 'A', query: luceneQuery }, 500, null);
  692. expect(query.query.bool.filter).toContainEqual({
  693. query_string: { analyze_wildcard: true, query: luceneQuery },
  694. });
  695. });
  696. it('should not add query_string filter when query is empty', () => {
  697. const query = builder.getLogsQuery({ refId: 'A' }, 500, null);
  698. expect(
  699. query.query.bool.filter.find((filter: any) => Object.keys(filter).includes('query_string'))
  700. ).toBeFalsy();
  701. });
  702. });
  703. it('with adhoc filters', () => {
  704. // TODO: Types for AdHocFilters
  705. const adhocFilters = [
  706. { key: 'key1', operator: '=', value: 'value1' },
  707. { key: 'key2', operator: '!=', value: 'value2' },
  708. { key: 'key3', operator: '<', value: 'value3' },
  709. { key: 'key4', operator: '>', value: 'value4' },
  710. { key: 'key5', operator: '=~', value: 'value5' },
  711. { key: 'key6', operator: '!~', value: 'value6' },
  712. ];
  713. const query = builder.getLogsQuery({ refId: 'A' }, 500, adhocFilters);
  714. expect(query.query.bool.must[0].match_phrase['key1'].query).toBe('value1');
  715. expect(query.query.bool.must_not[0].match_phrase['key2'].query).toBe('value2');
  716. expect(query.query.bool.filter[1].range['key3'].lt).toBe('value3');
  717. expect(query.query.bool.filter[2].range['key4'].gt).toBe('value4');
  718. expect(query.query.bool.filter[3].regexp['key5']).toBe('value5');
  719. expect(query.query.bool.filter[4].bool.must_not.regexp['key6']).toBe('value6');
  720. });
  721. });
  722. });
  723. });
  724. describe('Value casting for settings', () => {
  725. it('correctly casts values in moving_avg ', () => {
  726. const query = builder7x.build({
  727. refId: 'A',
  728. metrics: [
  729. { type: 'avg', id: '2' },
  730. {
  731. type: 'moving_avg',
  732. id: '3',
  733. field: '2',
  734. settings: {
  735. window: '5',
  736. model: 'holt_winters',
  737. predict: '10',
  738. settings: {
  739. alpha: '1',
  740. beta: '2',
  741. gamma: '3',
  742. period: '4',
  743. },
  744. },
  745. },
  746. ],
  747. timeField: '@timestamp',
  748. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
  749. });
  750. const movingAvg = query.aggs['1'].aggs['3'].moving_avg;
  751. expect(movingAvg.window).toBe(5);
  752. expect(movingAvg.predict).toBe(10);
  753. expect(movingAvg.settings.alpha).toBe(1);
  754. expect(movingAvg.settings.beta).toBe(2);
  755. expect(movingAvg.settings.gamma).toBe(3);
  756. expect(movingAvg.settings.period).toBe(4);
  757. });
  758. it('correctly casts values in serial_diff ', () => {
  759. const query = builder7x.build({
  760. refId: 'A',
  761. metrics: [
  762. { type: 'avg', id: '2' },
  763. {
  764. type: 'serial_diff',
  765. id: '3',
  766. field: '2',
  767. settings: {
  768. lag: '1',
  769. },
  770. },
  771. ],
  772. timeField: '@timestamp',
  773. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
  774. });
  775. const serialDiff = query.aggs['1'].aggs['3'].serial_diff;
  776. expect(serialDiff.lag).toBe(1);
  777. });
  778. describe('date_histogram', () => {
  779. it('should not include time_zone if not present in the query model', () => {
  780. const query = builder.build({
  781. refId: 'A',
  782. metrics: [{ type: 'count', id: '1' }],
  783. timeField: '@timestamp',
  784. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2', settings: { min_doc_count: '1' } }],
  785. });
  786. expect(query.aggs['2'].date_histogram.time_zone).not.toBeDefined();
  787. });
  788. it('should not include time_zone if "utc" in the query model', () => {
  789. const query = builder.build({
  790. refId: 'A',
  791. metrics: [{ type: 'count', id: '1' }],
  792. timeField: '@timestamp',
  793. bucketAggs: [
  794. { type: 'date_histogram', field: '@timestamp', id: '2', settings: { min_doc_count: '1', timeZone: 'utc' } },
  795. ],
  796. });
  797. expect(query.aggs['2'].date_histogram.time_zone).not.toBeDefined();
  798. });
  799. it('should include time_zone if not "utc" in the query model', () => {
  800. const expectedTimezone = 'America/Los_angeles';
  801. const query = builder.build({
  802. refId: 'A',
  803. metrics: [{ type: 'count', id: '1' }],
  804. timeField: '@timestamp',
  805. bucketAggs: [
  806. {
  807. type: 'date_histogram',
  808. field: '@timestamp',
  809. id: '2',
  810. settings: { min_doc_count: '1', timeZone: expectedTimezone },
  811. },
  812. ],
  813. });
  814. expect(query.aggs['2'].date_histogram.time_zone).toBe(expectedTimezone);
  815. });
  816. describe('field property', () => {
  817. it('should use timeField from datasource when not specified', () => {
  818. const query = builder.build({
  819. refId: 'A',
  820. metrics: [{ type: 'count', id: '1' }],
  821. timeField: '@timestamp',
  822. bucketAggs: [
  823. {
  824. type: 'date_histogram',
  825. id: '2',
  826. settings: { min_doc_count: '1' },
  827. },
  828. ],
  829. });
  830. expect(query.aggs['2'].date_histogram.field).toBe('@timestamp');
  831. });
  832. it('should use field from bucket agg when specified', () => {
  833. const query = builder.build({
  834. refId: 'A',
  835. metrics: [{ type: 'count', id: '1' }],
  836. timeField: '@timestamp',
  837. bucketAggs: [
  838. {
  839. type: 'date_histogram',
  840. id: '2',
  841. field: '@time',
  842. settings: { min_doc_count: '1' },
  843. },
  844. ],
  845. });
  846. expect(query.aggs['2'].date_histogram.field).toBe('@time');
  847. });
  848. describe('interval parameter', () => {
  849. it('should use interval if Elasticsearch version <8.0.0', () => {
  850. const query = builder77.build({
  851. refId: 'A',
  852. metrics: [{ type: 'count', id: '1' }],
  853. timeField: '@timestamp',
  854. bucketAggs: [
  855. {
  856. type: 'date_histogram',
  857. id: '2',
  858. field: '@time',
  859. settings: { min_doc_count: '1', interval: '1d' },
  860. },
  861. ],
  862. });
  863. expect(query.aggs['2'].date_histogram.interval).toBe('1d');
  864. expect(query.aggs['2'].date_histogram.fixed_interval).toBeUndefined();
  865. });
  866. });
  867. it('should use fixed_interval if Elasticsearch version >=8.0.0', () => {
  868. const query = builder8.build({
  869. refId: 'A',
  870. metrics: [{ type: 'count', id: '1' }],
  871. timeField: '@timestamp',
  872. bucketAggs: [
  873. {
  874. type: 'date_histogram',
  875. id: '2',
  876. field: '@time',
  877. settings: { min_doc_count: '1', interval: '1d' },
  878. },
  879. ],
  880. });
  881. expect(query.aggs['2'].date_histogram.interval).toBeUndefined();
  882. expect(query.aggs['2'].date_histogram.fixed_interval).toBe('1d');
  883. });
  884. });
  885. });
  886. });
  887. });