utils.tsx 6.7 KB

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