tags.spec.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. render,
  4. screen,
  5. userEvent,
  6. waitForElementToBeRemoved,
  7. } from 'sentry-test/reactTestingLibrary';
  8. import EventView from 'sentry/utils/discover/eventView';
  9. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  10. import {Tags} from 'sentry/views/discover/tags';
  11. // There seem to be no types for this, but it's essentially what is passed to the
  12. // EventView constructor
  13. const commonQueryConditions = {
  14. additionalConditions: new MutableSearch([]),
  15. display: '',
  16. start: '',
  17. end: '',
  18. id: '',
  19. name: '',
  20. project: [1],
  21. environment: [],
  22. topEvents: '',
  23. yAxis: '',
  24. createdBy: TestStubs.User(),
  25. team: TestStubs.Team(),
  26. statsPeriod: '14d',
  27. };
  28. describe('Tags', function () {
  29. function generateUrl(key, value) {
  30. return `/endpoint/${key}/${value}`;
  31. }
  32. const org = TestStubs.Organization();
  33. beforeEach(function () {
  34. MockApiClient.addMockResponse({
  35. url: `/organizations/${org.slug}/events-facets/`,
  36. body: [
  37. {
  38. key: 'release',
  39. topValues: [{count: 30, value: '123abcd', name: '123abcd'}],
  40. },
  41. {
  42. key: 'environment',
  43. topValues: [
  44. {count: 20, value: 'abcd123', name: 'abcd123'},
  45. {count: 10, value: 'anotherOne', name: 'anotherOne'},
  46. ],
  47. },
  48. {
  49. key: 'color',
  50. topValues: [
  51. {count: 10, value: 'red', name: 'red'},
  52. {count: 5, value: 'blue', name: 'blue'},
  53. {count: 5, value: 'green', name: 'green'},
  54. {count: 5, value: 'yellow', name: 'yellow'},
  55. {count: 5, value: 'orange', name: 'orange'},
  56. ],
  57. },
  58. ],
  59. });
  60. });
  61. afterEach(function () {
  62. MockApiClient.clearMockResponses();
  63. });
  64. it('renders', async function () {
  65. const view = new EventView({
  66. fields: [],
  67. sorts: [],
  68. query: 'event.type:csp',
  69. ...commonQueryConditions,
  70. });
  71. render(
  72. <Tags
  73. eventView={view}
  74. api={new MockApiClient()}
  75. totalValues={30}
  76. organization={org}
  77. location={{...TestStubs.location(), query: {}}}
  78. generateUrl={generateUrl}
  79. confirmedQuery={false}
  80. />
  81. );
  82. // component is in loading state
  83. expect(screen.getAllByTestId('loading-placeholder')[0]).toBeInTheDocument();
  84. // component has loaded
  85. await waitForElementToBeRemoved(
  86. () => screen.queryAllByTestId('loading-placeholder')[0]
  87. );
  88. });
  89. it('creates URLs with generateUrl', async function () {
  90. const view = new EventView({
  91. fields: [],
  92. sorts: [],
  93. query: 'event.type:csp',
  94. ...commonQueryConditions,
  95. });
  96. const initialData = initializeOrg({
  97. organization: org,
  98. router: {
  99. location: {query: {}},
  100. },
  101. });
  102. render(
  103. <Tags
  104. eventView={view}
  105. api={new MockApiClient()}
  106. organization={org}
  107. totalValues={30}
  108. location={initialData.router.location}
  109. generateUrl={generateUrl}
  110. confirmedQuery={false}
  111. />,
  112. {context: initialData.routerContext}
  113. );
  114. // component has loaded
  115. await waitForElementToBeRemoved(
  116. () => screen.queryAllByTestId('loading-placeholder')[0]
  117. );
  118. await userEvent.click(screen.getByText('environment'));
  119. await userEvent.click(
  120. screen.getByRole('link', {
  121. name: 'environment, abcd123, 66% of all events. View events with this tag value.',
  122. })
  123. );
  124. expect(initialData.router.push).toHaveBeenCalledWith('/endpoint/environment/abcd123');
  125. });
  126. it('renders tag keys', async function () {
  127. const view = new EventView({
  128. fields: [],
  129. sorts: [],
  130. query: 'event.type:csp',
  131. ...commonQueryConditions,
  132. });
  133. render(
  134. <Tags
  135. eventView={view}
  136. api={new MockApiClient()}
  137. totalValues={30}
  138. organization={org}
  139. location={{...TestStubs.location(), query: {}}}
  140. generateUrl={generateUrl}
  141. confirmedQuery={false}
  142. />
  143. );
  144. await waitForElementToBeRemoved(
  145. () => screen.queryAllByTestId('loading-placeholder')[0]
  146. );
  147. expect(screen.getByRole('listitem', {name: 'release'})).toBeInTheDocument();
  148. expect(screen.getByRole('listitem', {name: 'environment'})).toBeInTheDocument();
  149. expect(screen.getByRole('listitem', {name: 'color'})).toBeInTheDocument();
  150. });
  151. it('excludes top tag values on current page query', async function () {
  152. const initialData = initializeOrg({
  153. organization: org,
  154. router: {
  155. location: {pathname: '/organizations/org-slug/discover/homepage/', query: {}},
  156. },
  157. });
  158. const view = new EventView({
  159. fields: [],
  160. sorts: [],
  161. query: '',
  162. ...commonQueryConditions,
  163. });
  164. render(
  165. <Tags
  166. eventView={view}
  167. api={new MockApiClient()}
  168. totalValues={30}
  169. organization={org}
  170. location={initialData.router.location}
  171. generateUrl={generateUrl}
  172. confirmedQuery={false}
  173. />,
  174. {context: initialData.routerContext}
  175. );
  176. await waitForElementToBeRemoved(
  177. () => screen.queryAllByTestId('loading-placeholder')[0]
  178. );
  179. await userEvent.click(
  180. screen.getByRole('button', {name: 'Expand color tag distribution'})
  181. );
  182. expect(
  183. screen.getByRole('link', {
  184. name: 'Other color tag values, 16% of all events. View other tags.',
  185. })
  186. ).toHaveAttribute(
  187. 'href',
  188. '/organizations/org-slug/discover/homepage/?query=%21color%3A%5Bred%2C%20blue%2C%20green%2C%20yellow%5D'
  189. );
  190. });
  191. it('has a Show More button when there are more tags', async () => {
  192. MockApiClient.addMockResponse({
  193. url: `/organizations/${org.slug}/events-facets/`,
  194. match: [MockApiClient.matchQuery({cursor: undefined})],
  195. headers: {
  196. Link:
  197. '<http://localhost/api/0new /organizations()/org-slug/events-facets/?cursor=0:0:1>; rel="previous"; results="false"; cursor="0:0:1",' +
  198. '<http://localhost/api/0new /organizations()/org-slug/events-facets/?cursor=0:10:0>; rel="next"; results="true"; cursor="0:10:0"',
  199. },
  200. body: [
  201. {
  202. key: 'release',
  203. topValues: [{count: 30, value: '123abcd', name: '123abcd'}],
  204. },
  205. ],
  206. });
  207. const mockRequest = MockApiClient.addMockResponse({
  208. url: `/organizations/${org.slug}/events-facets/`,
  209. match: [MockApiClient.matchQuery({cursor: '0:10:0'})],
  210. body: [],
  211. headers: {
  212. Link:
  213. '<http://localhost/api/0new /organizations()/org-slug/events-facets/?cursor=0:0:1>; rel="previous"; results="false"; cursor="0:10:1",' +
  214. '<http://localhost/api/0new /organizations()/org-slug/events-facets/?cursor=0:20:0>; rel="next"; results="false"; cursor="0:20:0"',
  215. },
  216. });
  217. const view = new EventView({
  218. fields: [],
  219. sorts: [],
  220. query: '',
  221. ...commonQueryConditions,
  222. });
  223. render(
  224. <Tags
  225. eventView={view}
  226. api={new MockApiClient()}
  227. totalValues={30}
  228. organization={org}
  229. location={{...TestStubs.location(), query: {}}}
  230. generateUrl={generateUrl}
  231. confirmedQuery={false}
  232. />
  233. );
  234. await waitForElementToBeRemoved(
  235. () => screen.queryAllByTestId('loading-placeholder')[0]
  236. );
  237. expect(screen.getByRole('button', {name: 'Show More'})).toBeInTheDocument();
  238. await userEvent.click(screen.getByRole('button', {name: 'Show More'}));
  239. expect(mockRequest).toHaveBeenCalled();
  240. // Button should disappear when there are no more tags to load
  241. expect(screen.queryByRole('button', {name: 'Show More'})).not.toBeInTheDocument();
  242. });
  243. });