bigNumberWidget.spec.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  2. import {textWithMarkupMatcher} from 'sentry-test/utils';
  3. import {BigNumberWidget} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidget';
  4. describe('BigNumberWidget', () => {
  5. describe('Layout', () => {
  6. it('Renders', () => {
  7. render(
  8. <BigNumberWidget
  9. title="EPS"
  10. description="Number of events per second"
  11. data={[
  12. {
  13. 'eps()': 0.01087819860850493,
  14. },
  15. ]}
  16. meta={{
  17. fields: {
  18. 'eps()': 'rate',
  19. },
  20. units: {
  21. 'eps()': '1/second',
  22. },
  23. }}
  24. />
  25. );
  26. expect(screen.getByText('0.0109/s')).toBeInTheDocument();
  27. });
  28. });
  29. describe('Visualization', () => {
  30. it('Explains missing data', () => {
  31. render(
  32. <BigNumberWidget
  33. data={[{}]}
  34. meta={{
  35. fields: {
  36. 'p95(span.duration)': 'number',
  37. },
  38. }}
  39. />
  40. );
  41. expect(screen.getByText('No Data')).toBeInTheDocument();
  42. });
  43. it('Explains non-numeric data', () => {
  44. render(
  45. <BigNumberWidget
  46. data={[
  47. {
  48. 'count()': Infinity,
  49. },
  50. ]}
  51. meta={{
  52. fields: {
  53. 'count()': 'number',
  54. },
  55. }}
  56. />
  57. );
  58. expect(screen.getByText('Value is not a finite number.')).toBeInTheDocument();
  59. });
  60. it('Formats dates', () => {
  61. render(
  62. <BigNumberWidget
  63. data={[
  64. {
  65. 'max(timestamp)': '2024-10-17T16:08:07+00:00',
  66. },
  67. ]}
  68. meta={{
  69. fields: {
  70. 'max(timestamp)': 'date',
  71. },
  72. units: {
  73. 'max(timestamp)': null,
  74. },
  75. }}
  76. />
  77. );
  78. expect(screen.getByText('Oct 17, 2024 4:08:07 PM UTC')).toBeInTheDocument();
  79. });
  80. it('Renders strings', () => {
  81. render(
  82. <BigNumberWidget
  83. data={[
  84. {
  85. 'any(transaction)': '/api/0/fetch',
  86. },
  87. ]}
  88. meta={{
  89. fields: {
  90. 'max(timestamp)': 'string',
  91. },
  92. }}
  93. />
  94. );
  95. expect(screen.getByText('/api/0/fetch')).toBeInTheDocument();
  96. });
  97. it('Formats duration data', () => {
  98. render(
  99. <BigNumberWidget
  100. data={[
  101. {
  102. 'p95(span.duration)': 17.28,
  103. },
  104. ]}
  105. meta={{
  106. fields: {
  107. 'p95(span.duration)': 'duration',
  108. },
  109. units: {
  110. 'p95(span.duration)': 'milliseconds',
  111. },
  112. }}
  113. />
  114. );
  115. expect(screen.getByText('17.28ms')).toBeInTheDocument();
  116. });
  117. it('Shows the full unformatted value on hover', async () => {
  118. render(
  119. <BigNumberWidget
  120. data={[
  121. {
  122. 'count()': 178451214,
  123. },
  124. ]}
  125. meta={{
  126. fields: {
  127. 'count()': 'integer',
  128. },
  129. units: {
  130. 'count()': null,
  131. },
  132. }}
  133. />
  134. );
  135. await userEvent.hover(screen.getByText('178m'));
  136. expect(screen.getByText('178451214')).toBeInTheDocument();
  137. });
  138. it('Respect maximum value', () => {
  139. render(
  140. <BigNumberWidget
  141. title="Count"
  142. data={[
  143. {
  144. 'count()': 178451214,
  145. },
  146. ]}
  147. maximumValue={100000000}
  148. meta={{
  149. fields: {
  150. 'count()': 'integer',
  151. },
  152. }}
  153. />
  154. );
  155. expect(screen.getByText(textWithMarkupMatcher('>100m'))).toBeInTheDocument();
  156. });
  157. });
  158. describe('State', () => {
  159. it('Shows a loading placeholder', () => {
  160. render(<BigNumberWidget isLoading />);
  161. expect(screen.getByText('—')).toBeInTheDocument();
  162. });
  163. it('Loading state takes precedence over error state', () => {
  164. render(
  165. <BigNumberWidget isLoading error={new Error('Parsing error of old value')} />
  166. );
  167. expect(screen.getByText('—')).toBeInTheDocument();
  168. });
  169. it('Shows an error message', () => {
  170. render(<BigNumberWidget error={new Error('Uh oh')} />);
  171. expect(screen.getByText('Error: Uh oh')).toBeInTheDocument();
  172. });
  173. it('Shows a retry button', async () => {
  174. const onRetry = jest.fn();
  175. render(<BigNumberWidget error={new Error('Oh no!')} onRetry={onRetry} />);
  176. await userEvent.click(screen.getByRole('button', {name: 'Retry'}));
  177. expect(onRetry).toHaveBeenCalledTimes(1);
  178. });
  179. it('Hides other actions if there is an error and a retry handler', () => {
  180. const onRetry = jest.fn();
  181. render(
  182. <BigNumberWidget
  183. error={new Error('Oh no!')}
  184. onRetry={onRetry}
  185. actions={[
  186. {
  187. key: 'Open in Discover',
  188. to: '/discover',
  189. },
  190. ]}
  191. />
  192. );
  193. expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
  194. expect(
  195. screen.queryByRole('link', {name: 'Open in Discover'})
  196. ).not.toBeInTheDocument();
  197. });
  198. });
  199. describe('Previous Period Data', () => {
  200. it('Shows the difference between the current and previous data', () => {
  201. render(
  202. <BigNumberWidget
  203. title="http_response_code_rate(500)"
  204. data={[
  205. {
  206. 'http_response_code_rate(500)': 0.14227123,
  207. },
  208. ]}
  209. previousPeriodData={[
  210. {
  211. 'http_response_code_rate(500)': 0.1728139,
  212. },
  213. ]}
  214. meta={{
  215. fields: {
  216. 'http_response_code_rate(500)': 'percentage',
  217. },
  218. units: {
  219. 'http_response_code_rate(500)': null,
  220. },
  221. }}
  222. />
  223. );
  224. expect(screen.getByText('14.23%')).toBeInTheDocument();
  225. expect(screen.getByText('3.05%')).toBeInTheDocument();
  226. });
  227. });
  228. describe('Thresholds', () => {
  229. it('Evaluates the current value against a threshold', async () => {
  230. render(
  231. <BigNumberWidget
  232. data={[
  233. {
  234. 'eps()': 14.227123,
  235. },
  236. ]}
  237. meta={{
  238. fields: {
  239. 'eps()': 'rate',
  240. },
  241. units: {
  242. 'eps()': '1/second',
  243. },
  244. }}
  245. thresholds={{
  246. max_values: {
  247. max1: 10,
  248. max2: 20,
  249. },
  250. unit: '1/second',
  251. }}
  252. />
  253. );
  254. expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'meh');
  255. await userEvent.hover(screen.getByRole('status'));
  256. expect(await screen.findByText('Thresholds in /second')).toBeInTheDocument();
  257. });
  258. it('Normalizes the units', () => {
  259. render(
  260. <BigNumberWidget
  261. data={[
  262. {
  263. 'mystery_error_rate()': 135, // 2.25/s
  264. },
  265. ]}
  266. meta={{
  267. fields: {
  268. 'mystery_error_rate()': 'rate',
  269. },
  270. units: {
  271. 'mystery_error_rate()': '1/minute',
  272. },
  273. }}
  274. thresholds={{
  275. max_values: {
  276. max1: 2,
  277. max2: 5,
  278. },
  279. unit: '1/second',
  280. }}
  281. />
  282. );
  283. expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'meh');
  284. });
  285. it('Respects the preferred polarity', () => {
  286. render(
  287. <BigNumberWidget
  288. data={[
  289. {
  290. 'mystery_error_rate()': 135,
  291. },
  292. ]}
  293. meta={{
  294. fields: {
  295. 'mystery_error_rate()': 'rate',
  296. },
  297. units: {
  298. 'mystery_error_rate()': '1/second',
  299. },
  300. }}
  301. thresholds={{
  302. max_values: {
  303. max1: 200,
  304. max2: 500,
  305. },
  306. unit: '1/second',
  307. }}
  308. preferredPolarity="-"
  309. />
  310. );
  311. expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'good');
  312. });
  313. });
  314. });