utils.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import type {Query} from 'history';
  2. import type {EventTag} from 'sentry/types/event';
  3. import {formatNumberWithDynamicDecimalPoints} from 'sentry/utils/formatters';
  4. import {appendTagCondition} from 'sentry/utils/queryString';
  5. export function intcomma(x: number): string {
  6. return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  7. }
  8. /**
  9. * Replaces slug special chars with a space
  10. */
  11. export function explodeSlug(slug: string): string {
  12. return slug.replace(/[-_]+/g, ' ').trim();
  13. }
  14. export function defined<T>(item: T): item is Exclude<T, null | undefined> {
  15. return item !== undefined && item !== null;
  16. }
  17. export function nl2br(str: string): string {
  18. return str.replace(/(?:\r\n|\r|\n)/g, '<br />');
  19. }
  20. export function escape(str: string): string {
  21. return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  22. }
  23. export function percent(value: number, totalValue: number): number {
  24. // prevent division by zero
  25. if (totalValue === 0) {
  26. return 0;
  27. }
  28. return (value / totalValue) * 100;
  29. }
  30. /**
  31. * Note the difference between *a-bytes (base 10) vs *i-bytes (base 2), which
  32. * means that:
  33. * - 1000 megabytes is equal to 1 gigabyte
  34. * - 1024 mebibytes is equal to 1 gibibytes
  35. *
  36. * We will use base 10 throughout billing for attachments. This function formats
  37. * quota/usage values for display.
  38. *
  39. * For storage/memory/file sizes, please take a look at formatBytesBase2
  40. */
  41. export function formatBytesBase10(bytes: number, u: number = 0) {
  42. const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  43. const threshold = 1000;
  44. while (bytes >= threshold) {
  45. bytes /= threshold;
  46. u += 1;
  47. }
  48. return formatNumberWithDynamicDecimalPoints(bytes) + ' ' + units[u];
  49. }
  50. /**
  51. * Note the difference between *a-bytes (base 10) vs *i-bytes (base 2), which
  52. * means that:
  53. * - 1000 megabytes is equal to 1 gigabyte
  54. * - 1024 mebibytes is equal to 1 gibibytes
  55. *
  56. * We will use base 2 to display storage/memory/file sizes as that is commonly
  57. * used by Windows or RAM or CPU cache sizes, and it is more familiar to the user
  58. *
  59. * For billing-related code around attachments. please take a look at
  60. * formatBytesBase10
  61. */
  62. export function formatBytesBase2(bytes: number, fixPoints: number | false = 1): string {
  63. const units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  64. const thresh = 1024;
  65. if (bytes < thresh) {
  66. return (
  67. (fixPoints === false
  68. ? formatNumberWithDynamicDecimalPoints(bytes)
  69. : bytes.toFixed(fixPoints)) + ' B'
  70. );
  71. }
  72. let u = -1;
  73. do {
  74. bytes /= thresh;
  75. ++u;
  76. } while (bytes >= thresh);
  77. return (
  78. (fixPoints === false
  79. ? formatNumberWithDynamicDecimalPoints(bytes)
  80. : bytes.toFixed(fixPoints)) +
  81. ' ' +
  82. units[u]
  83. );
  84. }
  85. export function getShortCommitHash(hash: string): string {
  86. if (hash.match(/^[a-f0-9]{40}$/)) {
  87. hash = hash.substring(0, 7);
  88. }
  89. return hash;
  90. }
  91. export function parseRepo<T>(repo: T): T {
  92. if (typeof repo === 'string') {
  93. const re = /(?:github\.com|bitbucket\.org)\/([^\/]+\/[^\/]+)/i;
  94. const match = repo.match(re);
  95. const parsedRepo = match ? match[1] : repo;
  96. return parsedRepo as any;
  97. }
  98. return repo;
  99. }
  100. /**
  101. * Converts a multi-line textarea input value into an array,
  102. * eliminating empty lines
  103. */
  104. export function extractMultilineFields(value: string): string[] {
  105. return value
  106. .split('\n')
  107. .map(f => f.trim())
  108. .filter(f => f !== '');
  109. }
  110. /**
  111. * If the value is of type Array, converts it to type string, keeping the line breaks, if there is any
  112. */
  113. export function convertMultilineFieldValue<T extends string | string[]>(
  114. value: T
  115. ): string {
  116. if (Array.isArray(value)) {
  117. return value.join('\n');
  118. }
  119. if (typeof value === 'string') {
  120. return value.split('\n').join('\n');
  121. }
  122. return '';
  123. }
  124. // build actorIds
  125. export const buildUserId = (id: string) => `user:${id}`;
  126. export const buildTeamId = (id: string) => `team:${id}`;
  127. /**
  128. * Removes the organization / project scope prefix on feature names.
  129. */
  130. export function descopeFeatureName<T>(feature: T): T | string {
  131. if (typeof feature !== 'string') {
  132. return feature;
  133. }
  134. const results = feature.match(/(?:^(?:projects|organizations):)?(.*)/);
  135. if (results && results.length > 0) {
  136. return results.pop()!;
  137. }
  138. return feature;
  139. }
  140. export function isWebpackChunkLoadingError(error: Error): boolean {
  141. return (
  142. error &&
  143. typeof error.message === 'string' &&
  144. error.message.toLowerCase().includes('loading chunk')
  145. );
  146. }
  147. export function generateQueryWithTag(prevQuery: Query, tag: EventTag): Query {
  148. const query = {...prevQuery};
  149. // some tags are dedicated query strings since other parts of the app consumes this,
  150. // for example, the global selection header.
  151. switch (tag.key) {
  152. case 'environment':
  153. query.environment = tag.value;
  154. break;
  155. case 'project':
  156. query.project = tag.value;
  157. break;
  158. default:
  159. query.query = appendTagCondition(query.query, tag.key, tag.value);
  160. }
  161. return query;
  162. }
  163. // NOTE: only escapes a " if it's not already escaped
  164. export function escapeDoubleQuotes(str: string) {
  165. return str.replace(/\\([\s\S])|(")/g, '\\$1$2');
  166. }
  167. export function generateOrgSlugUrl(orgSlug) {
  168. const sentryDomain = window.__initialData.links.sentryUrl.split('/')[2];
  169. return `${window.location.protocol}//${orgSlug}.${sentryDomain}${window.location.pathname}`;
  170. }