123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- import * as React from 'react';
- import styled from '@emotion/styled';
- import DateTime from 'app/components/dateTime';
- import {SpanDetailContainer} from 'app/components/events/interfaces/spans/spanDetail';
- import {rawSpanKeys, SpanType} from 'app/components/events/interfaces/spans/types';
- import {getHumanDuration} from 'app/components/performance/waterfall/utils';
- import Pill from 'app/components/pill';
- import Pills from 'app/components/pills';
- import {t} from 'app/locale';
- import space from 'app/styles/space';
- import getDynamicText from 'app/utils/getDynamicText';
- import theme from 'app/utils/theme';
- import SpanDetailContent from './spanDetailContent';
- import {SpanBarRectangle} from './styles';
- import {
- DiffSpanType,
- generateCSSWidth,
- getSpanDuration,
- SpanGeneratedBoundsType,
- SpanWidths,
- } from './utils';
- type DurationDisplay = 'right' | 'inset';
- const getDurationDisplay = (width: SpanWidths | undefined): DurationDisplay => {
- if (!width) {
- return 'right';
- }
- switch (width.type) {
- case 'WIDTH_PIXEL': {
- return 'right';
- }
- case 'WIDTH_PERCENTAGE': {
- const spaceNeeded = 0.3;
- if (width.width < 1 - spaceNeeded) {
- return 'right';
- }
- return 'inset';
- }
- default: {
- const _exhaustiveCheck: never = width;
- return _exhaustiveCheck;
- }
- }
- };
- type Props = {
- span: Readonly<DiffSpanType>;
- bounds: SpanGeneratedBoundsType;
- };
- class SpanDetail extends React.Component<Props> {
- renderContent() {
- const {span, bounds} = this.props;
- switch (span.comparisonResult) {
- case 'matched': {
- return (
- <MatchedSpanDetailsContent
- baselineSpan={span.baselineSpan}
- regressionSpan={span.regressionSpan}
- bounds={bounds}
- />
- );
- }
- case 'regression': {
- return <SpanDetailContent span={span.regressionSpan} />;
- }
- case 'baseline': {
- return <SpanDetailContent span={span.baselineSpan} />;
- }
- default: {
- const _exhaustiveCheck: never = span;
- return _exhaustiveCheck;
- }
- }
- }
- render() {
- return (
- <SpanDetailContainer
- onClick={event => {
- // prevent toggling the span detail
- event.stopPropagation();
- }}
- >
- {this.renderContent()}
- </SpanDetailContainer>
- );
- }
- }
- const MatchedSpanDetailsContent = (props: {
- baselineSpan: SpanType;
- regressionSpan: SpanType;
- bounds: SpanGeneratedBoundsType;
- }) => {
- const {baselineSpan, regressionSpan, bounds} = props;
- const dataKeys = new Set([
- ...Object.keys(baselineSpan?.data ?? {}),
- ...Object.keys(regressionSpan?.data ?? {}),
- ]);
- const unknownKeys = new Set([
- ...Object.keys(baselineSpan).filter(key => {
- return !rawSpanKeys.has(key as any);
- }),
- ...Object.keys(regressionSpan).filter(key => {
- return !rawSpanKeys.has(key as any);
- }),
- ]);
- return (
- <div>
- <SpanBars
- bounds={bounds}
- baselineSpan={baselineSpan}
- regressionSpan={regressionSpan}
- />
- <Row
- baselineTitle={t('Baseline Span ID')}
- regressionTitle={t("This Event's Span ID")}
- renderBaselineContent={() => baselineSpan.span_id}
- renderRegressionContent={() => regressionSpan.span_id}
- />
- <Row
- title={t('Parent Span ID')}
- renderBaselineContent={() => baselineSpan.parent_span_id || ''}
- renderRegressionContent={() => regressionSpan.parent_span_id || ''}
- />
- <Row
- title={t('Trace ID')}
- renderBaselineContent={() => baselineSpan.trace_id}
- renderRegressionContent={() => regressionSpan.trace_id}
- />
- <Row
- title={t('Description')}
- renderBaselineContent={() => baselineSpan.description ?? ''}
- renderRegressionContent={() => regressionSpan.description ?? ''}
- />
- <Row
- title={t('Start Date')}
- renderBaselineContent={() =>
- getDynamicText({
- fixed: 'Mar 16, 2020 9:10:12 AM UTC',
- value: (
- <React.Fragment>
- <DateTime date={baselineSpan.start_timestamp * 1000} />
- {` (${baselineSpan.start_timestamp})`}
- </React.Fragment>
- ),
- })
- }
- renderRegressionContent={() =>
- getDynamicText({
- fixed: 'Mar 16, 2020 9:10:12 AM UTC',
- value: (
- <React.Fragment>
- <DateTime date={regressionSpan.start_timestamp * 1000} />
- {` (${baselineSpan.start_timestamp})`}
- </React.Fragment>
- ),
- })
- }
- />
- <Row
- title={t('End Date')}
- renderBaselineContent={() =>
- getDynamicText({
- fixed: 'Mar 16, 2020 9:10:12 AM UTC',
- value: (
- <React.Fragment>
- <DateTime date={baselineSpan.timestamp * 1000} />
- {` (${baselineSpan.timestamp})`}
- </React.Fragment>
- ),
- })
- }
- renderRegressionContent={() =>
- getDynamicText({
- fixed: 'Mar 16, 2020 9:10:12 AM UTC',
- value: (
- <React.Fragment>
- <DateTime date={regressionSpan.timestamp * 1000} />
- {` (${regressionSpan.timestamp})`}
- </React.Fragment>
- ),
- })
- }
- />
- <Row
- title={t('Duration')}
- renderBaselineContent={() => {
- const startTimestamp: number = baselineSpan.start_timestamp;
- const endTimestamp: number = baselineSpan.timestamp;
- const duration = (endTimestamp - startTimestamp) * 1000;
- return `${duration.toFixed(3)}ms`;
- }}
- renderRegressionContent={() => {
- const startTimestamp: number = regressionSpan.start_timestamp;
- const endTimestamp: number = regressionSpan.timestamp;
- const duration = (endTimestamp - startTimestamp) * 1000;
- return `${duration.toFixed(3)}ms`;
- }}
- />
- <Row
- title={t('Operation')}
- renderBaselineContent={() => baselineSpan.op || ''}
- renderRegressionContent={() => regressionSpan.op || ''}
- />
- <Row
- title={t('Same Process as Parent')}
- renderBaselineContent={() => String(!!baselineSpan.same_process_as_parent)}
- renderRegressionContent={() => String(!!regressionSpan.same_process_as_parent)}
- />
- <Tags baselineSpan={baselineSpan} regressionSpan={regressionSpan} />
- {Array.from(dataKeys).map((dataTitle: string) => (
- <Row
- key={dataTitle}
- title={dataTitle}
- renderBaselineContent={() => {
- const data = baselineSpan?.data ?? {};
- const value: string | undefined = data[dataTitle];
- return JSON.stringify(value, null, 4) || '';
- }}
- renderRegressionContent={() => {
- const data = regressionSpan?.data ?? {};
- const value: string | undefined = data[dataTitle];
- return JSON.stringify(value, null, 4) || '';
- }}
- />
- ))}
- {Array.from(unknownKeys).map(key => (
- <Row
- key={key}
- title={key}
- renderBaselineContent={() => {
- return JSON.stringify(baselineSpan[key], null, 4) || '';
- }}
- renderRegressionContent={() => {
- return JSON.stringify(regressionSpan[key], null, 4) || '';
- }}
- />
- ))}
- </div>
- );
- };
- const RowSplitter = styled('div')`
- display: flex;
- flex-direction: row;
- > * + * {
- border-left: 1px solid ${p => p.theme.border};
- }
- `;
- const SpanBarContainer = styled('div')`
- position: relative;
- height: 16px;
- margin-top: ${space(3)};
- margin-bottom: ${space(2)};
- `;
- const SpanBars = (props: {
- bounds: SpanGeneratedBoundsType;
- baselineSpan: SpanType;
- regressionSpan: SpanType;
- }) => {
- const {bounds, baselineSpan, regressionSpan} = props;
- const baselineDurationDisplay = getDurationDisplay(bounds.baseline);
- const regressionDurationDisplay = getDurationDisplay(bounds.regression);
- return (
- <RowSplitter>
- <RowContainer>
- <SpanBarContainer>
- <SpanBarRectangle
- style={{
- backgroundColor: theme.gray500,
- width: generateCSSWidth(bounds.baseline),
- position: 'absolute',
- height: '16px',
- }}
- >
- <DurationPill
- durationDisplay={baselineDurationDisplay}
- fontColors={{right: theme.gray500, inset: theme.white}}
- >
- {getHumanDuration(getSpanDuration(baselineSpan))}
- </DurationPill>
- </SpanBarRectangle>
- </SpanBarContainer>
- </RowContainer>
- <RowContainer>
- <SpanBarContainer>
- <SpanBarRectangle
- style={{
- backgroundColor: theme.purple200,
- width: generateCSSWidth(bounds.regression),
- position: 'absolute',
- height: '16px',
- }}
- >
- <DurationPill
- durationDisplay={regressionDurationDisplay}
- fontColors={{right: theme.gray500, inset: theme.gray500}}
- >
- {getHumanDuration(getSpanDuration(regressionSpan))}
- </DurationPill>
- </SpanBarRectangle>
- </SpanBarContainer>
- </RowContainer>
- </RowSplitter>
- );
- };
- const Row = (props: {
- title?: string;
- baselineTitle?: string;
- regressionTitle?: string;
- renderBaselineContent: () => React.ReactNode;
- renderRegressionContent: () => React.ReactNode;
- }) => {
- const {title, baselineTitle, regressionTitle} = props;
- const baselineContent = props.renderBaselineContent();
- const regressionContent = props.renderRegressionContent();
- if (!baselineContent && !regressionContent) {
- return null;
- }
- return (
- <RowSplitter>
- <RowCell title={baselineTitle ?? title ?? ''}>{baselineContent}</RowCell>
- <RowCell title={regressionTitle ?? title ?? ''}>{regressionContent}</RowCell>
- </RowSplitter>
- );
- };
- const RowContainer = styled('div')`
- width: 50%;
- min-width: 50%;
- max-width: 50%;
- flex-basis: 50%;
- padding-left: ${space(2)};
- padding-right: ${space(2)};
- `;
- const RowTitle = styled('div')`
- font-size: 13px;
- font-weight: 600;
- `;
- const RowCell = ({title, children}: {title: string; children: React.ReactNode}) => {
- return (
- <RowContainer>
- <RowTitle>{title}</RowTitle>
- <div>
- <pre className="val" style={{marginBottom: space(1)}}>
- <span className="val-string">{children}</span>
- </pre>
- </div>
- </RowContainer>
- );
- };
- const getTags = (span: SpanType) => {
- const tags: {[tag_name: string]: string} | undefined = span?.tags;
- if (!tags) {
- return undefined;
- }
- const keys = Object.keys(tags);
- if (keys.length <= 0) {
- return undefined;
- }
- return tags;
- };
- const TagPills = ({tags}: {tags: {[tag_name: string]: string} | undefined}) => {
- if (!tags) {
- return null;
- }
- const keys = Object.keys(tags);
- if (keys.length <= 0) {
- return null;
- }
- return (
- <Pills>
- {keys.map((key, index) => (
- <Pill key={index} name={key} value={String(tags[key]) || ''} />
- ))}
- </Pills>
- );
- };
- const Tags = ({
- baselineSpan,
- regressionSpan,
- }: {
- baselineSpan: SpanType;
- regressionSpan: SpanType;
- }) => {
- const baselineTags = getTags(baselineSpan);
- const regressionTags = getTags(regressionSpan);
- if (!baselineTags && !regressionTags) {
- return null;
- }
- return (
- <RowSplitter>
- <RowContainer>
- <RowTitle>{t('Tags')}</RowTitle>
- <div>
- <TagPills tags={baselineTags} />
- </div>
- </RowContainer>
- <RowContainer>
- <RowTitle>{t('Tags')}</RowTitle>
- <div>
- <TagPills tags={regressionTags} />
- </div>
- </RowContainer>
- </RowSplitter>
- );
- };
- const DurationPill = styled('div')<{
- durationDisplay: DurationDisplay;
- fontColors: {right: string; inset: string};
- }>`
- position: absolute;
- top: 50%;
- display: flex;
- align-items: center;
- transform: translateY(-50%);
- white-space: nowrap;
- font-size: ${p => p.theme.fontSizeExtraSmall};
- color: ${p => p.fontColors.right};
- ${p => {
- switch (p.durationDisplay) {
- case 'right':
- return `left: calc(100% + ${space(0.75)});`;
- default:
- return `
- right: ${space(0.75)};
- color: ${p.fontColors.inset};
- `;
- }
- }};
- @media (max-width: ${p => p.theme.breakpoints[1]}) {
- font-size: 10px;
- }
- `;
- export default SpanDetail;
|