bigNumberWidget.stories.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import JSXNode from 'sentry/components/stories/jsxNode';
  4. import JSXProperty from 'sentry/components/stories/jsxProperty';
  5. import SideBySide from 'sentry/components/stories/sideBySide';
  6. import SizingWindow from 'sentry/components/stories/sizingWindow';
  7. import storyBook from 'sentry/stories/storyBook';
  8. import {BigNumberWidget} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidget';
  9. export default storyBook(BigNumberWidget, story => {
  10. story('Getting Started', () => {
  11. return (
  12. <Fragment>
  13. <p>
  14. <JSXNode name="BigNumberWidget" /> is a Dashboard Widget Component. It displays
  15. a single large value. Used in places like Dashboards Big Number widgets, Project
  16. Details pages, and Organization Stats pages.
  17. </p>
  18. </Fragment>
  19. );
  20. });
  21. story('Visualization', () => {
  22. return (
  23. <Fragment>
  24. <p>
  25. The visualization of <JSXNode name="BigNumberWidget" /> a large number, just
  26. like it says on the tin. Depending on the value passed to it, it intelligently
  27. rounds and humanizes the results. If the number is humanized, hovering over the
  28. visualization shows a tooltip with the full value.
  29. </p>
  30. <p>
  31. <JSXNode name="BigNumberWidget" /> also supports string values. This is not
  32. commonly used, but it's capable of rendering timestamps and in fact most fields
  33. defined in our field renderer pipeline
  34. </p>
  35. <SideBySide>
  36. <SmallSizingWindow>
  37. <BigNumberWidget
  38. title="EPS"
  39. description="Number of events per second"
  40. value={0.01087819860850493}
  41. field="eps()"
  42. meta={{
  43. fields: {
  44. 'eps()': 'rate',
  45. },
  46. units: {
  47. 'eps()': '1/second',
  48. },
  49. }}
  50. thresholds={{
  51. max_values: {
  52. max1: 1,
  53. max2: 2,
  54. },
  55. unit: '1/second',
  56. }}
  57. />
  58. </SmallSizingWindow>
  59. <SmallSizingWindow>
  60. <BigNumberWidget
  61. title="Count"
  62. value={178451214}
  63. field="count()"
  64. meta={{
  65. fields: {
  66. 'count()': 'integer',
  67. },
  68. units: {
  69. 'count()': null,
  70. },
  71. }}
  72. />
  73. </SmallSizingWindow>
  74. <SmallSizingWindow>
  75. <BigNumberWidget
  76. title="Query Duration"
  77. description="p95(span.duration)"
  78. value={17.28}
  79. field="p95(span.duration)"
  80. meta={{
  81. fields: {
  82. 'p95(span.duration)': 'duration',
  83. },
  84. units: {
  85. 'p95(spa.duration)': 'milliseconds',
  86. },
  87. }}
  88. />
  89. </SmallSizingWindow>
  90. <SmallSizingWindow>
  91. <BigNumberWidget
  92. title="Latest Timestamp"
  93. description=""
  94. value={'2024-10-17T16:08:07+00:00'}
  95. field="max(timestamp)"
  96. meta={{
  97. fields: {
  98. 'max(timestamp)': 'date',
  99. },
  100. units: {
  101. 'max(timestamp)': null,
  102. },
  103. }}
  104. />
  105. </SmallSizingWindow>
  106. </SideBySide>
  107. <p>
  108. The <code>maximumValue</code> prop allows setting the maximum displayable value.
  109. e.g., imagine a widget that displays a count. A count of more than a million is
  110. too expensive for the API to compute, so the API returns a maximum of 1,000,000.
  111. If the API returns exactly 1,000,000, that means the actual number is unknown,
  112. something higher than the max. Setting{' '}
  113. <JSXProperty name="maximumValue" value={1000000} /> will show &gt;1m.
  114. </p>
  115. <SideBySide>
  116. <SmallWidget>
  117. <BigNumberWidget
  118. title="Count"
  119. value={1000000}
  120. field="count()"
  121. maximumValue={1000000}
  122. meta={{
  123. fields: {
  124. 'count()': 'integer',
  125. },
  126. }}
  127. />
  128. </SmallWidget>
  129. </SideBySide>
  130. </Fragment>
  131. );
  132. });
  133. story('State', () => {
  134. return (
  135. <Fragment>
  136. <p>
  137. <JSXNode name="BigNumberWidget" /> supports the usual loading and error states.
  138. The loading state shows a simple placeholder. The error state also shows an
  139. optional "Retry" button.
  140. </p>
  141. <SideBySide>
  142. <SmallWidget>
  143. <BigNumberWidget title="Loading Count" isLoading />
  144. </SmallWidget>
  145. <SmallWidget>
  146. <BigNumberWidget title="Missing Count" />
  147. </SmallWidget>
  148. <SmallWidget>
  149. <BigNumberWidget
  150. title="Count Error"
  151. error={new Error('Something went wrong!')}
  152. />
  153. </SmallWidget>
  154. <SmallWidget>
  155. <BigNumberWidget
  156. title="Data Error"
  157. error={new Error('Something went wrong!')}
  158. onRetry={() => {}}
  159. />
  160. </SmallWidget>
  161. </SideBySide>
  162. <p>The contents of the error adjust slightly as the widget gets bigger.</p>
  163. <SideBySide>
  164. <MediumWidget>
  165. <BigNumberWidget
  166. title="Data Error"
  167. error={new Error('Something went wrong!')}
  168. onRetry={() => {}}
  169. />
  170. </MediumWidget>
  171. </SideBySide>
  172. </Fragment>
  173. );
  174. });
  175. story('Previous Period Data', () => {
  176. return (
  177. <Fragment>
  178. <p>
  179. <JSXNode name="BigNumberWidget" /> shows the difference of the current value and
  180. the previous period value as the difference between the two values, in small
  181. text next to the main value.
  182. </p>
  183. <p>
  184. The <code>preferredPolarity</code> prop controls the color of the comparison
  185. string. Setting <JSXProperty name="preferredPolarity" value={'+'} /> mean that a
  186. higher number is <i>better</i> and will paint increases in the value green. Vice
  187. versa with negative polarity. Omitting a preferred polarity will prevent
  188. colorization.
  189. </p>
  190. <SideBySide>
  191. <SmallWidget>
  192. <BigNumberWidget
  193. title="eps()"
  194. value={17.1087819860850493}
  195. field="eps()"
  196. previousPeriodValue={15.0088607819850493}
  197. meta={{
  198. fields: {
  199. 'eps()': 'rate',
  200. },
  201. units: {
  202. 'eps()': '1/second',
  203. },
  204. }}
  205. />
  206. </SmallWidget>
  207. <SmallWidget>
  208. <BigNumberWidget
  209. title="http_rate(500)"
  210. value={0.14227123}
  211. previousPeriodValue={0.1728139}
  212. field="http_rate(500)"
  213. preferredPolarity="-"
  214. meta={{
  215. fields: {
  216. 'http_rate(500)': 'percentage',
  217. },
  218. }}
  219. />
  220. </SmallWidget>
  221. <SmallWidget>
  222. <BigNumberWidget
  223. title="http_rate(200)"
  224. field="http_rate(200)"
  225. value={0.14227123}
  226. previousPeriodValue={0.1728139}
  227. preferredPolarity="+"
  228. meta={{
  229. fields: {
  230. 'http_rate(200)': 'percentage',
  231. },
  232. }}
  233. />
  234. </SmallWidget>
  235. </SideBySide>
  236. </Fragment>
  237. );
  238. });
  239. story('Thresholds', () => {
  240. const meta = {
  241. fields: {
  242. 'eps()': 'rate',
  243. },
  244. units: {
  245. 'eps()': '1/second',
  246. },
  247. };
  248. const thresholds = {
  249. max_values: {
  250. max1: 20,
  251. max2: 50,
  252. },
  253. unit: '1/second',
  254. };
  255. return (
  256. <Fragment>
  257. <p>
  258. <JSXNode name="BigNumberWidget" /> supports a <code>thresholds</code> prop. If
  259. specified, the value in the widget will be evaluated against these thresholds,
  260. and indicated using a colorful circle next to the value.
  261. </p>
  262. <SideBySide>
  263. <SmallWidget>
  264. <BigNumberWidget
  265. title="eps()"
  266. value={7.1}
  267. field="eps()"
  268. meta={meta}
  269. thresholds={thresholds}
  270. preferredPolarity="+"
  271. />
  272. </SmallWidget>
  273. <SmallWidget>
  274. <BigNumberWidget
  275. title="eps()"
  276. value={27.781}
  277. field="eps()"
  278. meta={meta}
  279. thresholds={thresholds}
  280. preferredPolarity="-"
  281. />
  282. </SmallWidget>
  283. <SmallWidget>
  284. <BigNumberWidget
  285. title="eps()"
  286. field="eps()"
  287. value={78.1}
  288. meta={meta}
  289. thresholds={thresholds}
  290. preferredPolarity="+"
  291. />
  292. </SmallWidget>
  293. </SideBySide>
  294. <p>
  295. The thresholds respect the preferred polarity. By default, the preferred
  296. polarity is positive (higher numbers are good).
  297. </p>
  298. <SideBySide>
  299. <SmallWidget>
  300. <BigNumberWidget
  301. title="eps()"
  302. field="eps()"
  303. value={7.1}
  304. meta={meta}
  305. thresholds={thresholds}
  306. preferredPolarity="-"
  307. />
  308. </SmallWidget>
  309. <SmallWidget>
  310. <BigNumberWidget
  311. title="eps()"
  312. field="eps()"
  313. value={27.781}
  314. meta={meta}
  315. thresholds={thresholds}
  316. preferredPolarity="-"
  317. />
  318. </SmallWidget>
  319. <SmallWidget>
  320. <BigNumberWidget
  321. title="eps()"
  322. field="eps()"
  323. value={78.1}
  324. meta={meta}
  325. thresholds={thresholds}
  326. preferredPolarity="-"
  327. />
  328. </SmallWidget>
  329. </SideBySide>
  330. </Fragment>
  331. );
  332. });
  333. });
  334. const SmallSizingWindow = styled(SizingWindow)`
  335. width: auto;
  336. height: 200px;
  337. `;
  338. const SmallWidget = styled('div')`
  339. width: 250px;
  340. `;
  341. const MediumWidget = styled('div')`
  342. width: 420px;
  343. `;