tags.spec.tsx 7.6 KB

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