utils.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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 {SPAN_HATCH_TYPE_COLOURS, SpanBarHatch} 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(spanBarHatch: SpanBarHatch | undefined) {
  24. if (spanBarHatch) {
  25. const {primary, alternate} = SPAN_HATCH_TYPE_COLOURS[spanBarHatch];
  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. spanBarHatch?: SpanBarHatch;
  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 getDurationPillColour = ({
  68. durationDisplay,
  69. theme,
  70. showDetail,
  71. spanBarHatch,
  72. }: {
  73. durationDisplay: DurationDisplay;
  74. showDetail: boolean;
  75. theme: Theme;
  76. spanBarHatch?: SpanBarHatch;
  77. }) => {
  78. if (durationDisplay === 'inset') {
  79. return `color: ${
  80. spanBarHatch && spanBarHatch === SpanBarHatch.gap ? theme.gray300 : theme.white
  81. };`;
  82. }
  83. return `color: ${showDetail === true ? theme.gray200 : theme.gray300};`;
  84. };
  85. export const getToggleTheme = ({
  86. theme,
  87. isExpanded,
  88. disabled,
  89. errored,
  90. isSpanGroupToggler,
  91. }: {
  92. disabled: boolean;
  93. errored: boolean;
  94. isExpanded: boolean;
  95. theme: Theme;
  96. isSpanGroupToggler?: boolean;
  97. }) => {
  98. const buttonTheme = isExpanded ? theme.button.default : theme.button.primary;
  99. const errorTheme = theme.button.danger;
  100. const background = errored
  101. ? isExpanded
  102. ? buttonTheme.background
  103. : errorTheme.background
  104. : buttonTheme.background;
  105. const border = errored ? errorTheme.background : buttonTheme.border;
  106. const color = errored
  107. ? isExpanded
  108. ? errorTheme.background
  109. : buttonTheme.color
  110. : buttonTheme.color;
  111. if (isSpanGroupToggler) {
  112. return `
  113. background: ${theme.blue300};
  114. border: 1px solid ${theme.button.default.border};
  115. color: ${color};
  116. cursor: pointer;
  117. `;
  118. }
  119. if (disabled) {
  120. return `
  121. background: ${background};
  122. border: 1px solid ${border};
  123. color: ${color};
  124. cursor: default;
  125. `;
  126. }
  127. return `
  128. background: ${background};
  129. border: 1px solid ${border};
  130. color: ${color};
  131. `;
  132. };
  133. export const getDurationDisplay = ({
  134. width,
  135. left,
  136. }: {
  137. left: undefined | number;
  138. width: undefined | number;
  139. }): DurationDisplay => {
  140. const spaceNeeded = 0.3;
  141. if (left === undefined || width === undefined) {
  142. return 'inset';
  143. }
  144. if (left + width < 1 - spaceNeeded) {
  145. return 'right';
  146. }
  147. if (left > spaceNeeded) {
  148. return 'left';
  149. }
  150. return 'inset';
  151. };
  152. export const getHumanDuration = (duration: number): string => {
  153. // note: duration is assumed to be in seconds
  154. const durationMs = duration * 1000;
  155. return `${durationMs.toLocaleString(undefined, {
  156. minimumFractionDigits: 2,
  157. maximumFractionDigits: 2,
  158. })}ms`;
  159. };
  160. export const toPercent = (value: number) => `${(value * 100).toFixed(3)}%`;
  161. type Rect = {
  162. height: number;
  163. width: number;
  164. // x and y are left/top coords respectively
  165. x: number;
  166. y: number;
  167. };
  168. // get position of element relative to top/left of document
  169. export const getOffsetOfElement = (element: Element) => {
  170. // left and top are relative to viewport
  171. const {left, top} = element.getBoundingClientRect();
  172. // get values that the document is currently scrolled by
  173. const scrollLeft = window.pageXOffset;
  174. const scrollTop = window.pageYOffset;
  175. return {x: left + scrollLeft, y: top + scrollTop};
  176. };
  177. export const rectOfContent = (element: Element): Rect => {
  178. const {x, y} = getOffsetOfElement(element);
  179. // offsets for the border and any scrollbars (clientLeft and clientTop),
  180. // and if the element was scrolled (scrollLeft and scrollTop)
  181. //
  182. // NOTE: clientLeft and clientTop does not account for any margins nor padding
  183. const contentOffsetLeft = element.clientLeft - element.scrollLeft;
  184. const contentOffsetTop = element.clientTop - element.scrollTop;
  185. return {
  186. x: x + contentOffsetLeft,
  187. y: y + contentOffsetTop,
  188. width: element.scrollWidth,
  189. height: element.scrollHeight,
  190. };
  191. };
  192. export const clamp = (value: number, min: number, max: number): number => {
  193. if (value < min) {
  194. return min;
  195. }
  196. if (value > max) {
  197. return max;
  198. }
  199. return value;
  200. };
  201. const getLetterIndex = (letter: string): number => {
  202. const index = 'abcdefghijklmnopqrstuvwxyz'.indexOf(letter) || 0;
  203. return index === -1 ? 0 : index;
  204. };
  205. const colorsAsArray = Object.keys(CHART_PALETTE).map(key => CHART_PALETTE[17][key]);
  206. export const barColors = {
  207. default: CHART_PALETTE[17][4],
  208. transaction: CHART_PALETTE[17][8],
  209. http: CHART_PALETTE[17][10],
  210. db: CHART_PALETTE[17][17],
  211. };
  212. export const pickBarColor = (input: string | undefined): string => {
  213. // We pick the color for span bars using the first three letters of the op name.
  214. // That way colors stay consistent between transactions.
  215. if (!input || input.length < 3) {
  216. return CHART_PALETTE[17][4];
  217. }
  218. if (barColors[input]) {
  219. return barColors[input];
  220. }
  221. const letterIndex1 = getLetterIndex(input.slice(0, 1));
  222. const letterIndex2 = getLetterIndex(input.slice(1, 2));
  223. const letterIndex3 = getLetterIndex(input.slice(2, 3));
  224. const letterIndex4 = getLetterIndex(input.slice(3, 4));
  225. return colorsAsArray[
  226. (letterIndex1 + letterIndex2 + letterIndex3 + letterIndex4) % colorsAsArray.length
  227. ];
  228. };