utils.tsx 6.3 KB

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