fieldRenderers.spec.jsx 11 KB

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