fields.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {
  3. aggregateMultiPlotType,
  4. aggregateOutputType,
  5. ColumnValueType,
  6. explodeField,
  7. fieldAlignment,
  8. generateAggregateFields,
  9. getAggregateAlias,
  10. isAggregateEquation,
  11. isAggregateField,
  12. isMeasurement,
  13. measurementType,
  14. parseFunction,
  15. } from 'sentry/utils/discover/fields';
  16. describe('parseFunction', function () {
  17. it('returns null on non aggregate fields', function () {
  18. expect(parseFunction('field')).toEqual(null);
  19. expect(parseFunction('under_field')).toEqual(null);
  20. expect(parseFunction('foo.bar.is-Enterprise_42')).toEqual(null);
  21. });
  22. it('handles 0 arg functions', function () {
  23. expect(parseFunction('count()')).toEqual({
  24. name: 'count',
  25. arguments: [],
  26. });
  27. expect(parseFunction('count_unique()')).toEqual({
  28. name: 'count_unique',
  29. arguments: [],
  30. });
  31. });
  32. it('handles 1 arg functions', function () {
  33. expect(parseFunction('count(id)')).toEqual({
  34. name: 'count',
  35. arguments: ['id'],
  36. });
  37. expect(parseFunction('count_unique(user)')).toEqual({
  38. name: 'count_unique',
  39. arguments: ['user'],
  40. });
  41. expect(parseFunction('count_unique(issue.id)')).toEqual({
  42. name: 'count_unique',
  43. arguments: ['issue.id'],
  44. });
  45. expect(parseFunction('count(foo.bar.is-Enterprise_42)')).toEqual({
  46. name: 'count',
  47. arguments: ['foo.bar.is-Enterprise_42'],
  48. });
  49. });
  50. it('handles 2 arg functions', function () {
  51. expect(parseFunction('percentile(transaction.duration,0.81)')).toEqual({
  52. name: 'percentile',
  53. arguments: ['transaction.duration', '0.81'],
  54. });
  55. expect(parseFunction('percentile(transaction.duration, 0.11)')).toEqual({
  56. name: 'percentile',
  57. arguments: ['transaction.duration', '0.11'],
  58. });
  59. });
  60. it('handles 3 arg functions', function () {
  61. expect(parseFunction('count_if(transaction.duration,greater,0.81)')).toEqual({
  62. name: 'count_if',
  63. arguments: ['transaction.duration', 'greater', '0.81'],
  64. });
  65. expect(parseFunction('count_if(some_tag,greater,"0.81,123,152,()")')).toEqual({
  66. name: 'count_if',
  67. arguments: ['some_tag', 'greater', '"0.81,123,152,()"'],
  68. });
  69. expect(parseFunction('function(foo, bar, baz)')).toEqual({
  70. name: 'function',
  71. arguments: ['foo', 'bar', 'baz'],
  72. });
  73. });
  74. it('handles 4 arg functions', function () {
  75. expect(parseFunction('to_other(release,"0.81,123,152,()",others,current)')).toEqual({
  76. name: 'to_other',
  77. arguments: ['release', '"0.81,123,152,()"', 'others', 'current'],
  78. });
  79. });
  80. });
  81. describe('getAggregateAlias', function () {
  82. it('no-ops simple fields', function () {
  83. expect(getAggregateAlias('field')).toEqual('field');
  84. expect(getAggregateAlias('under_field')).toEqual('under_field');
  85. expect(getAggregateAlias('foo.bar.is-Enterprise_42')).toEqual(
  86. 'foo.bar.is-Enterprise_42'
  87. );
  88. });
  89. it('handles 0 arg functions', function () {
  90. expect(getAggregateAlias('count()')).toEqual('count');
  91. expect(getAggregateAlias('count_unique()')).toEqual('count_unique');
  92. });
  93. it('handles 1 arg functions', function () {
  94. expect(getAggregateAlias('count(id)')).toEqual('count_id');
  95. expect(getAggregateAlias('count_unique(user)')).toEqual('count_unique_user');
  96. expect(getAggregateAlias('count_unique(issue.id)')).toEqual('count_unique_issue_id');
  97. expect(getAggregateAlias('count(foo.bar.is-Enterprise_42)')).toEqual(
  98. 'count_foo_bar_is_Enterprise_42'
  99. );
  100. });
  101. it('handles 2 arg functions', function () {
  102. expect(getAggregateAlias('percentile(transaction.duration,0.81)')).toEqual(
  103. 'percentile_transaction_duration_0_81'
  104. );
  105. expect(getAggregateAlias('percentile(transaction.duration, 0.11)')).toEqual(
  106. 'percentile_transaction_duration_0_11'
  107. );
  108. });
  109. it('handles to_other with symbols', function () {
  110. expect(
  111. getAggregateAlias('to_other(release,"release:beta@1.1.1 (2)",others,current)')
  112. ).toEqual('to_other_release__release_beta_1_1_1__2___others_current');
  113. });
  114. });
  115. describe('isAggregateField', function () {
  116. it('detects aliases', function () {
  117. expect(isAggregateField('p888')).toBe(false);
  118. expect(isAggregateField('other_field')).toBe(false);
  119. expect(isAggregateField('foo.bar.is-Enterprise_42')).toBe(false);
  120. });
  121. it('detects functions', function () {
  122. expect(isAggregateField('count()')).toBe(true);
  123. expect(isAggregateField('p75()')).toBe(true);
  124. expect(isAggregateField('percentile(transaction.duration, 0.55)')).toBe(true);
  125. expect(isAggregateField('last_seen()')).toBe(true);
  126. expect(isAggregateField('thing(')).toBe(false);
  127. expect(isAggregateField('unique_count(user)')).toBe(true);
  128. expect(isAggregateField('unique_count(foo.bar.is-Enterprise_42)')).toBe(true);
  129. });
  130. });
  131. describe('isAggregateEquation', function () {
  132. it('detects functions', function () {
  133. expect(isAggregateEquation('equation|5 + count()')).toBe(true);
  134. expect(
  135. isAggregateEquation('equation|percentile(transaction.duration, 0.55) / count()')
  136. ).toBe(true);
  137. expect(isAggregateEquation('equation|(5 + 5) + (count() - 2)')).toBe(true);
  138. });
  139. it('detects lack of functions', function () {
  140. expect(isAggregateEquation('equation|5 + 5')).toBe(false);
  141. expect(isAggregateEquation('equation|(5 + 5)')).toBe(false);
  142. expect(isAggregateEquation('equation|5 + (thing - other_thing)')).toBe(false);
  143. expect(isAggregateEquation('equation|5+(thing-other_thing)')).toBe(false);
  144. });
  145. });
  146. describe('measurement', function () {
  147. it('isMeasurement', function () {
  148. expect(isMeasurement('measurements.fp')).toBe(true);
  149. expect(isMeasurement('measurements.fcp')).toBe(true);
  150. expect(isMeasurement('measurements.lcp')).toBe(true);
  151. expect(isMeasurement('measurements.fid')).toBe(true);
  152. expect(isMeasurement('measurements.foo')).toBe(true);
  153. expect(isMeasurement('measurements.bar')).toBe(true);
  154. expect(isMeasurement('timestamp')).toBe(false);
  155. expect(isMeasurement('project.id')).toBe(false);
  156. expect(isMeasurement('transaction')).toBe(false);
  157. expect(isMeasurement('max(timestamp)')).toBe(false);
  158. expect(isMeasurement('percentile(measurements.fcp, 0.5)')).toBe(false);
  159. });
  160. it('measurementType', function () {
  161. expect(measurementType('measurements.fp')).toBe('duration');
  162. expect(measurementType('measurements.fcp')).toBe('duration');
  163. expect(measurementType('measurements.lcp')).toBe('duration');
  164. expect(measurementType('measurements.fid')).toBe('duration');
  165. expect(measurementType('measurements.foo')).toBe('number');
  166. expect(measurementType('measurements.bar')).toBe('number');
  167. });
  168. });
  169. describe('explodeField', function () {
  170. it('explodes fields', function () {
  171. expect(explodeField({field: 'foobar'})).toEqual({
  172. kind: 'field',
  173. field: 'foobar',
  174. });
  175. // has width
  176. expect(explodeField({field: 'foobar', width: 123})).toEqual({
  177. kind: 'field',
  178. field: 'foobar',
  179. });
  180. // has aggregation
  181. expect(explodeField({field: 'count(foobar)', width: 123})).toEqual({
  182. kind: 'function',
  183. function: ['count', 'foobar', undefined, undefined],
  184. });
  185. // custom tag
  186. expect(explodeField({field: 'foo.bar.is-Enterprise_42', width: 123})).toEqual({
  187. kind: 'field',
  188. field: 'foo.bar.is-Enterprise_42',
  189. });
  190. // custom tag with aggregation
  191. expect(explodeField({field: 'count(foo.bar.is-Enterprise_42)', width: 123})).toEqual({
  192. kind: 'function',
  193. function: ['count', 'foo.bar.is-Enterprise_42', undefined, undefined],
  194. });
  195. });
  196. });
  197. describe('aggregateOutputType', function () {
  198. it('handles unknown fields', function () {
  199. expect(aggregateOutputType('')).toEqual('number');
  200. expect(aggregateOutputType('blerg')).toEqual('number');
  201. });
  202. it('handles duration functions', function () {
  203. expect(aggregateOutputType('p50()')).toEqual('duration');
  204. expect(aggregateOutputType('p75()')).toEqual('duration');
  205. expect(aggregateOutputType('p95()')).toEqual('duration');
  206. expect(aggregateOutputType('p99()')).toEqual('duration');
  207. expect(aggregateOutputType('p100()')).toEqual('duration');
  208. expect(aggregateOutputType('p50(transaction.duration)')).toEqual('duration');
  209. expect(aggregateOutputType('p75(transaction.duration)')).toEqual('duration');
  210. expect(aggregateOutputType('p95(transaction.duration)')).toEqual('duration');
  211. expect(aggregateOutputType('p99(transaction.duration)')).toEqual('duration');
  212. expect(aggregateOutputType('p100(transaction.duration)')).toEqual('duration');
  213. expect(aggregateOutputType('percentile(transaction.duration, 0.51)')).toEqual(
  214. 'duration'
  215. );
  216. expect(aggregateOutputType('percentile(transaction.duration,0.99)')).toEqual(
  217. 'duration'
  218. );
  219. });
  220. it('handles percentage functions', function () {
  221. expect(aggregateOutputType('failure_rate()')).toEqual('percentage');
  222. });
  223. it('handles number functions', function () {
  224. expect(aggregateOutputType('apdex()')).toEqual('number');
  225. expect(aggregateOutputType('apdex(500)')).toEqual('number');
  226. expect(aggregateOutputType('count_miserable(user, 500)')).toEqual('number');
  227. expect(aggregateOutputType('user_misery(500)')).toEqual('number');
  228. expect(aggregateOutputType('eps()')).toEqual('number');
  229. expect(aggregateOutputType('epm()')).toEqual('number');
  230. });
  231. it('handles inherit functions', function () {
  232. expect(aggregateOutputType('sum(transaction.duration)')).toEqual('duration');
  233. expect(aggregateOutputType('sum(stack.colno)')).toEqual('number');
  234. expect(aggregateOutputType('min(stack.colno)')).toEqual('number');
  235. expect(aggregateOutputType('min(timestamp)')).toEqual('date');
  236. expect(aggregateOutputType('max(stack.colno)')).toEqual('number');
  237. expect(aggregateOutputType('max(timestamp)')).toEqual('date');
  238. });
  239. it('handles measurements', function () {
  240. expect(aggregateOutputType('sum(measurements.fcp)')).toEqual('duration');
  241. expect(aggregateOutputType('min(measurements.fcp)')).toEqual('duration');
  242. expect(aggregateOutputType('max(measurements.fcp)')).toEqual('duration');
  243. expect(aggregateOutputType('avg(measurements.fcp)')).toEqual('duration');
  244. expect(aggregateOutputType('percentile(measurements.fcp, 0.5)')).toEqual('duration');
  245. expect(aggregateOutputType('sum(measurements.bar)')).toEqual('number');
  246. expect(aggregateOutputType('min(measurements.bar)')).toEqual('number');
  247. expect(aggregateOutputType('max(measurements.bar)')).toEqual('number');
  248. expect(aggregateOutputType('avg(measurements.bar)')).toEqual('number');
  249. expect(aggregateOutputType('percentile(measurements.bar, 0.5)')).toEqual('number');
  250. expect(aggregateOutputType('p50(measurements.bar)')).toEqual('number');
  251. expect(aggregateOutputType('p75(measurements.bar)')).toEqual('number');
  252. expect(aggregateOutputType('p95(measurements.bar)')).toEqual('number');
  253. expect(aggregateOutputType('p99(measurements.bar)')).toEqual('number');
  254. expect(aggregateOutputType('p100(measurements.bar)')).toEqual('number');
  255. });
  256. });
  257. describe('aggregateMultiPlotType', function () {
  258. it('handles unknown functions', function () {
  259. expect(aggregateMultiPlotType('blerg')).toBe('area');
  260. expect(aggregateMultiPlotType('blerg(uhoh)')).toBe('area');
  261. });
  262. it('handles known functions', function () {
  263. expect(aggregateMultiPlotType('sum(transaction.duration)')).toBe('area');
  264. expect(aggregateMultiPlotType('p95()')).toBe('line');
  265. expect(aggregateMultiPlotType('equation|sum(transaction.duration) / 2')).toBe('line');
  266. });
  267. });
  268. describe('generateAggregateFields', function () {
  269. const organization = Organization();
  270. it('gets default aggregates', function () {
  271. expect(generateAggregateFields(organization, [])).toContainEqual({field: 'count()'});
  272. });
  273. it('includes fields from eventFields', function () {
  274. expect(
  275. generateAggregateFields(organization, [{field: 'not_real_aggregate()'}])
  276. ).toContainEqual({field: 'not_real_aggregate()'});
  277. });
  278. it('excludes fields from aggregates', function () {
  279. expect(generateAggregateFields(organization, [], ['count()'])).not.toContainEqual({
  280. field: 'count()',
  281. });
  282. });
  283. });
  284. describe('fieldAlignment()', function () {
  285. it('works with only field name', function () {
  286. expect(fieldAlignment('event.type')).toEqual('left');
  287. // Should be right, but we don't have any type data.
  288. expect(fieldAlignment('transaction.duration')).toEqual('left');
  289. });
  290. it('works with type parameter', function () {
  291. expect(fieldAlignment('transaction.duration', 'duration')).toEqual('right');
  292. expect(fieldAlignment('device.battery_level', 'number')).toEqual('right');
  293. expect(fieldAlignment('min(timestamp)', 'date')).toEqual('left');
  294. });
  295. it('can use table metadata', function () {
  296. const meta: Record<string, ColumnValueType> = {
  297. 'transaction.duration': 'duration',
  298. };
  299. expect(fieldAlignment('transaction.duration', 'never', meta)).toEqual('right');
  300. expect(fieldAlignment('transaction.duration', undefined, meta)).toEqual('right');
  301. expect(fieldAlignment('title', undefined, meta)).toEqual('left');
  302. });
  303. });