utils.spec.jsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. import {browserHistory} from 'react-router';
  2. import {COL_WIDTH_UNDEFINED} from 'app/components/gridEditable';
  3. import EventView from 'app/utils/discover/eventView';
  4. import {
  5. decodeColumnOrder,
  6. downloadAsCsv,
  7. getExpandedResults,
  8. pushEventViewToLocation,
  9. } from 'app/views/eventsV2/utils';
  10. describe('decodeColumnOrder', function () {
  11. it('can decode 0 elements', function () {
  12. const results = decodeColumnOrder([]);
  13. expect(Array.isArray(results)).toBeTruthy();
  14. expect(results).toHaveLength(0);
  15. });
  16. it('can decode fields', function () {
  17. const results = decodeColumnOrder([{field: 'title', width: 123}]);
  18. expect(Array.isArray(results)).toBeTruthy();
  19. expect(results[0]).toEqual({
  20. key: 'title',
  21. name: 'title',
  22. column: {
  23. kind: 'field',
  24. field: 'title',
  25. },
  26. width: 123,
  27. isSortable: false,
  28. type: 'string',
  29. });
  30. });
  31. it('can decode measurement fields', function () {
  32. const results = decodeColumnOrder([{field: 'measurements.foo', width: 123}]);
  33. expect(Array.isArray(results)).toBeTruthy();
  34. expect(results[0]).toEqual({
  35. key: 'measurements.foo',
  36. name: 'measurements.foo',
  37. column: {
  38. kind: 'field',
  39. field: 'measurements.foo',
  40. },
  41. width: 123,
  42. isSortable: false,
  43. type: 'number',
  44. });
  45. });
  46. it('can decode span op breakdown fields', function () {
  47. const results = decodeColumnOrder([{field: 'spans.foo', width: 123}]);
  48. expect(Array.isArray(results)).toBeTruthy();
  49. expect(results[0]).toEqual({
  50. key: 'spans.foo',
  51. name: 'spans.foo',
  52. column: {
  53. kind: 'field',
  54. field: 'spans.foo',
  55. },
  56. width: 123,
  57. isSortable: false,
  58. type: 'duration',
  59. });
  60. });
  61. it('can decode aggregate functions with no arguments', function () {
  62. let results = decodeColumnOrder([{field: 'count()', width: 123}]);
  63. expect(Array.isArray(results)).toBeTruthy();
  64. expect(results[0]).toEqual({
  65. key: 'count()',
  66. name: 'count()',
  67. column: {
  68. kind: 'function',
  69. function: ['count', '', undefined, undefined],
  70. },
  71. width: 123,
  72. isSortable: true,
  73. type: 'number',
  74. });
  75. results = decodeColumnOrder([{field: 'p75()', width: 123}]);
  76. expect(results[0].type).toEqual('duration');
  77. results = decodeColumnOrder([{field: 'p99()', width: 123}]);
  78. expect(results[0].type).toEqual('duration');
  79. });
  80. it('can decode elements with aggregate functions with arguments', function () {
  81. const results = decodeColumnOrder([{field: 'avg(transaction.duration)'}]);
  82. expect(Array.isArray(results)).toBeTruthy();
  83. expect(results[0]).toEqual({
  84. key: 'avg(transaction.duration)',
  85. name: 'avg(transaction.duration)',
  86. column: {
  87. kind: 'function',
  88. function: ['avg', 'transaction.duration', undefined, undefined],
  89. },
  90. width: COL_WIDTH_UNDEFINED,
  91. isSortable: true,
  92. type: 'duration',
  93. });
  94. });
  95. it('can decode elements with aggregate functions with multiple arguments', function () {
  96. const results = decodeColumnOrder([
  97. {field: 'percentile(transaction.duration, 0.65)'},
  98. ]);
  99. expect(Array.isArray(results)).toBeTruthy();
  100. expect(results[0]).toEqual({
  101. key: 'percentile(transaction.duration, 0.65)',
  102. name: 'percentile(transaction.duration, 0.65)',
  103. column: {
  104. kind: 'function',
  105. function: ['percentile', 'transaction.duration', '0.65', undefined],
  106. },
  107. width: COL_WIDTH_UNDEFINED,
  108. isSortable: true,
  109. type: 'duration',
  110. });
  111. });
  112. it('can decode elements with aggregate functions using measurements', function () {
  113. const results = decodeColumnOrder([{field: 'avg(measurements.foo)'}]);
  114. expect(Array.isArray(results)).toBeTruthy();
  115. expect(results[0]).toEqual({
  116. key: 'avg(measurements.foo)',
  117. name: 'avg(measurements.foo)',
  118. column: {
  119. kind: 'function',
  120. function: ['avg', 'measurements.foo', undefined, undefined],
  121. },
  122. width: COL_WIDTH_UNDEFINED,
  123. isSortable: true,
  124. type: 'number',
  125. });
  126. });
  127. it('can decode elements with aggregate functions with multiple arguments using measurements', function () {
  128. const results = decodeColumnOrder([{field: 'percentile(measurements.lcp, 0.65)'}]);
  129. expect(Array.isArray(results)).toBeTruthy();
  130. expect(results[0]).toEqual({
  131. key: 'percentile(measurements.lcp, 0.65)',
  132. name: 'percentile(measurements.lcp, 0.65)',
  133. column: {
  134. kind: 'function',
  135. function: ['percentile', 'measurements.lcp', '0.65', undefined],
  136. },
  137. width: COL_WIDTH_UNDEFINED,
  138. isSortable: true,
  139. type: 'duration',
  140. });
  141. });
  142. it('can decode elements with aggregate functions using span op breakdowns', function () {
  143. const results = decodeColumnOrder([{field: 'avg(spans.foo)'}]);
  144. expect(Array.isArray(results)).toBeTruthy();
  145. expect(results[0]).toEqual({
  146. key: 'avg(spans.foo)',
  147. name: 'avg(spans.foo)',
  148. column: {
  149. kind: 'function',
  150. function: ['avg', 'spans.foo', undefined, undefined],
  151. },
  152. width: COL_WIDTH_UNDEFINED,
  153. isSortable: true,
  154. type: 'duration',
  155. });
  156. });
  157. it('can decode elements with aggregate functions with multiple arguments using span op breakdowns', function () {
  158. const results = decodeColumnOrder([{field: 'percentile(spans.lcp, 0.65)'}]);
  159. expect(Array.isArray(results)).toBeTruthy();
  160. expect(results[0]).toEqual({
  161. key: 'percentile(spans.lcp, 0.65)',
  162. name: 'percentile(spans.lcp, 0.65)',
  163. column: {
  164. kind: 'function',
  165. function: ['percentile', 'spans.lcp', '0.65', undefined],
  166. },
  167. width: COL_WIDTH_UNDEFINED,
  168. isSortable: true,
  169. type: 'duration',
  170. });
  171. });
  172. });
  173. describe('pushEventViewToLocation', function () {
  174. const state = {
  175. id: '1234',
  176. name: 'best query',
  177. fields: [{field: 'count()', width: 420}, {field: 'project.id'}],
  178. sorts: [{field: 'count', kind: 'desc'}],
  179. query: 'event.type:error',
  180. project: [42],
  181. start: '2019-10-01T00:00:00',
  182. end: '2019-10-02T00:00:00',
  183. statsPeriod: '14d',
  184. environment: ['staging'],
  185. };
  186. const location = {
  187. query: {
  188. bestCountry: 'canada',
  189. user: '1',
  190. },
  191. };
  192. it('correct query string object pushed to history', function () {
  193. const eventView = new EventView(state);
  194. pushEventViewToLocation({
  195. location,
  196. nextEventView: eventView,
  197. });
  198. expect(browserHistory.push).toHaveBeenCalledWith({
  199. query: {
  200. id: '1234',
  201. name: 'best query',
  202. field: ['count()', 'project.id'],
  203. widths: [420],
  204. sort: ['-count'],
  205. query: 'event.type:error',
  206. project: [42],
  207. start: '2019-10-01T00:00:00',
  208. end: '2019-10-02T00:00:00',
  209. statsPeriod: '14d',
  210. environment: ['staging'],
  211. user: '1',
  212. yAxis: 'count()',
  213. },
  214. });
  215. });
  216. it('extra query params', function () {
  217. const eventView = new EventView(state);
  218. pushEventViewToLocation({
  219. location,
  220. nextEventView: eventView,
  221. extraQuery: {
  222. cursor: 'some cursor',
  223. },
  224. });
  225. expect(browserHistory.push).toHaveBeenCalledWith({
  226. query: {
  227. id: '1234',
  228. name: 'best query',
  229. field: ['count()', 'project.id'],
  230. widths: [420],
  231. sort: ['-count'],
  232. query: 'event.type:error',
  233. project: [42],
  234. start: '2019-10-01T00:00:00',
  235. end: '2019-10-02T00:00:00',
  236. statsPeriod: '14d',
  237. environment: ['staging'],
  238. cursor: 'some cursor',
  239. user: '1',
  240. yAxis: 'count()',
  241. },
  242. });
  243. });
  244. });
  245. describe('getExpandedResults()', function () {
  246. const state = {
  247. id: '1234',
  248. name: 'best query',
  249. fields: [
  250. {field: 'count()'},
  251. {field: 'last_seen()'},
  252. {field: 'title'},
  253. {field: 'custom_tag'},
  254. ],
  255. sorts: [{field: 'count', kind: 'desc'}],
  256. query: 'event.type:error',
  257. project: [42],
  258. start: '2019-10-01T00:00:00',
  259. end: '2019-10-02T00:00:00',
  260. statsPeriod: '14d',
  261. environment: ['staging'],
  262. };
  263. it('id should be default column when drilldown results in no columns', () => {
  264. const view = new EventView({
  265. ...state,
  266. fields: [{field: 'count()'}, {field: 'epm()'}, {field: 'eps()'}],
  267. });
  268. const result = getExpandedResults(view, {}, {});
  269. expect(result.fields).toEqual([{field: 'id', width: -1}]);
  270. });
  271. it('preserves aggregated fields', () => {
  272. let view = new EventView(state);
  273. let result = getExpandedResults(view, {}, {});
  274. // id should be omitted as it is an implicit property on unaggregated results.
  275. expect(result.fields).toEqual([
  276. {field: 'timestamp', width: -1},
  277. {field: 'title'},
  278. {field: 'custom_tag'},
  279. ]);
  280. expect(result.query).toEqual('event.type:error');
  281. // de-duplicate transformed columns
  282. view = new EventView({
  283. ...state,
  284. fields: [
  285. {field: 'count()'},
  286. {field: 'last_seen()'},
  287. {field: 'title'},
  288. {field: 'custom_tag'},
  289. {field: 'count(id)'},
  290. ],
  291. });
  292. result = getExpandedResults(view, {}, {});
  293. // id should be omitted as it is an implicit property on unaggregated results.
  294. expect(result.fields).toEqual([
  295. {field: 'timestamp', width: -1},
  296. {field: 'title'},
  297. {field: 'custom_tag'},
  298. ]);
  299. // transform aliased fields, & de-duplicate any transforms
  300. view = new EventView({
  301. ...state,
  302. fields: [
  303. {field: 'last_seen()'}, // expect this to be transformed to timestamp
  304. {field: 'title'},
  305. {field: 'avg(transaction.duration)'}, // expect this to be dropped
  306. {field: 'p50()'},
  307. {field: 'p75()'},
  308. {field: 'p95()'},
  309. {field: 'p99()'},
  310. {field: 'p100()'},
  311. {field: 'p9001()'}, // it's over 9000
  312. {field: 'foobar()'}, // unknown function with no parameter
  313. {field: 'custom_tag'},
  314. {field: 'transaction.duration'}, // should be dropped
  315. {field: 'title'}, // should be dropped
  316. {field: 'unique_count(id)'},
  317. {field: 'apdex(300)'}, // should be dropped
  318. {field: 'user_misery(300)'}, // should be dropped
  319. {field: 'failure_count()'}, // expect this to be transformed to transaction.status
  320. ],
  321. });
  322. result = getExpandedResults(view, {}, {});
  323. expect(result.fields).toEqual([
  324. {field: 'timestamp', width: -1},
  325. {field: 'title'},
  326. {field: 'transaction.duration', width: -1},
  327. {field: 'custom_tag'},
  328. {field: 'transaction.status', width: -1},
  329. ]);
  330. // transforms pXX functions with optional arguments properly
  331. view = new EventView({
  332. ...state,
  333. fields: [
  334. {field: 'p50(transaction.duration)'},
  335. {field: 'p75(measurements.foo)'},
  336. {field: 'p95(measurements.bar)'},
  337. {field: 'p99(measurements.fcp)'},
  338. {field: 'p100(measurements.lcp)'},
  339. ],
  340. });
  341. result = getExpandedResults(view, {}, {});
  342. expect(result.fields).toEqual([
  343. {field: 'transaction.duration', width: -1},
  344. {field: 'measurements.foo', width: -1},
  345. {field: 'measurements.bar', width: -1},
  346. {field: 'measurements.fcp', width: -1},
  347. {field: 'measurements.lcp', width: -1},
  348. ]);
  349. });
  350. it('applies provided additional conditions', () => {
  351. const view = new EventView({
  352. ...state,
  353. fields: [...state.fields, {field: 'measurements.lcp'}, {field: 'measurements.fcp'}],
  354. });
  355. let result = getExpandedResults(view, {extra: 'condition'}, {});
  356. expect(result.query).toEqual('event.type:error extra:condition');
  357. // handles user tag values.
  358. result = getExpandedResults(view, {user: 'id:12735'}, {});
  359. expect(result.query).toEqual('event.type:error user:id:12735');
  360. result = getExpandedResults(view, {user: 'name:uhoh'}, {});
  361. expect(result.query).toEqual('event.type:error user:name:uhoh');
  362. // quotes value
  363. result = getExpandedResults(view, {extra: 'has space'}, {});
  364. expect(result.query).toEqual('event.type:error extra:"has space"');
  365. // appends to existing conditions
  366. result = getExpandedResults(view, {'event.type': 'csp'}, {});
  367. expect(result.query).toEqual('event.type:csp');
  368. // Includes empty strings
  369. result = getExpandedResults(view, {}, {custom_tag: ''});
  370. expect(result.query).toEqual('event.type:error custom_tag:""');
  371. // Includes 0
  372. result = getExpandedResults(view, {}, {custom_tag: 0});
  373. expect(result.query).toEqual('event.type:error custom_tag:0');
  374. // Includes null
  375. result = getExpandedResults(view, {}, {custom_tag: null});
  376. expect(result.query).toEqual('event.type:error custom_tag:""');
  377. // Handles measurements while ignoring null values
  378. result = getExpandedResults(
  379. view,
  380. {},
  381. {'measurements.lcp': 2, 'measurements.fcp': null}
  382. );
  383. expect(result.query).toEqual('event.type:error measurements.lcp:2');
  384. });
  385. it('removes any aggregates in either search conditions or extra conditions', () => {
  386. const view = new EventView({...state, query: 'event.type:error count(id):<10'});
  387. const result = getExpandedResults(view, {'count(id)': '>2'}, {});
  388. expect(result.query).toEqual('event.type:error');
  389. });
  390. it('applies conditions from dataRow map structure based on fields', () => {
  391. const view = new EventView(state);
  392. const result = getExpandedResults(view, {extra: 'condition'}, {title: 'Event title'});
  393. expect(result.query).toEqual('event.type:error extra:condition title:"Event title"');
  394. });
  395. it('applies tag key conditions from event data', () => {
  396. const view = new EventView(state);
  397. const event = {
  398. type: 'error',
  399. tags: [
  400. {key: 'nope', value: 'nope'},
  401. {key: 'custom_tag', value: 'tag_value'},
  402. ],
  403. };
  404. const result = getExpandedResults(view, {}, event);
  405. expect(result.query).toEqual('event.type:error custom_tag:tag_value');
  406. });
  407. it('generate eventview from an empty eventview', () => {
  408. const view = EventView.fromLocation({query: {}});
  409. const result = getExpandedResults(view, {some_tag: 'value'}, {});
  410. expect(result.fields).toEqual([]);
  411. expect(result.query).toEqual('some_tag:value');
  412. });
  413. it('removes equations on aggregates', () => {
  414. const view = new EventView({
  415. ...state,
  416. fields: [
  417. {field: 'count()'},
  418. {field: 'equation|count() / 2'},
  419. {field: 'equation|(count() - count()) + 5'},
  420. ],
  421. });
  422. const result = getExpandedResults(view, {});
  423. expect(result.fields).toEqual([
  424. {
  425. field: 'id',
  426. width: -1,
  427. },
  428. ]);
  429. });
  430. it('keeps equations without aggregates', () => {
  431. const view = new EventView({
  432. ...state,
  433. fields: [{field: 'count()'}, {field: 'equation|transaction.duration / 2'}],
  434. });
  435. const result = getExpandedResults(view, {});
  436. expect(result.fields).toEqual([
  437. {
  438. field: 'equation|transaction.duration / 2',
  439. width: -1,
  440. },
  441. ]);
  442. });
  443. it('applies array value conditions from event data', () => {
  444. const view = new EventView({
  445. ...state,
  446. fields: [...state.fields, {field: 'error.type'}],
  447. });
  448. const event = {
  449. type: 'error',
  450. tags: [
  451. {key: 'nope', value: 'nope'},
  452. {key: 'custom_tag', value: 'tag_value'},
  453. ],
  454. 'error.type': ['DeadSystem Exception', 'RuntimeException', 'RuntimeException'],
  455. };
  456. const result = getExpandedResults(view, {}, event);
  457. expect(result.query).toEqual(
  458. 'event.type:error custom_tag:tag_value error.type:"DeadSystem Exception" error.type:RuntimeException error.type:RuntimeException'
  459. );
  460. });
  461. it('applies project condition to project property', () => {
  462. const view = new EventView(state);
  463. const result = getExpandedResults(view, {'project.id': 1});
  464. expect(result.query).toEqual('event.type:error');
  465. expect(result.project).toEqual([42, 1]);
  466. });
  467. it('applies environment condition to environment property', () => {
  468. const view = new EventView(state);
  469. const result = getExpandedResults(view, {environment: 'dev'});
  470. expect(result.query).toEqual('event.type:error');
  471. expect(result.environment).toEqual(['staging', 'dev']);
  472. });
  473. it('applies tags that overlap globalselection state', () => {
  474. const view = new EventView({
  475. ...state,
  476. fields: [{field: 'project'}, {field: 'environment'}, {field: 'title'}],
  477. });
  478. const event = {
  479. title: 'something bad',
  480. timestamp: '2020-02-13T17:05:46+00:00',
  481. tags: [
  482. {key: 'project', value: '12345'},
  483. {key: 'environment', value: 'earth'},
  484. ],
  485. };
  486. const result = getExpandedResults(view, {}, event);
  487. expect(result.query).toEqual(
  488. 'event.type:error tags[project]:12345 tags[environment]:earth title:"something bad"'
  489. );
  490. expect(result.project).toEqual([42]);
  491. expect(result.environment).toEqual(['staging']);
  492. });
  493. it('applies the normalized user tag', function () {
  494. const view = new EventView({
  495. ...state,
  496. fields: [{field: 'user'}, {field: 'title'}],
  497. });
  498. let event = {
  499. title: 'something bad',
  500. // user context should be ignored.
  501. user: {
  502. id: 1234,
  503. username: 'uhoh',
  504. },
  505. tags: [{key: 'user', value: 'id:1234'}],
  506. };
  507. let result = getExpandedResults(view, {}, event);
  508. expect(result.query).toEqual('event.type:error user:id:1234 title:"something bad"');
  509. event = {
  510. title: 'something bad',
  511. tags: [{key: 'user', value: '1234'}],
  512. };
  513. result = getExpandedResults(view, {}, event);
  514. expect(result.query).toEqual('event.type:error user:1234 title:"something bad"');
  515. });
  516. it('applies the user field in a table row', function () {
  517. const view = new EventView({
  518. ...state,
  519. fields: [{field: 'user'}, {field: 'title'}],
  520. });
  521. const event = {
  522. title: 'something bad',
  523. user: 'id:1234',
  524. };
  525. const result = getExpandedResults(view, {}, event);
  526. expect(result.query).toEqual('event.type:error user:id:1234 title:"something bad"');
  527. });
  528. it('normalizes the timestamp field', () => {
  529. const view = new EventView({
  530. ...state,
  531. fields: [{field: 'timestamp'}],
  532. sorts: [{field: 'timestamp', kind: 'desc'}],
  533. });
  534. const event = {
  535. type: 'error',
  536. timestamp: '2020-02-13T17:05:46+00:00',
  537. };
  538. const result = getExpandedResults(view, {}, event);
  539. expect(result.query).toEqual('event.type:error timestamp:2020-02-13T17:05:46');
  540. });
  541. it('does not duplicate conditions', () => {
  542. const view = new EventView({
  543. ...state,
  544. query: 'event.type:error title:bogus',
  545. });
  546. const event = {
  547. title: 'bogus',
  548. };
  549. const result = getExpandedResults(view, {trace: 'abc123'}, event);
  550. expect(result.query).toEqual('event.type:error trace:abc123 title:bogus');
  551. });
  552. it('applies project as condition if present', () => {
  553. const view = new EventView({
  554. ...state,
  555. query: '',
  556. fields: [{field: 'project'}],
  557. });
  558. const event = {project: 'whoosh'};
  559. const result = getExpandedResults(view, {}, event);
  560. expect(result.query).toEqual('project:whoosh');
  561. });
  562. it('applies project name as condition if present', () => {
  563. const view = new EventView({
  564. ...state,
  565. query: '',
  566. fields: [{field: 'project.name'}],
  567. });
  568. const event = {'project.name': 'whoosh'};
  569. const result = getExpandedResults(view, {}, event);
  570. expect(result.query).toEqual('project.name:whoosh');
  571. });
  572. it('should not trim values that need to be quoted', () => {
  573. const view = new EventView({
  574. ...state,
  575. query: '',
  576. fields: [{field: 'title'}],
  577. });
  578. // needs to be quoted because of whitespace in middle
  579. const event = {title: 'hello there '};
  580. const result = getExpandedResults(view, {}, event);
  581. expect(result.query).toEqual('title:"hello there "');
  582. });
  583. it('should add environment from the data row', () => {
  584. const view = new EventView({
  585. ...state,
  586. environment: [],
  587. query: '',
  588. fields: [{field: 'environment'}],
  589. });
  590. expect(view.environment).toEqual([]);
  591. const event = {environment: 'staging'};
  592. const result = getExpandedResults(view, {}, event);
  593. expect(result.environment).toEqual(['staging']);
  594. });
  595. it('should not add duplicate environment', () => {
  596. const view = new EventView({
  597. ...state,
  598. query: '',
  599. fields: [{field: 'environment'}],
  600. });
  601. expect(view.environment).toEqual(['staging']);
  602. const event = {environment: 'staging'};
  603. const result = getExpandedResults(view, {}, event);
  604. expect(result.environment).toEqual(['staging']);
  605. });
  606. });
  607. describe('downloadAsCsv', function () {
  608. const messageColumn = {name: 'message'};
  609. const environmentColumn = {name: 'environment'};
  610. const countColumn = {name: 'count'};
  611. const userColumn = {name: 'user'};
  612. it('handles raw data', function () {
  613. const result = {
  614. data: [
  615. {message: 'test 1', environment: 'prod'},
  616. {message: 'test 2', environment: 'test'},
  617. ],
  618. };
  619. expect(downloadAsCsv(result, [messageColumn, environmentColumn])).toContain(
  620. encodeURIComponent('message,environment\r\ntest 1,prod\r\ntest 2,test')
  621. );
  622. });
  623. it('handles aggregations', function () {
  624. const result = {
  625. data: [{count: 3}],
  626. };
  627. expect(downloadAsCsv(result, [countColumn])).toContain(encodeURI('count\r\n3'));
  628. });
  629. it('quotes unsafe strings', function () {
  630. const result = {
  631. data: [{message: '=HYPERLINK(http://some-bad-website#)'}],
  632. };
  633. expect(downloadAsCsv(result, [messageColumn])).toContain(
  634. encodeURIComponent("message\r\n'=HYPERLINK(http://some-bad-website#)")
  635. );
  636. });
  637. it('handles the user column', function () {
  638. const result = {
  639. data: [
  640. {message: 'test 0', user: 'name:baz'},
  641. {message: 'test 1', user: 'id:123'},
  642. {message: 'test 2', user: 'email:test@example.com'},
  643. {message: 'test 3', user: 'ip:127.0.0.1'},
  644. ],
  645. };
  646. expect(downloadAsCsv(result, [messageColumn, userColumn])).toContain(
  647. encodeURIComponent(
  648. 'message,user\r\ntest 0,name:baz\r\ntest 1,id:123\r\ntest 2,email:test@example.com\r\ntest 3,ip:127.0.0.1'
  649. )
  650. );
  651. });
  652. });