123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import DateTime from 'sentry/components/dateTime';
- import {Tooltip} from 'sentry/components/tooltip';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Event} from 'sentry/types';
- import {TraceTimelineTooltip} from 'sentry/views/issueDetails/traceTimeline/traceTimelineTooltip';
- import type {TimelineEvent} from './useTraceTimelineEvents';
- import {useTraceTimelineEvents} from './useTraceTimelineEvents';
- import {getChunkTimeRange, getEventsByColumn} from './utils';
- // Adjusting this will change the number of tooltip groups
- const PARENT_WIDTH = 12;
- // Adjusting subwidth changes how many dots to render
- const CHILD_WIDTH = 4;
- interface TraceTimelineEventsProps {
- event: Event;
- width: number;
- }
- export function TraceTimelineEvents({event, width}: TraceTimelineEventsProps) {
- const {startTimestamp, endTimestamp, data} = useTraceTimelineEvents({event});
- let durationMs = endTimestamp - startTimestamp;
- const paddedStartTime = startTimestamp - 200;
- let paddedEndTime = endTimestamp + 100;
- // Will need to figure out padding
- if (durationMs === 0) {
- durationMs = 1000;
- // If the duration is 0, we need to pad the end time
- paddedEndTime = startTimestamp + 1000;
- }
- const totalColumns = Math.floor(width / PARENT_WIDTH);
- const eventsByColumn = getEventsByColumn(
- durationMs,
- data,
- totalColumns,
- paddedStartTime
- );
- const columnSize = width / totalColumns;
- return (
- <Fragment>
- <TimelineColumns totalColumns={totalColumns}>
- {Array.from(eventsByColumn.entries()).map(([column, colEvents]) => {
- // Calculate the timestamp range that this column represents
- const timeRange = getChunkTimeRange(
- paddedStartTime,
- column - 1,
- durationMs / totalColumns
- );
- return (
- <EventColumn
- key={column}
- style={{gridColumn: Math.floor(column), width: columnSize}}
- >
- <NodeGroup
- event={event}
- colEvents={colEvents}
- columnSize={columnSize}
- timeRange={timeRange}
- currentEventId={event.id}
- />
- </EventColumn>
- );
- })}
- </TimelineColumns>
- <TimestampColumns>
- <TimestampItem style={{textAlign: 'left'}}>
- <DateTime date={paddedStartTime} timeOnly />
- </TimestampItem>
- <TimestampItem style={{textAlign: 'center'}}>
- <DateTime date={paddedStartTime + Math.floor(durationMs / 2)} timeOnly />
- </TimestampItem>
- <TimestampItem style={{textAlign: 'right'}}>
- <DateTime date={paddedEndTime} timeOnly />
- </TimestampItem>
- </TimestampColumns>
- </Fragment>
- );
- }
- /**
- * Use grid to create columns that we can place child nodes into.
- * Leveraging grid for alignment means we don't need to calculate percent offset
- * nor use position:absolute to lay out items.
- *
- * <Columns>
- * <Col>...</Col>
- * <Col>...</Col>
- * </Columns>
- */
- const TimelineColumns = styled('ul')<{totalColumns: number}>`
- /* Reset defaults for <ul> */
- list-style: none;
- margin: 0;
- padding: 0;
- /* Layout of the lines */
- display: grid;
- grid-template-columns: repeat(${p => p.totalColumns}, 1fr);
- margin-top: -1px;
- `;
- const TimestampColumns = styled('div')`
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- margin-top: ${space(1)};
- `;
- const TimestampItem = styled('div')`
- place-items: stretch;
- display: grid;
- align-items: center;
- position: relative;
- color: ${p => p.theme.subText};
- font-size: ${p => p.theme.fontSizeSmall};
- `;
- function NodeGroup({
- event,
- timeRange,
- colEvents,
- columnSize,
- currentEventId,
- }: {
- colEvents: TimelineEvent[];
- columnSize: number;
- currentEventId: string;
- event: Event;
- timeRange: [number, number];
- }) {
- const totalSubColumns = Math.floor(columnSize / CHILD_WIDTH);
- const durationMs = timeRange[1] - timeRange[0];
- const eventsByColumn = getEventsByColumn(
- durationMs,
- colEvents,
- totalSubColumns,
- timeRange[0]
- );
- return (
- <Tooltip
- title={<TraceTimelineTooltip event={event} timelineEvents={colEvents} />}
- overlayStyle={{
- padding: `0 !important`,
- maxWidth: '250px !important',
- width: '250px',
- }}
- offset={10}
- position="bottom"
- isHoverable
- skipWrapper
- >
- <TimelineColumns totalColumns={totalSubColumns}>
- {Array.from(eventsByColumn.entries()).map(([column, groupEvents]) => (
- <EventColumn key={column} style={{gridColumn: Math.floor(column)}}>
- {groupEvents.map(groupEvent => (
- // TODO: use sentry colors and add the other styles
- <IconNode
- key={groupEvent.id}
- aria-label={
- groupEvent.id === currentEventId ? t('Current Event') : undefined
- }
- style={
- groupEvent.id === currentEventId
- ? {
- backgroundColor: 'rgb(181, 19, 7, 1)',
- outline: '1px solid rgb(181, 19, 7, 0.5)',
- outlineOffset: '3px',
- }
- : undefined
- }
- />
- ))}
- </EventColumn>
- ))}
- </TimelineColumns>
- </Tooltip>
- );
- }
- const EventColumn = styled('li')`
- place-items: stretch;
- display: grid;
- align-items: center;
- position: relative;
- &:hover {
- z-index: ${p => p.theme.zIndex.initial};
- }
- `;
- const IconNode = styled('div')`
- position: absolute;
- grid-column: 1;
- grid-row: 1;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- color: ${p => p.theme.white};
- box-shadow: ${p => p.theme.dropShadowLight};
- user-select: none;
- background-color: rgb(181, 19, 7, 0.2);
- `;
|