utils.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import {DurationDisplay} from 'sentry/components/performance/waterfall/types';
  2. import CHART_PALETTE from 'sentry/constants/chartPalette';
  3. import space from 'sentry/styles/space';
  4. import {Theme} from 'sentry/utils/theme';
  5. export const getBackgroundColor = ({
  6. showStriping,
  7. showDetail,
  8. theme,
  9. }: {
  10. theme: Theme;
  11. showDetail?: boolean;
  12. showStriping?: boolean;
  13. }) => {
  14. if (showDetail) {
  15. return theme.textColor;
  16. }
  17. if (showStriping) {
  18. return theme.backgroundSecondary;
  19. }
  20. return theme.background;
  21. };
  22. type HatchProps = {
  23. spanBarHatch: boolean;
  24. };
  25. export function getHatchPattern(
  26. {spanBarHatch}: HatchProps,
  27. primary: string,
  28. alternate: string
  29. ) {
  30. if (spanBarHatch === true) {
  31. return `
  32. background-image: linear-gradient(135deg,
  33. ${alternate},
  34. ${alternate} 2.5px,
  35. ${primary} 2.5px,
  36. ${primary} 5px,
  37. ${alternate} 6px,
  38. ${alternate} 8px,
  39. ${primary} 8px,
  40. ${primary} 11px,
  41. ${alternate} 11px,
  42. ${alternate} 14px,
  43. ${primary} 14px,
  44. ${primary} 16.5px,
  45. ${alternate} 16.5px,
  46. ${alternate} 19px,
  47. ${primary} 20px
  48. );
  49. background-size: 16px 16px;
  50. `;
  51. }
  52. return null;
  53. }
  54. export const getDurationPillAlignment = ({
  55. durationDisplay,
  56. theme,
  57. spanBarHatch,
  58. }: {
  59. durationDisplay: DurationDisplay;
  60. spanBarHatch: boolean;
  61. theme: Theme;
  62. }) => {
  63. switch (durationDisplay) {
  64. case 'left':
  65. return `right: calc(100% + ${space(0.5)});`;
  66. case 'right':
  67. return `left: calc(100% + ${space(0.75)});`;
  68. default:
  69. return `
  70. right: ${space(0.75)};
  71. color: ${spanBarHatch === true ? theme.gray300 : theme.white};
  72. `;
  73. }
  74. };
  75. export const getToggleTheme = ({
  76. theme,
  77. isExpanded,
  78. disabled,
  79. errored,
  80. isSpanGroupToggler,
  81. }: {
  82. disabled: boolean;
  83. errored: boolean;
  84. isExpanded: boolean;
  85. theme: Theme;
  86. isSpanGroupToggler?: boolean;
  87. }) => {
  88. const buttonTheme = isExpanded ? theme.button.default : theme.button.primary;
  89. const errorTheme = theme.button.danger;
  90. const background = errored
  91. ? isExpanded
  92. ? buttonTheme.background
  93. : errorTheme.background
  94. : buttonTheme.background;
  95. const border = errored ? errorTheme.background : buttonTheme.border;
  96. const color = errored
  97. ? isExpanded
  98. ? errorTheme.background
  99. : buttonTheme.color
  100. : buttonTheme.color;
  101. if (isSpanGroupToggler) {
  102. return `
  103. background: ${theme.blue300};
  104. border: 1px solid ${theme.button.default.border};
  105. color: ${color};
  106. cursor: pointer;
  107. `;
  108. }
  109. if (disabled) {
  110. return `
  111. background: ${background};
  112. border: 1px solid ${border};
  113. color: ${color};
  114. cursor: default;
  115. `;
  116. }
  117. return `
  118. background: ${background};
  119. border: 1px solid ${border};
  120. color: ${color};
  121. `;
  122. };
  123. export const getDurationDisplay = ({
  124. width,
  125. left,
  126. }: {
  127. left: undefined | number;
  128. width: undefined | number;
  129. }): DurationDisplay => {
  130. const spaceNeeded = 0.3;
  131. if (left === undefined || width === undefined) {
  132. return 'inset';
  133. }
  134. if (left + width < 1 - spaceNeeded) {
  135. return 'right';
  136. }
  137. if (left > spaceNeeded) {
  138. return 'left';
  139. }
  140. return 'inset';
  141. };
  142. export const getHumanDuration = (duration: number): string => {
  143. // note: duration is assumed to be in seconds
  144. const durationMS = duration * 1000;
  145. return `${durationMS.toLocaleString(undefined, {
  146. minimumFractionDigits: 2,
  147. maximumFractionDigits: 2,
  148. })}ms`;
  149. };
  150. export const toPercent = (value: number) => `${(value * 100).toFixed(3)}%`;
  151. type Rect = {
  152. height: number;
  153. width: number;
  154. // x and y are left/top coords respectively
  155. x: number;
  156. y: number;
  157. };
  158. // get position of element relative to top/left of document
  159. export const getOffsetOfElement = (element: Element) => {
  160. // left and top are relative to viewport
  161. const {left, top} = element.getBoundingClientRect();
  162. // get values that the document is currently scrolled by
  163. const scrollLeft = window.pageXOffset;
  164. const scrollTop = window.pageYOffset;
  165. return {x: left + scrollLeft, y: top + scrollTop};
  166. };
  167. export const rectOfContent = (element: Element): Rect => {
  168. const {x, y} = getOffsetOfElement(element);
  169. // offsets for the border and any scrollbars (clientLeft and clientTop),
  170. // and if the element was scrolled (scrollLeft and scrollTop)
  171. //
  172. // NOTE: clientLeft and clientTop does not account for any margins nor padding
  173. const contentOffsetLeft = element.clientLeft - element.scrollLeft;
  174. const contentOffsetTop = element.clientTop - element.scrollTop;
  175. return {
  176. x: x + contentOffsetLeft,
  177. y: y + contentOffsetTop,
  178. width: element.scrollWidth,
  179. height: element.scrollHeight,
  180. };
  181. };
  182. export const clamp = (value: number, min: number, max: number): number => {
  183. if (value < min) {
  184. return min;
  185. }
  186. if (value > max) {
  187. return max;
  188. }
  189. return value;
  190. };
  191. const getLetterIndex = (letter: string): number => {
  192. const index = 'abcdefghijklmnopqrstuvwxyz'.indexOf(letter) || 0;
  193. return index === -1 ? 0 : index;
  194. };
  195. const colorsAsArray = Object.keys(CHART_PALETTE).map(key => CHART_PALETTE[17][key]);
  196. export const barColors = {
  197. default: CHART_PALETTE[17][4],
  198. transaction: CHART_PALETTE[17][8],
  199. http: CHART_PALETTE[17][10],
  200. db: CHART_PALETTE[17][17],
  201. };
  202. export const pickBarColor = (input: string | undefined): string => {
  203. // We pick the color for span bars using the first three letters of the op name.
  204. // That way colors stay consistent between transactions.
  205. if (!input || input.length < 3) {
  206. return CHART_PALETTE[17][4];
  207. }
  208. if (barColors[input]) {
  209. return barColors[input];
  210. }
  211. const letterIndex1 = getLetterIndex(input.slice(0, 1));
  212. const letterIndex2 = getLetterIndex(input.slice(1, 2));
  213. const letterIndex3 = getLetterIndex(input.slice(2, 3));
  214. const letterIndex4 = getLetterIndex(input.slice(3, 4));
  215. return colorsAsArray[
  216. (letterIndex1 + letterIndex2 + letterIndex3 + letterIndex4) % colorsAsArray.length
  217. ];
  218. };