fieldRenderer.spec.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {act} from 'sentry-test/reactTestingLibrary';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import ProjectsStore from 'sentry/stores/projectsStore';
  6. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  7. import {SPAN_OP_RELATIVE_BREAKDOWN_FIELD} from 'sentry/utils/discover/fields';
  8. describe('getFieldRenderer', function () {
  9. let location, context, project, organization, data, user;
  10. beforeEach(function () {
  11. context = initializeOrg({
  12. project: TestStubs.Project(),
  13. });
  14. organization = context.organization;
  15. project = context.project;
  16. act(() => ProjectsStore.loadInitialData([project]));
  17. user = 'email:text@example.com';
  18. location = {
  19. pathname: '/events',
  20. query: {},
  21. };
  22. data = {
  23. id: '1',
  24. team_key_transaction: 1,
  25. title: 'ValueError: something bad',
  26. transaction: 'api.do_things',
  27. boolValue: 1,
  28. numeric: 1.23,
  29. createdAt: new Date(2019, 9, 3, 12, 13, 14),
  30. url: '/example',
  31. project: project.slug,
  32. release: 'F2520C43515BD1F0E8A6BD46233324641A370BF6',
  33. user,
  34. 'span_ops_breakdown.relative': '',
  35. 'spans.browser': 10,
  36. 'spans.db': 30,
  37. 'spans.http': 15,
  38. 'spans.resource': 20,
  39. 'spans.total.time': 75,
  40. 'transaction.duration': 75,
  41. 'timestamp.to_day': '2021-09-05T00:00:00+00:00',
  42. lifetimeCount: 10000,
  43. filteredCount: 3000,
  44. count: 6000,
  45. selectionDateString: 'last 7 days',
  46. };
  47. MockApiClient.addMockResponse({
  48. url: `/organizations/${organization.slug}/projects/${project.slug}/`,
  49. body: project,
  50. });
  51. MockApiClient.addMockResponse({
  52. url: `/organizations/${organization.slug}/key-transactions/`,
  53. method: 'POST',
  54. });
  55. MockApiClient.addMockResponse({
  56. url: `/organizations/${organization.slug}/key-transactions/`,
  57. method: 'DELETE',
  58. });
  59. MockApiClient.addMockResponse({
  60. url: `/organizations/${organization.slug}/projects/`,
  61. body: [project],
  62. });
  63. });
  64. it('can render string fields', function () {
  65. const renderer = getFieldRenderer('url', {url: 'string'});
  66. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  67. const text = wrapper.find('Container');
  68. expect(text.text()).toEqual(data.url);
  69. });
  70. it('can render empty string fields', function () {
  71. const renderer = getFieldRenderer('url', {url: 'string'});
  72. data.url = '';
  73. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  74. const value = wrapper.find('EmptyValueContainer');
  75. expect(value).toHaveLength(1);
  76. expect(value.text()).toEqual('(empty string)');
  77. });
  78. it('can render boolean fields', function () {
  79. const renderer = getFieldRenderer('boolValue', {boolValue: 'boolean'});
  80. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  81. const text = wrapper.find('Container');
  82. expect(text.text()).toEqual('true');
  83. });
  84. it('can render integer fields', function () {
  85. const renderer = getFieldRenderer('numeric', {numeric: 'integer'});
  86. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  87. const value = wrapper.find('Count');
  88. expect(value).toHaveLength(1);
  89. expect(value.props().value).toEqual(data.numeric);
  90. });
  91. it('can render date fields', function () {
  92. const renderer = getFieldRenderer('createdAt', {createdAt: 'date'});
  93. expect(renderer).toBeInstanceOf(Function);
  94. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  95. const value = wrapper.find('FieldDateTime');
  96. expect(value).toHaveLength(1);
  97. expect(value.props().date).toEqual(data.createdAt);
  98. });
  99. it('can render null date fields', function () {
  100. const renderer = getFieldRenderer('nope', {nope: 'date'});
  101. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  102. const value = wrapper.find('FieldDateTime');
  103. expect(value).toHaveLength(0);
  104. expect(wrapper.text()).toEqual('(no value)');
  105. });
  106. it('can render timestamp.to_day', function () {
  107. // Set timezone
  108. ConfigStore.loadInitialData({
  109. user: {
  110. options: {
  111. timezone: 'America/Los_Angeles',
  112. },
  113. },
  114. });
  115. const renderer = getFieldRenderer('timestamp.to_day', {'timestamp.to_day': 'date'});
  116. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  117. const text = wrapper.find('Container');
  118. expect(text.text()).toEqual('Sep 5, 2021');
  119. });
  120. it('can render error.handled values', function () {
  121. const renderer = getFieldRenderer('error.handled', {'error.handled': 'boolean'});
  122. // Should render the same as the filter.
  123. // ie. all 1 or null
  124. let wrapper = mountWithTheme(
  125. renderer({'error.handled': [0, 1]}, {location, organization})
  126. );
  127. expect(wrapper.text()).toEqual('false');
  128. wrapper = mountWithTheme(
  129. renderer({'error.handled': [1, 0]}, {location, organization})
  130. );
  131. expect(wrapper.text()).toEqual('false');
  132. wrapper = mountWithTheme(
  133. renderer({'error.handled': [null, 0]}, {location, organization})
  134. );
  135. expect(wrapper.text()).toEqual('false');
  136. wrapper = mountWithTheme(
  137. renderer({'error.handled': [0, null]}, {location, organization})
  138. );
  139. expect(wrapper.text()).toEqual('false');
  140. wrapper = mountWithTheme(
  141. renderer({'error.handled': [null, 1]}, {location, organization})
  142. );
  143. expect(wrapper.text()).toEqual('true');
  144. wrapper = mountWithTheme(
  145. renderer({'error.handled': [1, null]}, {location, organization})
  146. );
  147. expect(wrapper.text()).toEqual('true');
  148. // null = true for error.handled data.
  149. wrapper = mountWithTheme(
  150. renderer({'error.handled': [null]}, {location, organization})
  151. );
  152. expect(wrapper.text()).toEqual('true');
  153. // Default events won't have error.handled and will return an empty list.
  154. wrapper = mountWithTheme(renderer({'error.handled': []}, {location, organization}));
  155. expect(wrapper.text()).toEqual('(no value)');
  156. // Transactions will have null for error.handled as the 'tag' won't be set.
  157. wrapper = mountWithTheme(renderer({'error.handled': null}, {location, organization}));
  158. expect(wrapper.text()).toEqual('(no value)');
  159. });
  160. it('can render user fields with aliased user', function () {
  161. const renderer = getFieldRenderer('user', {user: 'string'});
  162. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  163. const badge = wrapper.find('UserBadge');
  164. expect(badge).toHaveLength(1);
  165. const value = wrapper.find('StyledNameAndEmail');
  166. expect(value).toHaveLength(1);
  167. expect(value.text()).toEqual('text@example.com');
  168. });
  169. it('can render null user fields', function () {
  170. const renderer = getFieldRenderer('user', {user: 'string'});
  171. delete data.user;
  172. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  173. const badge = wrapper.find('UserBadge');
  174. expect(badge).toHaveLength(0);
  175. const value = wrapper.find('EmptyValueContainer');
  176. expect(value).toHaveLength(1);
  177. expect(value.text()).toEqual('(no value)');
  178. });
  179. it('can render null release fields', function () {
  180. const renderer = getFieldRenderer('release', {release: 'string'});
  181. delete data.release;
  182. const wrapper = mountWithTheme(renderer(data, {location, organization}));
  183. const value = wrapper.find('EmptyValueContainer');
  184. expect(value).toHaveLength(1);
  185. expect(value.text()).toEqual('(no value)');
  186. });
  187. it('can render project as an avatar', function () {
  188. const renderer = getFieldRenderer('project', {project: 'string'});
  189. const wrapper = mountWithTheme(
  190. renderer(data, {location, organization}),
  191. context.routerContext
  192. );
  193. const value = wrapper.find('ProjectBadge');
  194. expect(value).toHaveLength(1);
  195. expect(value.text()).toEqual(project.slug);
  196. });
  197. it('can render project id as an avatar', async function () {
  198. const renderer = getFieldRenderer('project', {project: 'number'});
  199. data = {...data, project: parseInt(project.id, 10)};
  200. const wrapper = mountWithTheme(
  201. renderer(data, {location, organization}),
  202. context.routerContext
  203. );
  204. await tick();
  205. const value = wrapper.find('ProjectBadge');
  206. expect(value).toHaveLength(1);
  207. expect(value.text()).toEqual(project.slug);
  208. });
  209. it('can render team key transaction as a star with the dropdown', function () {
  210. const renderer = getFieldRenderer('team_key_transaction', {
  211. team_key_transaction: 'boolean',
  212. });
  213. const wrapper = mountWithTheme(
  214. renderer(data, {location, organization}),
  215. context.routerContext
  216. );
  217. const value = wrapper.find('IconStar');
  218. expect(value).toHaveLength(1);
  219. expect(value.props().isSolid).toBeTruthy();
  220. expect(wrapper.find('TeamKeyTransaction')).toHaveLength(1);
  221. });
  222. it('can render team key transaction as a star without the dropdown', function () {
  223. const renderer = getFieldRenderer('team_key_transaction', {
  224. team_key_transaction: 'boolean',
  225. });
  226. delete data.project;
  227. const wrapper = mountWithTheme(
  228. renderer(data, {location, organization}),
  229. context.routerContext
  230. );
  231. const value = wrapper.find('IconStar');
  232. expect(value).toHaveLength(1);
  233. expect(value.props().isSolid).toBeTruthy();
  234. // Since there is no project column, it is not wrapped with the dropdown
  235. expect(wrapper.find('TeamKeyTransaction')).toHaveLength(0);
  236. });
  237. describe('ops breakdown', () => {
  238. const getWidth = (wrapper, index) =>
  239. wrapper.children().children().at(index).getDOMNode().style.width;
  240. it('can render operation breakdowns', function () {
  241. const renderer = getFieldRenderer(SPAN_OP_RELATIVE_BREAKDOWN_FIELD, {
  242. [SPAN_OP_RELATIVE_BREAKDOWN_FIELD]: 'string',
  243. });
  244. const wrapper = mountWithTheme(
  245. renderer(data, {location, organization}),
  246. context.routerContext
  247. );
  248. const value = wrapper.find('RelativeOpsBreakdown');
  249. expect(value).toHaveLength(1);
  250. expect(getWidth(value, 0)).toEqual('13.333%');
  251. expect(getWidth(value, 1)).toEqual('40.000%');
  252. expect(getWidth(value, 2)).toEqual('20.000%');
  253. expect(getWidth(value, 3)).toEqual('26.667%');
  254. });
  255. it('renders operation breakdowns in sorted order when a sort field is provided', function () {
  256. const renderer = getFieldRenderer(SPAN_OP_RELATIVE_BREAKDOWN_FIELD, {
  257. [SPAN_OP_RELATIVE_BREAKDOWN_FIELD]: 'string',
  258. });
  259. const wrapper = mountWithTheme(
  260. renderer(data, {
  261. location,
  262. organization,
  263. eventView: {sorts: [{field: 'spans.db'}]},
  264. }),
  265. context.routerContext
  266. );
  267. const value = wrapper.find('RelativeOpsBreakdown');
  268. expect(value).toHaveLength(1);
  269. expect(getWidth(value, 0)).toEqual('40.000%');
  270. expect(getWidth(value, 1)).toEqual('13.333%');
  271. expect(getWidth(value, 2)).toEqual('20.000%');
  272. expect(getWidth(value, 3)).toEqual('26.667%');
  273. });
  274. });
  275. });