formatters.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import {Release} from '@sentry/release-parser';
  2. import round from 'lodash/round';
  3. import {t, tn} from 'sentry/locale';
  4. import {CommitAuthor, User} from 'sentry/types';
  5. export function userDisplayName(user: User | CommitAuthor, includeEmail = true): string {
  6. let displayName = String(user?.name ?? t('Unknown author')).trim();
  7. if (displayName.length <= 0) {
  8. displayName = t('Unknown author');
  9. }
  10. const email = String(user?.email ?? '').trim();
  11. if (email.length > 0 && email !== displayName && includeEmail) {
  12. displayName += ' (' + email + ')';
  13. }
  14. return displayName;
  15. }
  16. export const formatVersion = (rawVersion: string, withPackage = false) => {
  17. try {
  18. const parsedVersion = new Release(rawVersion);
  19. const versionToDisplay = parsedVersion.describe();
  20. if (versionToDisplay.length) {
  21. return `${versionToDisplay}${
  22. withPackage && parsedVersion.package ? `, ${parsedVersion.package}` : ''
  23. }`;
  24. }
  25. return rawVersion;
  26. } catch {
  27. return rawVersion;
  28. }
  29. };
  30. function roundWithFixed(
  31. value: number,
  32. fixedDigits: number
  33. ): {label: string; result: number} {
  34. const label = value.toFixed(fixedDigits);
  35. const result = fixedDigits <= 0 ? Math.round(value) : value;
  36. return {label, result};
  37. }
  38. // in milliseconds
  39. export const MONTH = 2629800000;
  40. export const WEEK = 604800000;
  41. export const DAY = 86400000;
  42. export const HOUR = 3600000;
  43. export const MINUTE = 60000;
  44. export const SECOND = 1000;
  45. export function getDuration(
  46. seconds: number,
  47. fixedDigits: number = 0,
  48. abbreviation: boolean = false,
  49. extraShort: boolean = false
  50. ): string {
  51. // value in milliseconds
  52. const msValue = seconds * 1000;
  53. const value = Math.abs(msValue);
  54. if (value >= MONTH && !extraShort) {
  55. const {label, result} = roundWithFixed(msValue / MONTH, fixedDigits);
  56. return `${label}${abbreviation ? t('mo') : ` ${tn('month', 'months', result)}`}`;
  57. }
  58. if (value >= WEEK) {
  59. const {label, result} = roundWithFixed(msValue / WEEK, fixedDigits);
  60. if (extraShort) {
  61. return `${label}${t('w')}`;
  62. }
  63. if (abbreviation) {
  64. return `${label}${t('wk')}`;
  65. }
  66. return `${label} ${tn('week', 'weeks', result)}`;
  67. }
  68. if (value >= DAY) {
  69. const {label, result} = roundWithFixed(msValue / DAY, fixedDigits);
  70. return `${label}${
  71. abbreviation || extraShort ? t('d') : ` ${tn('day', 'days', result)}`
  72. }`;
  73. }
  74. if (value >= HOUR) {
  75. const {label, result} = roundWithFixed(msValue / HOUR, fixedDigits);
  76. if (extraShort) {
  77. return `${label}${t('h')}`;
  78. }
  79. if (abbreviation) {
  80. return `${label}${t('hr')}`;
  81. }
  82. return `${label} ${tn('hour', 'hours', result)}`;
  83. }
  84. if (value >= MINUTE) {
  85. const {label, result} = roundWithFixed(msValue / MINUTE, fixedDigits);
  86. if (extraShort) {
  87. return `${label}${t('m')}`;
  88. }
  89. if (abbreviation) {
  90. return `${label}${t('min')}`;
  91. }
  92. return `${label} ${tn('minute', 'minutes', result)}`;
  93. }
  94. if (value >= SECOND) {
  95. const {label, result} = roundWithFixed(msValue / SECOND, fixedDigits);
  96. if (extraShort || abbreviation) {
  97. return `${label}${t('s')}`;
  98. }
  99. return `${label} ${tn('second', 'seconds', result)}`;
  100. }
  101. const {label} = roundWithFixed(msValue, fixedDigits);
  102. return label + t('ms');
  103. }
  104. export function getExactDuration(seconds: number, abbreviation: boolean = false) {
  105. const convertDuration = (secs: number, abbr: boolean): string => {
  106. // value in milliseconds
  107. const msValue = round(secs * 1000);
  108. const value = round(Math.abs(secs * 1000));
  109. const divideBy = (time: number) => {
  110. return {
  111. quotient: msValue < 0 ? Math.ceil(msValue / time) : Math.floor(msValue / time),
  112. remainder: msValue % time,
  113. };
  114. };
  115. if (value >= WEEK) {
  116. const {quotient, remainder} = divideBy(WEEK);
  117. return `${quotient}${
  118. abbr ? t('wk') : ` ${tn('week', 'weeks', quotient)}`
  119. } ${convertDuration(remainder / 1000, abbr)}`;
  120. }
  121. if (value >= DAY) {
  122. const {quotient, remainder} = divideBy(DAY);
  123. return `${quotient}${
  124. abbr ? t('d') : ` ${tn('day', 'days', quotient)}`
  125. } ${convertDuration(remainder / 1000, abbr)}`;
  126. }
  127. if (value >= HOUR) {
  128. const {quotient, remainder} = divideBy(HOUR);
  129. return `${quotient}${
  130. abbr ? t('hr') : ` ${tn('hour', 'hours', quotient)}`
  131. } ${convertDuration(remainder / 1000, abbr)}`;
  132. }
  133. if (value >= MINUTE) {
  134. const {quotient, remainder} = divideBy(MINUTE);
  135. return `${quotient}${
  136. abbr ? t('min') : ` ${tn('minute', 'minutes', quotient)}`
  137. } ${convertDuration(remainder / 1000, abbr)}`;
  138. }
  139. if (value >= SECOND) {
  140. const {quotient, remainder} = divideBy(SECOND);
  141. return `${quotient}${
  142. abbr ? t('s') : ` ${tn('second', 'seconds', quotient)}`
  143. } ${convertDuration(remainder / 1000, abbr)}`;
  144. }
  145. if (value === 0) {
  146. return '';
  147. }
  148. return `${msValue}${abbr ? t('ms') : ` ${tn('millisecond', 'milliseconds', value)}`}`;
  149. };
  150. const result = convertDuration(seconds, abbreviation).trim();
  151. if (result.length) {
  152. return result;
  153. }
  154. return `0${abbreviation ? t('ms') : ` ${t('milliseconds')}`}`;
  155. }
  156. export function formatFloat(number: number, places: number) {
  157. const multi = Math.pow(10, places);
  158. return parseInt((number * multi).toString(), 10) / multi;
  159. }
  160. /**
  161. * Format a value between 0 and 1 as a percentage
  162. */
  163. export function formatPercentage(value: number, places: number = 2) {
  164. if (value === 0) {
  165. return '0%';
  166. }
  167. return (
  168. round(value * 100, places).toLocaleString(undefined, {
  169. maximumFractionDigits: places,
  170. }) + '%'
  171. );
  172. }
  173. const numberFormats = [
  174. [1000000000, 'b'],
  175. [1000000, 'm'],
  176. [1000, 'k'],
  177. ] as const;
  178. export function formatAbbreviatedNumber(number: number | string) {
  179. number = Number(number);
  180. let lookup: typeof numberFormats[number];
  181. // eslint-disable-next-line no-cond-assign
  182. for (let i = 0; (lookup = numberFormats[i]); i++) {
  183. const [suffixNum, suffix] = lookup;
  184. const shortValue = Math.floor(number / suffixNum);
  185. const fitsBound = number % suffixNum;
  186. if (shortValue <= 0) {
  187. continue;
  188. }
  189. return shortValue / 10 > 1 || !fitsBound
  190. ? `${shortValue}${suffix}`
  191. : `${formatFloat(number / suffixNum, 1)}${suffix}`;
  192. }
  193. return number.toLocaleString();
  194. }