bigNumberWidget.spec.tsx 7.7 KB

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