datasource.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import angular from 'angular';
  2. import * as dateMath from '@grafana/ui/src/utils/datemath';
  3. import _ from 'lodash';
  4. import { ElasticDatasource } from '../datasource';
  5. import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper';
  6. describe('ElasticDatasource', function(this: any) {
  7. const backendSrv = {
  8. datasourceRequest: jest.fn(),
  9. };
  10. const $rootScope = {
  11. $on: jest.fn(),
  12. appEvent: jest.fn(),
  13. };
  14. const templateSrv = {
  15. replace: jest.fn(text => {
  16. if (text.startsWith('$')) {
  17. return `resolvedVariable`;
  18. } else {
  19. return text;
  20. }
  21. }),
  22. getAdhocFilters: jest.fn(() => []),
  23. };
  24. const timeSrv = {
  25. time: { from: 'now-1h', to: 'now' },
  26. timeRange: jest.fn(() => {
  27. return {
  28. from: dateMath.parse(this.time.from, false),
  29. to: dateMath.parse(this.time.to, true),
  30. };
  31. }),
  32. setTime: jest.fn(time => {
  33. this.time = time;
  34. }),
  35. };
  36. const ctx = {
  37. $rootScope,
  38. backendSrv,
  39. } as any;
  40. function createDatasource(instanceSettings) {
  41. instanceSettings.jsonData = instanceSettings.jsonData || {};
  42. ctx.ds = new ElasticDatasource(instanceSettings, {}, backendSrv, templateSrv, timeSrv);
  43. }
  44. describe('When testing datasource with index pattern', () => {
  45. beforeEach(() => {
  46. createDatasource({
  47. url: 'http://es.com',
  48. index: '[asd-]YYYY.MM.DD',
  49. jsonData: { interval: 'Daily', esVersion: '2' },
  50. });
  51. });
  52. it('should translate index pattern to current day', () => {
  53. let requestOptions;
  54. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  55. requestOptions = options;
  56. return Promise.resolve({ data: {} });
  57. });
  58. ctx.ds.testDatasource();
  59. const today = toUtc().format('YYYY.MM.DD');
  60. expect(requestOptions.url).toBe('http://es.com/asd-' + today + '/_mapping');
  61. });
  62. });
  63. describe('When issuing metric query with interval pattern', () => {
  64. let requestOptions, parts, header, query, result;
  65. beforeEach(async () => {
  66. createDatasource({
  67. url: 'http://es.com',
  68. index: '[asd-]YYYY.MM.DD',
  69. jsonData: { interval: 'Daily', esVersion: '2' },
  70. });
  71. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  72. requestOptions = options;
  73. return Promise.resolve({
  74. data: {
  75. responses: [
  76. {
  77. aggregations: {
  78. '1': {
  79. buckets: [
  80. {
  81. doc_count: 10,
  82. key: 1000,
  83. },
  84. ],
  85. },
  86. },
  87. },
  88. ],
  89. },
  90. });
  91. });
  92. query = {
  93. range: {
  94. from: toUtc([2015, 4, 30, 10]),
  95. to: toUtc([2015, 5, 1, 10]),
  96. },
  97. targets: [
  98. {
  99. alias: '$varAlias',
  100. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
  101. metrics: [{ type: 'count', id: '1' }],
  102. query: 'escape\\:test',
  103. },
  104. ],
  105. };
  106. result = await ctx.ds.query(query);
  107. parts = requestOptions.data.split('\n');
  108. header = angular.fromJson(parts[0]);
  109. });
  110. it('should translate index pattern to current day', () => {
  111. expect(header.index).toEqual(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
  112. });
  113. it('should not resolve the variable in the original alias field in the query', () => {
  114. expect(query.targets[0].alias).toEqual('$varAlias');
  115. });
  116. it('should resolve the alias variable for the alias/target in the result', () => {
  117. expect(result.data[0].target).toEqual('resolvedVariable');
  118. });
  119. it('should json escape lucene query', () => {
  120. const body = angular.fromJson(parts[1]);
  121. expect(body.query.bool.filter[1].query_string.query).toBe('escape\\:test');
  122. });
  123. });
  124. describe('When issuing document query', () => {
  125. let requestOptions, parts, header;
  126. beforeEach(() => {
  127. createDatasource({
  128. url: 'http://es.com',
  129. index: 'test',
  130. jsonData: { esVersion: '2' },
  131. });
  132. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  133. requestOptions = options;
  134. return Promise.resolve({ data: { responses: [] } });
  135. });
  136. ctx.ds.query({
  137. range: {
  138. from: dateTime([2015, 4, 30, 10]),
  139. to: dateTime([2015, 5, 1, 10]),
  140. },
  141. targets: [
  142. {
  143. bucketAggs: [],
  144. metrics: [{ type: 'raw_document' }],
  145. query: 'test',
  146. },
  147. ],
  148. });
  149. parts = requestOptions.data.split('\n');
  150. header = angular.fromJson(parts[0]);
  151. });
  152. it('should set search type to query_then_fetch', () => {
  153. expect(header.search_type).toEqual('query_then_fetch');
  154. });
  155. it('should set size', () => {
  156. const body = angular.fromJson(parts[1]);
  157. expect(body.size).toBe(500);
  158. });
  159. });
  160. describe('When getting fields', () => {
  161. beforeEach(() => {
  162. createDatasource({ url: 'http://es.com', index: 'metricbeat', jsonData: { esVersion: 50 } });
  163. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  164. return Promise.resolve({
  165. data: {
  166. metricbeat: {
  167. mappings: {
  168. metricsets: {
  169. _all: {},
  170. properties: {
  171. '@timestamp': { type: 'date' },
  172. beat: {
  173. properties: {
  174. name: {
  175. fields: { raw: { type: 'keyword' } },
  176. type: 'string',
  177. },
  178. hostname: { type: 'string' },
  179. },
  180. },
  181. system: {
  182. properties: {
  183. cpu: {
  184. properties: {
  185. system: { type: 'float' },
  186. user: { type: 'float' },
  187. },
  188. },
  189. process: {
  190. properties: {
  191. cpu: {
  192. properties: {
  193. total: { type: 'float' },
  194. },
  195. },
  196. name: { type: 'string' },
  197. },
  198. },
  199. },
  200. },
  201. },
  202. },
  203. },
  204. },
  205. },
  206. });
  207. });
  208. });
  209. it('should return nested fields', async () => {
  210. const fieldObjects = await ctx.ds.getFields({
  211. find: 'fields',
  212. query: '*',
  213. });
  214. const fields = _.map(fieldObjects, 'text');
  215. expect(fields).toEqual([
  216. '@timestamp',
  217. 'beat.name.raw',
  218. 'beat.name',
  219. 'beat.hostname',
  220. 'system.cpu.system',
  221. 'system.cpu.user',
  222. 'system.process.cpu.total',
  223. 'system.process.name',
  224. ]);
  225. });
  226. it('should return number fields', async () => {
  227. const fieldObjects = await ctx.ds.getFields({
  228. find: 'fields',
  229. query: '*',
  230. type: 'number',
  231. });
  232. const fields = _.map(fieldObjects, 'text');
  233. expect(fields).toEqual(['system.cpu.system', 'system.cpu.user', 'system.process.cpu.total']);
  234. });
  235. it('should return date fields', async () => {
  236. const fieldObjects = await ctx.ds.getFields({
  237. find: 'fields',
  238. query: '*',
  239. type: 'date',
  240. });
  241. const fields = _.map(fieldObjects, 'text');
  242. expect(fields).toEqual(['@timestamp']);
  243. });
  244. });
  245. describe('When getting fields from ES 7.0', () => {
  246. beforeEach(() => {
  247. createDatasource({ url: 'http://es.com', index: 'genuine.es7._mapping.response', jsonData: { esVersion: 70 } });
  248. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  249. return Promise.resolve({
  250. data: {
  251. 'genuine.es7._mapping.response': {
  252. mappings: {
  253. properties: {
  254. '@timestamp_millis': {
  255. type: 'date',
  256. format: 'epoch_millis',
  257. },
  258. classification_terms: {
  259. type: 'keyword',
  260. },
  261. domains: {
  262. type: 'keyword',
  263. },
  264. ip_address: {
  265. type: 'ip',
  266. },
  267. justification_blob: {
  268. properties: {
  269. criterion: {
  270. type: 'text',
  271. fields: {
  272. keyword: {
  273. type: 'keyword',
  274. ignore_above: 256,
  275. },
  276. },
  277. },
  278. overall_vote_score: {
  279. type: 'float',
  280. },
  281. shallow: {
  282. properties: {
  283. jsi: {
  284. properties: {
  285. sdb: {
  286. properties: {
  287. dsel2: {
  288. properties: {
  289. 'bootlegged-gille': {
  290. properties: {
  291. botness: {
  292. type: 'float',
  293. },
  294. general_algorithm_score: {
  295. type: 'float',
  296. },
  297. },
  298. },
  299. 'uncombed-boris': {
  300. properties: {
  301. botness: {
  302. type: 'float',
  303. },
  304. general_algorithm_score: {
  305. type: 'float',
  306. },
  307. },
  308. },
  309. },
  310. },
  311. },
  312. },
  313. },
  314. },
  315. },
  316. },
  317. },
  318. },
  319. overall_vote_score: {
  320. type: 'float',
  321. },
  322. ua_terms_long: {
  323. type: 'keyword',
  324. },
  325. ua_terms_short: {
  326. type: 'keyword',
  327. },
  328. },
  329. },
  330. },
  331. },
  332. });
  333. });
  334. });
  335. it('should return nested fields', async () => {
  336. const fieldObjects = await ctx.ds.getFields({
  337. find: 'fields',
  338. query: '*',
  339. });
  340. const fields = _.map(fieldObjects, 'text');
  341. expect(fields).toEqual([
  342. '@timestamp_millis',
  343. 'classification_terms',
  344. 'domains',
  345. 'ip_address',
  346. 'justification_blob.criterion.keyword',
  347. 'justification_blob.criterion',
  348. 'justification_blob.overall_vote_score',
  349. 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness',
  350. 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score',
  351. 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness',
  352. 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score',
  353. 'overall_vote_score',
  354. 'ua_terms_long',
  355. 'ua_terms_short',
  356. ]);
  357. });
  358. it('should return number fields', async () => {
  359. const fieldObjects = await ctx.ds.getFields({
  360. find: 'fields',
  361. query: '*',
  362. type: 'number',
  363. });
  364. const fields = _.map(fieldObjects, 'text');
  365. expect(fields).toEqual([
  366. 'justification_blob.overall_vote_score',
  367. 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness',
  368. 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score',
  369. 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness',
  370. 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score',
  371. 'overall_vote_score',
  372. ]);
  373. });
  374. it('should return date fields', async () => {
  375. const fieldObjects = await ctx.ds.getFields({
  376. find: 'fields',
  377. query: '*',
  378. type: 'date',
  379. });
  380. const fields = _.map(fieldObjects, 'text');
  381. expect(fields).toEqual(['@timestamp_millis']);
  382. });
  383. });
  384. describe('When issuing aggregation query on es5.x', () => {
  385. let requestOptions, parts, header;
  386. beforeEach(() => {
  387. createDatasource({
  388. url: 'http://es.com',
  389. index: 'test',
  390. jsonData: { esVersion: '5' },
  391. });
  392. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  393. requestOptions = options;
  394. return Promise.resolve({ data: { responses: [] } });
  395. });
  396. ctx.ds.query({
  397. range: {
  398. from: dateTime([2015, 4, 30, 10]),
  399. to: dateTime([2015, 5, 1, 10]),
  400. },
  401. targets: [
  402. {
  403. bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
  404. metrics: [{ type: 'count' }],
  405. query: 'test',
  406. },
  407. ],
  408. });
  409. parts = requestOptions.data.split('\n');
  410. header = angular.fromJson(parts[0]);
  411. });
  412. it('should not set search type to count', () => {
  413. expect(header.search_type).not.toEqual('count');
  414. });
  415. it('should set size to 0', () => {
  416. const body = angular.fromJson(parts[1]);
  417. expect(body.size).toBe(0);
  418. });
  419. });
  420. describe('When issuing metricFind query on es5.x', () => {
  421. let requestOptions, parts, header, body, results;
  422. beforeEach(() => {
  423. createDatasource({
  424. url: 'http://es.com',
  425. index: 'test',
  426. jsonData: { esVersion: '5' },
  427. });
  428. ctx.backendSrv.datasourceRequest = jest.fn(options => {
  429. requestOptions = options;
  430. return Promise.resolve({
  431. data: {
  432. responses: [
  433. {
  434. aggregations: {
  435. '1': {
  436. buckets: [
  437. { doc_count: 1, key: 'test' },
  438. {
  439. doc_count: 2,
  440. key: 'test2',
  441. key_as_string: 'test2_as_string',
  442. },
  443. ],
  444. },
  445. },
  446. },
  447. ],
  448. },
  449. });
  450. });
  451. ctx.ds.metricFindQuery('{"find": "terms", "field": "test"}').then(res => {
  452. results = res;
  453. });
  454. parts = requestOptions.data.split('\n');
  455. header = angular.fromJson(parts[0]);
  456. body = angular.fromJson(parts[1]);
  457. });
  458. it('should get results', () => {
  459. expect(results.length).toEqual(2);
  460. });
  461. it('should use key or key_as_string', () => {
  462. expect(results[0].text).toEqual('test');
  463. expect(results[1].text).toEqual('test2_as_string');
  464. });
  465. it('should not set search type to count', () => {
  466. expect(header.search_type).not.toEqual('count');
  467. });
  468. it('should set size to 0', () => {
  469. expect(body.size).toBe(0);
  470. });
  471. it('should not set terms aggregation size to 0', () => {
  472. expect(body['aggs']['1']['terms'].size).not.toBe(0);
  473. });
  474. });
  475. });