Files
2024-02-15 17:01:50 +00:00

274 lines
6.6 KiB
TypeScript

import type {Theme} from '@emotion/react';
import Color from 'color';
import type {DurationDisplay} from 'sentry/components/performance/waterfall/types';
import {CHART_PALETTE} from 'sentry/constants/chartPalette';
import {space} from 'sentry/styles/space';
import type {SpanBarType} from './constants';
import {getSpanBarColours} from './constants';
export const getBackgroundColor = ({
showStriping,
showDetail,
theme,
}: {
theme: Theme;
showDetail?: boolean;
showStriping?: boolean;
}) => {
if (showDetail) {
return theme.textColor;
}
if (showStriping) {
return theme.backgroundSecondary;
}
return theme.background;
};
export function getHatchPattern(spanBarType: SpanBarType | undefined, theme: Theme) {
if (spanBarType) {
const {primary, alternate} = getSpanBarColours(spanBarType, theme);
return `
background-image: linear-gradient(135deg,
${alternate},
${alternate} 2.5px,
${primary} 2.5px,
${primary} 5px,
${alternate} 6px,
${alternate} 8px,
${primary} 8px,
${primary} 11px,
${alternate} 11px,
${alternate} 14px,
${primary} 14px,
${primary} 16.5px,
${alternate} 16.5px,
${alternate} 19px,
${primary} 20px
);
background-size: 16px 16px;
`;
}
return null;
}
export const getDurationPillAlignment = ({
durationDisplay,
}: {
durationDisplay: DurationDisplay;
theme: Theme;
spanBarType?: SpanBarType;
}) => {
switch (durationDisplay) {
case 'left':
return `right: calc(100% + ${space(0.5)});`;
case 'right':
return `left: calc(100% + ${space(0.75)});`;
default:
return `
right: ${space(0.75)};
`;
}
};
export const getDurationPillColours = ({
durationDisplay,
theme,
showDetail,
spanBarType,
}: {
durationDisplay: DurationDisplay;
showDetail: boolean;
theme: Theme;
spanBarType?: SpanBarType;
}) => {
if (durationDisplay === 'inset') {
const {alternate, insetTextColour} = getSpanBarColours(spanBarType, theme);
return `background: ${alternate}; color: ${insetTextColour};`;
}
return `color: ${showDetail ? theme.gray200 : theme.gray300};`;
};
export const getToggleTheme = ({
theme,
isExpanded,
disabled,
errored,
isSpanGroupToggler,
spanBarType,
}: {
disabled: boolean;
errored: boolean;
isExpanded: boolean;
theme: Theme;
isSpanGroupToggler?: boolean;
spanBarType?: SpanBarType;
}) => {
if (spanBarType) {
const {primary} = getSpanBarColours(spanBarType, theme);
return `
background: ${primary};
border: 2px solid ${theme.button.default.border};
color: ${theme.button.primary.color};
cursor: pointer;
`;
}
const buttonTheme = isExpanded ? theme.button.default : theme.button.primary;
const errorTheme = theme.button.danger;
const background = errored
? isExpanded
? buttonTheme.background
: errorTheme.background
: buttonTheme.background;
const border = errored ? errorTheme.background : buttonTheme.border;
const color = errored
? isExpanded
? errorTheme.background
: buttonTheme.color
: buttonTheme.color;
if (isSpanGroupToggler) {
return `
background: ${theme.blue300};
border: 2px solid ${theme.button.default.border};
color: ${color};
cursor: pointer;
`;
}
if (disabled) {
return `
background: ${background};
border: 2px solid ${border};
color: ${color};
cursor: default;
`;
}
return `
background: ${background};
border: 2px solid ${border};
color: ${color};
`;
};
export const getDurationDisplay = ({
width,
left,
}: {
left: undefined | number;
width: undefined | number;
}): DurationDisplay => {
const spaceNeeded = 0.3;
if (left === undefined || width === undefined) {
return 'inset';
}
if (left + width < 1 - spaceNeeded) {
return 'right';
}
if (left > spaceNeeded) {
return 'left';
}
return 'inset';
};
export const getHumanDuration = (duration: number): string => {
// note: duration is assumed to be in seconds
const durationMs = duration * 1000;
return `${durationMs.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}ms`;
};
type Rect = {
height: number;
width: number;
// x and y are left/top coords respectively
x: number;
y: number;
};
// get position of element relative to top/left of document
export const getOffsetOfElement = (element: Element) => {
// left and top are relative to viewport
const {left, top} = element.getBoundingClientRect();
// get values that the document is currently scrolled by
const scrollLeft = window.pageXOffset;
const scrollTop = window.pageYOffset;
return {x: left + scrollLeft, y: top + scrollTop};
};
export const rectOfContent = (element: Element): Rect => {
const {x, y} = getOffsetOfElement(element);
// offsets for the border and any scrollbars (clientLeft and clientTop),
// and if the element was scrolled (scrollLeft and scrollTop)
//
// NOTE: clientLeft and clientTop does not account for any margins nor padding
const contentOffsetLeft = element.clientLeft - element.scrollLeft;
const contentOffsetTop = element.clientTop - element.scrollTop;
return {
x: x + contentOffsetLeft,
y: y + contentOffsetTop,
width: element.scrollWidth,
height: element.scrollHeight,
};
};
const getLetterIndex = (letter: string): number => {
const index = 'abcdefghijklmnopqrstuvwxyz'.indexOf(letter) || 0;
return index === -1 ? 0 : index;
};
const colorsAsArray = Object.keys(CHART_PALETTE).map(key => CHART_PALETTE[17][key]);
export const barColors = {
default: CHART_PALETTE[17][4],
transaction: CHART_PALETTE[17][8],
http: CHART_PALETTE[17][10],
db: CHART_PALETTE[17][17],
};
export const pickBarColor = (input: string | undefined): string => {
// We pick the color for span bars using the first three letters of the op name.
// That way colors stay consistent between transactions.
if (!input || input.length < 3) {
return CHART_PALETTE[17][4];
}
if (barColors[input]) {
return barColors[input];
}
const letterIndex1 = getLetterIndex(input[0]);
const letterIndex2 = getLetterIndex(input[1]);
const letterIndex3 = getLetterIndex(input[2]);
const letterIndex4 = getLetterIndex(input[3]);
return colorsAsArray[
(letterIndex1 + letterIndex2 + letterIndex3 + letterIndex4) % colorsAsArray.length
];
};
export const lightenBarColor = (
input: string | undefined,
lightenRatio: number
): string => {
const barColor = pickBarColor(input);
return Color(barColor).lighten(lightenRatio).string();
};