bigNumberWidget.stories.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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 data 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. data={[
  41. {
  42. 'eps()': 0.01087819860850493,
  43. },
  44. ]}
  45. meta={{
  46. fields: {
  47. 'eps()': 'rate',
  48. },
  49. units: {
  50. 'eps()': '1/second',
  51. },
  52. }}
  53. thresholds={{
  54. max_values: {
  55. max1: 1,
  56. max2: 2,
  57. },
  58. unit: '1/second',
  59. }}
  60. />
  61. </SmallSizingWindow>
  62. <SmallSizingWindow>
  63. <BigNumberWidget
  64. title="Count"
  65. data={[
  66. {
  67. 'count()': 178451214,
  68. },
  69. ]}
  70. meta={{
  71. fields: {
  72. 'count()': 'integer',
  73. },
  74. units: {
  75. 'count()': null,
  76. },
  77. }}
  78. />
  79. </SmallSizingWindow>
  80. <SmallSizingWindow>
  81. <BigNumberWidget
  82. title="Query Duration"
  83. description="p95(span.duration)"
  84. data={[
  85. {
  86. 'p95(span.duration)': 17.28,
  87. },
  88. ]}
  89. meta={{
  90. fields: {
  91. 'p95(span.duration)': 'duration',
  92. },
  93. units: {
  94. 'p95(spa.duration)': 'milliseconds',
  95. },
  96. }}
  97. />
  98. </SmallSizingWindow>
  99. <SmallSizingWindow>
  100. <BigNumberWidget
  101. title="Latest Timestamp"
  102. description=""
  103. data={[
  104. {
  105. 'max(timestamp)': '2024-10-17T16:08:07+00:00',
  106. },
  107. ]}
  108. meta={{
  109. fields: {
  110. 'max(timestamp)': 'date',
  111. },
  112. units: {
  113. 'max(timestamp)': null,
  114. },
  115. }}
  116. />
  117. </SmallSizingWindow>
  118. </SideBySide>
  119. <p>
  120. The <code>maximumValue</code> prop allows setting the maximum displayable value.
  121. e.g., imagine a widget that displays a count. A count of more than a million is
  122. too expensive for the API to compute, so the API returns a maximum of 1,000,000.
  123. If the API returns exactly 1,000,000, that means the actual number is unknown,
  124. something higher than the max. Setting{' '}
  125. <JSXProperty name="maximumValue" value={1000000} /> will show &gt;1m.
  126. </p>
  127. <SideBySide>
  128. <NormalWidget>
  129. <BigNumberWidget
  130. title="Count"
  131. data={[
  132. {
  133. 'count()': 1000000,
  134. },
  135. ]}
  136. maximumValue={1000000}
  137. meta={{
  138. fields: {
  139. 'count()': 'integer',
  140. },
  141. }}
  142. />
  143. </NormalWidget>
  144. </SideBySide>
  145. </Fragment>
  146. );
  147. });
  148. story('State', () => {
  149. return (
  150. <Fragment>
  151. <p>
  152. <JSXNode name="BigNumberWidget" /> supports the usual loading and error states.
  153. The loading state shows a simple placeholder. The error state also shows an
  154. optional "Retry" button.
  155. </p>
  156. <SideBySide>
  157. <NormalWidget>
  158. <BigNumberWidget title="Loading Count" isLoading />
  159. </NormalWidget>
  160. <NormalWidget>
  161. <BigNumberWidget title="Missing Count" data={[{}]} />
  162. </NormalWidget>
  163. <NormalWidget>
  164. <BigNumberWidget
  165. title="Count Error"
  166. error={new Error('Something went wrong!')}
  167. />
  168. </NormalWidget>
  169. <NormalWidget>
  170. <BigNumberWidget
  171. title="Data Error"
  172. error={new Error('Something went wrong!')}
  173. onRetry={() => {}}
  174. />
  175. </NormalWidget>
  176. </SideBySide>
  177. </Fragment>
  178. );
  179. });
  180. story('Previous Period Data', () => {
  181. return (
  182. <Fragment>
  183. <p>
  184. <JSXNode name="BigNumberWidget" /> shows the difference of the current data and
  185. the previous period data as the difference between the two values, in small text
  186. next to the main value.
  187. </p>
  188. <p>
  189. The <code>preferredPolarity</code> prop controls the color of the comparison
  190. string. Setting <JSXProperty name="preferredPolarity" value={'+'} /> mean that a
  191. higher number is <i>better</i> and will paint increases in the value green. Vice
  192. versa with negative polarity. Omitting a preferred polarity will prevent
  193. colorization.
  194. </p>
  195. <SideBySide>
  196. <NormalWidget>
  197. <BigNumberWidget
  198. title="eps()"
  199. data={[
  200. {
  201. 'eps()': 17.1087819860850493,
  202. },
  203. ]}
  204. previousPeriodData={[
  205. {
  206. 'eps()': 15.0088607819850493,
  207. },
  208. ]}
  209. meta={{
  210. fields: {
  211. 'eps()': 'rate',
  212. },
  213. units: {
  214. 'eps()': '1/second',
  215. },
  216. }}
  217. />
  218. </NormalWidget>
  219. <NormalWidget>
  220. <BigNumberWidget
  221. title="http_rate(500)"
  222. data={[
  223. {
  224. 'http_rate(500)': 0.14227123,
  225. },
  226. ]}
  227. previousPeriodData={[
  228. {
  229. 'http_rate(500)': 0.1728139,
  230. },
  231. ]}
  232. preferredPolarity="-"
  233. meta={{
  234. fields: {
  235. 'http_rate(500)': 'percentage',
  236. },
  237. }}
  238. />
  239. </NormalWidget>
  240. <NormalWidget>
  241. <BigNumberWidget
  242. title="http_rate(200)"
  243. data={[
  244. {
  245. 'http_rate(200)': 0.14227123,
  246. },
  247. ]}
  248. previousPeriodData={[
  249. {
  250. 'http_rate(200)': 0.1728139,
  251. },
  252. ]}
  253. preferredPolarity="+"
  254. meta={{
  255. fields: {
  256. 'http_rate(200)': 'percentage',
  257. },
  258. }}
  259. />
  260. </NormalWidget>
  261. </SideBySide>
  262. </Fragment>
  263. );
  264. });
  265. story('Thresholds', () => {
  266. const meta = {
  267. fields: {
  268. 'eps()': 'rate',
  269. },
  270. units: {
  271. 'eps()': '1/second',
  272. },
  273. };
  274. const thresholds = {
  275. max_values: {
  276. max1: 20,
  277. max2: 50,
  278. },
  279. unit: '1/second',
  280. };
  281. return (
  282. <Fragment>
  283. <p>
  284. <JSXNode name="BigNumberWidget" /> supports a <code>thresholds</code> prop. If
  285. specified, the value of the data in the widget will be evaluated against these
  286. thresholds, and indicated using a colorful circle next to the value.
  287. </p>
  288. <SideBySide>
  289. <NormalWidget>
  290. <BigNumberWidget
  291. title="eps()"
  292. data={[
  293. {
  294. 'eps()': 7.1,
  295. },
  296. ]}
  297. meta={meta}
  298. thresholds={thresholds}
  299. preferredPolarity="+"
  300. />
  301. </NormalWidget>
  302. <NormalWidget>
  303. <BigNumberWidget
  304. title="eps()"
  305. data={[
  306. {
  307. 'eps()': 27.781,
  308. },
  309. ]}
  310. meta={meta}
  311. thresholds={thresholds}
  312. preferredPolarity="-"
  313. />
  314. </NormalWidget>
  315. <NormalWidget>
  316. <BigNumberWidget
  317. title="eps()"
  318. data={[
  319. {
  320. 'eps()': 78.1,
  321. },
  322. ]}
  323. meta={meta}
  324. thresholds={thresholds}
  325. preferredPolarity="+"
  326. />
  327. </NormalWidget>
  328. </SideBySide>
  329. <p>
  330. The thresholds respect the preferred polarity. By default, the preferred
  331. polarity is positive (higher numbers are good).
  332. </p>
  333. <SideBySide>
  334. <NormalWidget>
  335. <BigNumberWidget
  336. title="eps()"
  337. data={[
  338. {
  339. 'eps()': 7.1,
  340. },
  341. ]}
  342. meta={meta}
  343. thresholds={thresholds}
  344. preferredPolarity="-"
  345. />
  346. </NormalWidget>
  347. <NormalWidget>
  348. <BigNumberWidget
  349. title="eps()"
  350. data={[
  351. {
  352. 'eps()': 27.781,
  353. },
  354. ]}
  355. meta={meta}
  356. thresholds={thresholds}
  357. preferredPolarity="-"
  358. />
  359. </NormalWidget>
  360. <NormalWidget>
  361. <BigNumberWidget
  362. title="eps()"
  363. data={[
  364. {
  365. 'eps()': 78.1,
  366. },
  367. ]}
  368. meta={meta}
  369. thresholds={thresholds}
  370. preferredPolarity="-"
  371. />
  372. </NormalWidget>
  373. </SideBySide>
  374. </Fragment>
  375. );
  376. });
  377. });
  378. const SmallSizingWindow = styled(SizingWindow)`
  379. width: auto;
  380. height: 200px;
  381. `;
  382. const NormalWidget = styled('div')`
  383. width: 250px;
  384. `;