diff --git a/web/src/components/AnalyzerResult/RuleResult.tsx b/web/src/components/AnalyzerResult/RuleResult.tsx index 89a42f8bd1..9ad5ce6a10 100644 --- a/web/src/components/AnalyzerResult/RuleResult.tsx +++ b/web/src/components/AnalyzerResult/RuleResult.tsx @@ -27,36 +27,46 @@ const RuleResult = ({index, data: {results, id, errorDescription}, style}: IProp dispatch(selectSpan({spanId})); }, [dispatch, spanId]); - return ( -
- } onClick={onClick} type="link" $error={!passed}> - {trace.flat[spanId].name ?? ''} - + const getTooltipOverlayFn = useMemo( + () => + !passed && !!errors.length + ? () => ( + <> + {errors.length > 1 && ( + <> +
+ {errorDescription} +
+ + {errors.map(error => ( +
  • + {error.value} +
  • + ))} +
    + + )} - {!passed && errors.length > 1 && ( - <> -
    - {errorDescription} -
    - - {errors.map(error => ( -
  • - - {error.value} - -
  • - ))} -
    - - )} + {errors.length === 1 && ( +
    + {errors[0].description} +
    + )} - {!passed && errors.length === 1 && ( -
    - {errors[0].description} -
    - )} + + + ) + : null, + [passed, errors, errorDescription, id] + ); - {!passed && } + return ( +
    + + } onClick={onClick} type="link" $error={!passed}> + {trace.flat[spanId].name ?? ''} + +
    ); }; diff --git a/web/src/components/ResizablePanels/FillPanel.tsx b/web/src/components/ResizablePanels/FillPanel.tsx index 4973a4df75..207c688695 100644 --- a/web/src/components/ResizablePanels/FillPanel.tsx +++ b/web/src/components/ResizablePanels/FillPanel.tsx @@ -1,5 +1,5 @@ import * as Spaces from 'react-spaces'; -const FillPanel: React.FC = ({children}) => {children}; +const FillPanel: React.FC = ({children}) => {children}; export default FillPanel; diff --git a/web/src/components/RunDetailTest/RunDetailTest.styled.ts b/web/src/components/RunDetailTest/RunDetailTest.styled.ts index 8c3b8eb12b..795ce5e272 100644 --- a/web/src/components/RunDetailTest/RunDetailTest.styled.ts +++ b/web/src/components/RunDetailTest/RunDetailTest.styled.ts @@ -1,5 +1,5 @@ import {Badge} from 'antd'; -import styled from 'styled-components'; +import styled, {css} from 'styled-components'; export const Container = styled.div` display: flex; @@ -11,10 +11,17 @@ export const Section = styled.div` flex: 1; `; -export const SectionLeft = styled(Section)` +export const SectionLeft = styled(Section)<{$isTimeline: boolean}>` background-color: ${({theme}) => theme.color.background}; box-shadow: inset 20px 0px 24px -20px rgba(153, 155, 168, 0.18), inset -20px 0 24px -20px rgba(153, 155, 168, 0.18); z-index: 1; + + ${({$isTimeline}) => + $isTimeline && + css` + max-size: calc(100% - 695px); + overflow: scroll; + `} `; export const SectionRight = styled(Section)` diff --git a/web/src/components/RunDetailTest/TestPanel.tsx b/web/src/components/RunDetailTest/TestPanel.tsx index e7b3b19dd8..34d17c6485 100644 --- a/web/src/components/RunDetailTest/TestPanel.tsx +++ b/web/src/components/RunDetailTest/TestPanel.tsx @@ -100,18 +100,10 @@ const TestPanel = ({run, testId, runEvents}: IProps) => { [revert] ); - const handleSelectSpan = useCallback( - (spanId: string) => { - onSelectSpan(spanId); - onSetFocusedSpan(spanId); - }, - [onSelectSpan, onSetFocusedSpan] - ); - return ( - + {run.state === TestState.FINISHED && ( ) : ( - ` display: flex; height: 100%; width: 100%; + min-width: ${({$isTimeline}) => $isTimeline && '1000px'}; `; export const SearchContainer = styled.div` diff --git a/web/src/components/RunDetailTrace/RunDetailTrace.tsx b/web/src/components/RunDetailTrace/RunDetailTrace.tsx index 5953b75b25..07a43fa872 100644 --- a/web/src/components/RunDetailTrace/RunDetailTrace.tsx +++ b/web/src/components/RunDetailTrace/RunDetailTrace.tsx @@ -26,7 +26,7 @@ export function getIsDAGDisabled(totalSpans: number = 0): boolean { const RunDetailTrace = ({run, runEvents, testId, skipTraceCollection}: IProps) => { return ( - + diff --git a/web/src/components/RunDetailTrace/TracePanel.tsx b/web/src/components/RunDetailTrace/TracePanel.tsx index ed958e3f09..5b7a3dacb8 100644 --- a/web/src/components/RunDetailTrace/TracePanel.tsx +++ b/web/src/components/RunDetailTrace/TracePanel.tsx @@ -26,7 +26,7 @@ const TracePanel = ({run, testId, runEvents, skipTraceCollection}: TProps) => { return ( - + diff --git a/web/src/components/RunDetailTrace/Visualization.tsx b/web/src/components/RunDetailTrace/Visualization.tsx index b2a52f9135..00b889975c 100644 --- a/web/src/components/RunDetailTrace/Visualization.tsx +++ b/web/src/components/RunDetailTrace/Visualization.tsx @@ -9,7 +9,7 @@ import TraceSelectors from 'selectors/Trace.selectors'; import TestRunService from 'services/TestRun.service'; import Trace from 'models/Trace.model'; import {TTestRunState} from 'types/TestRun.types'; -import TimelineV2 from 'components/Visualization/components/Timeline/TimelineV2'; +import TimelineV2 from 'components/Visualization/components/Timeline/Timeline'; import {VisualizationType} from './RunDetailTrace'; import TraceDAG from './TraceDAG'; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNode.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNode.tsx index ff6a241e24..af6908a38e 100644 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNode.tsx +++ b/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNode.tsx @@ -1,75 +1,61 @@ -import {Group} from '@visx/group'; - -import {AxisOffset, BaseLeftPadding, NodeHeight, NodeOverlayHeight} from 'constants/Timeline.constants'; import Span from 'models/Span.model'; -import Collapse from './Collapse'; import Connector from './Connector'; -import Label from './Label'; import {IPropsComponent} from '../SpanNodeFactory'; +import {useTimeline} from '../Timeline.provider'; import * as S from '../Timeline.styled'; +function toPercent(value: number) { + return `${(value * 100).toFixed(1)}%`; +} + +function getHintSide(viewStart: number, viewEnd: number) { + return viewStart > 1 - viewEnd ? 'left' : 'right'; +} + interface IProps extends IPropsComponent { - className?: string; - header?: React.ReactNode; span: Span; } -const BaseSpanNode = ({ - className, - header, - index, - indexParent, - isCollapsed = false, - isMatched = false, - isSelected = false, - minStartTime, - node, - onClick, - onCollapse, - span, - xScale, -}: IProps) => { - const isParent = Boolean(node.children); - const hasParent = indexParent !== -1; - const positionTop = index * NodeHeight; - const durationWidth = span.endTime - span.startTime; - const durationX = span.startTime - minStartTime; - const leftPadding = node.depth * BaseLeftPadding; +const BaseSpanNode = ({index, node, span, style}: IProps) => { + const {collapsedSpans, getScale, matchedSpans, onSpanCollapse, onSpanClick, selectedSpan} = useTimeline(); + const {start: viewStart, end: viewEnd} = getScale(span.startTime, span.endTime); + const hintSide = getHintSide(viewStart, viewEnd); + const isSelected = selectedSpan === node.data.id; + const isMatched = matchedSpans.includes(node.data.id); + const isCollapsed = collapsedSpans.includes(node.data.id); return ( - - {hasParent && } - - onClick(node.data.id)} top={0}> - - - - - - - - +
    + onSpanClick(node.data.id)} + $isEven={index % 2 === 0} + $isMatched={isMatched} + $isSelected={isSelected} + > + + + + + {span.name} + + + + + + + + {span.duration} + + + +
    ); }; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNodeV2.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNodeV2.tsx deleted file mode 100644 index 96934f48a1..0000000000 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/BaseSpanNodeV2.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import Span from 'models/Span.model'; -import Connector from './ConnectorV2'; -import {IPropsComponent} from '../SpanNodeFactoryV2'; -import {useTimeline} from '../Timeline.provider'; -import * as S from '../TimelineV2.styled'; - -function toPercent(value: number) { - return `${(value * 100).toFixed(1)}%`; -} - -function getHintSide(viewStart: number, viewEnd: number) { - return viewStart > 1 - viewEnd ? 'left' : 'right'; -} - -interface IProps extends IPropsComponent { - span: Span; -} - -const BaseSpanNode = ({index, node, span, style}: IProps) => { - const {collapsedSpans, getScale, matchedSpans, onSpanCollapse, onSpanClick, selectedSpan} = useTimeline(); - const {start: viewStart, end: viewEnd} = getScale(span.startTime, span.endTime); - const hintSide = getHintSide(viewStart, viewEnd); - const isSelected = selectedSpan === node.data.id; - const isMatched = matchedSpans.includes(node.data.id); - const isCollapsed = collapsedSpans.includes(node.data.id); - - return ( -
    - onSpanClick(node.data.id)} - $isEven={index % 2 === 0} - $isMatched={isMatched} - $isSelected={isSelected} - > - - - - - {span.name} - - - - - - - - {span.duration} - - - -
    - ); -}; - -export default BaseSpanNode; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Collapse.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/Collapse.tsx deleted file mode 100644 index 7844c5349a..0000000000 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Collapse.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as S from '../Timeline.styled'; - -interface IProps { - id: string; - isCollapsed?: boolean; - onCollapse(id: string): void; - totalChildren: number; -} - -const collapsedShape = 'M18.629 15.997l-7.083-7.081L13.462 7l8.997 8.997L13.457 25l-1.916-1.916z'; -const expandedShape = 'M16.003 18.626l7.081-7.081L25 13.46l-8.997 8.998-9.003-9 1.917-1.916z'; - -const Collapse = ({id, isCollapsed, onCollapse, totalChildren}: IProps) => ( - { - event.stopPropagation(); - onCollapse(id); - }} - top={0} - > - - - - - {totalChildren} - - -); - -export default Collapse; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Connector.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/Connector.tsx index 56e79b9f15..574280caab 100644 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Connector.tsx +++ b/web/src/components/Visualization/components/Timeline/BaseSpanNode/Connector.tsx @@ -1,19 +1,55 @@ -import {NodeHeight} from 'constants/Timeline.constants'; +import {BaseLeftPaddingV2} from 'constants/Timeline.constants'; import * as S from '../Timeline.styled'; interface IProps { - distance: number; - leftPadding: number; + hasParent: boolean; + id: string; + isCollapsed: boolean; + nodeDepth: number; + onCollapse(id: string): void; + totalChildren: number; } -const Connector = ({distance, leftPadding}: IProps) => { - const connectorX = distance * NodeHeight - 24; +const Connector = ({hasParent, id, isCollapsed, nodeDepth, onCollapse, totalChildren}: IProps) => { + const leftPadding = nodeDepth * BaseLeftPaddingV2; return ( - <> - - - + + {hasParent && ( + <> + + + + )} + + {totalChildren > 0 ? ( + <> + {!isCollapsed && } + + + {totalChildren} + + { + event.stopPropagation(); + onCollapse(id); + }} + /> + + ) : ( + + )} + + {new Array(nodeDepth).fill(0).map((_, index) => { + return ; + })} + ); }; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/ConnectorV2.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/ConnectorV2.tsx deleted file mode 100644 index cb332a3f84..0000000000 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/ConnectorV2.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import {BaseLeftPaddingV2} from 'constants/Timeline.constants'; -import * as S from '../TimelineV2.styled'; - -interface IProps { - hasParent: boolean; - id: string; - isCollapsed: boolean; - nodeDepth: number; - onCollapse(id: string): void; - totalChildren: number; -} - -const Connector = ({hasParent, id, isCollapsed, nodeDepth, onCollapse, totalChildren}: IProps) => { - const leftPadding = nodeDepth * BaseLeftPaddingV2; - - return ( - - {hasParent && ( - <> - - - - )} - - {totalChildren > 0 ? ( - <> - {!isCollapsed && } - - - {totalChildren} - - { - event.stopPropagation(); - onCollapse(id); - }} - /> - - ) : ( - - )} - - {new Array(nodeDepth).fill(0).map((_, index) => { - return ; - })} - - ); -}; - -export default Connector; diff --git a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Label.tsx b/web/src/components/Visualization/components/Timeline/BaseSpanNode/Label.tsx deleted file mode 100644 index b741c0fc5d..0000000000 --- a/web/src/components/Visualization/components/Timeline/BaseSpanNode/Label.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import {Group} from '@visx/group'; - -import settingIcon from 'assets/setting.svg'; -import {SemanticGroupNames, SemanticGroupNamesToText} from 'constants/SemanticGroupNames.constants'; -import {SpanKind, SpanKindToText} from 'constants/Span.constants'; -import * as S from '../Timeline.styled'; - -interface IProps { - duration: string; - header?: React.ReactNode; - kind: SpanKind; - name: string; - service: string; - system: string; - type: SemanticGroupNames; -} - -const Label = ({duration, header, kind, name, service, system, type}: IProps) => ( - - - - - {SemanticGroupNamesToText[type]} - - - - - {header} - - - - - {name} - - - - - - {`${service} ${SpanKindToText[kind]}`} - {Boolean(system) && {` - ${system}`}} - {` - ${duration}`} - - - -); - -export default Label; diff --git a/web/src/components/Visualization/components/Timeline/Header.tsx b/web/src/components/Visualization/components/Timeline/Header.tsx index d5ff7cdc41..ed12e7f44f 100644 --- a/web/src/components/Visualization/components/Timeline/Header.tsx +++ b/web/src/components/Visualization/components/Timeline/Header.tsx @@ -1,5 +1,5 @@ import Ticks from './Ticks/Ticks'; -import * as S from './TimelineV2.styled'; +import * as S from './Timeline.styled'; const NUM_TICKS = 5; diff --git a/web/src/components/Visualization/components/Timeline/ListWrapper.tsx b/web/src/components/Visualization/components/Timeline/ListWrapper.tsx index cda954bfb7..cc8af525dd 100644 --- a/web/src/components/Visualization/components/Timeline/ListWrapper.tsx +++ b/web/src/components/Visualization/components/Timeline/ListWrapper.tsx @@ -1,7 +1,7 @@ import {FixedSizeList as List} from 'react-window'; import Header from './Header'; -import SpanNodeFactory from './SpanNodeFactoryV2'; -import * as S from './TimelineV2.styled'; +import SpanNodeFactory from './SpanNodeFactory'; +import * as S from './Timeline.styled'; import {useTimeline} from './Timeline.provider'; const HEADER_HEIGHT = 242; diff --git a/web/src/components/Visualization/components/Timeline/SpanNodeFactory.tsx b/web/src/components/Visualization/components/Timeline/SpanNodeFactory.tsx index 877cd4b46f..fb61d7f081 100644 --- a/web/src/components/Visualization/components/Timeline/SpanNodeFactory.tsx +++ b/web/src/components/Visualization/components/Timeline/SpanNodeFactory.tsx @@ -1,35 +1,30 @@ -import {AxisScale} from '@visx/axis'; import {NodeTypesEnum} from 'constants/Visualization.constants'; import {TNode} from 'types/Timeline.types'; -import TestSpanNode from './TestSpanNode/TestSpanNode'; +// import TestSpanNode from './TestSpanNode/TestSpanNode'; import TraceSpanNode from './TraceSpanNode/TraceSpanNode'; export interface IPropsComponent { index: number; - indexParent: number; - isCollapsed?: boolean; - isMatched?: boolean; - isSelected?: boolean; - minStartTime: number; node: TNode; - onClick(id: string): void; - onCollapse(id: string): void; - xScale: AxisScale; + style: React.CSSProperties; } const ComponentMap: Record React.ReactElement> = { - [NodeTypesEnum.TestSpan]: TestSpanNode, + [NodeTypesEnum.TestSpan]: TraceSpanNode, [NodeTypesEnum.TraceSpan]: TraceSpanNode, }; -interface IProps extends IPropsComponent { - type: NodeTypesEnum; +interface IProps { + data: TNode[]; + index: number; + style: React.CSSProperties; } -const SpanNodeFactory = ({type, ...props}: IProps) => { - const Component = ComponentMap[type]; +const SpanNodeFactory = ({data, ...props}: IProps) => { + const node = data[props.index]; + const Component = ComponentMap[node.type]; - return ; + return ; }; export default SpanNodeFactory; diff --git a/web/src/components/Visualization/components/Timeline/SpanNodeFactoryV2.tsx b/web/src/components/Visualization/components/Timeline/SpanNodeFactoryV2.tsx deleted file mode 100644 index 9e6aed970c..0000000000 --- a/web/src/components/Visualization/components/Timeline/SpanNodeFactoryV2.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import {NodeTypesEnum} from 'constants/Visualization.constants'; -import {TNode} from 'types/Timeline.types'; -// import TestSpanNode from './TestSpanNode/TestSpanNode'; -import TraceSpanNode from './TraceSpanNode/TraceSpanNodeV2'; - -export interface IPropsComponent { - index: number; - node: TNode; - style: React.CSSProperties; -} - -const ComponentMap: Record React.ReactElement> = { - [NodeTypesEnum.TestSpan]: TraceSpanNode, - [NodeTypesEnum.TraceSpan]: TraceSpanNode, -}; - -interface IProps { - data: TNode[]; - index: number; - style: React.CSSProperties; -} - -const SpanNodeFactory = ({data, ...props}: IProps) => { - const node = data[props.index]; - const Component = ComponentMap[node.type]; - - return ; -}; - -export default SpanNodeFactory; diff --git a/web/src/components/Visualization/components/Timeline/TestSpanNode/Header.tsx b/web/src/components/Visualization/components/Timeline/TestSpanNode/Header.tsx deleted file mode 100644 index 719cc274e2..0000000000 --- a/web/src/components/Visualization/components/Timeline/TestSpanNode/Header.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import {Group} from '@visx/group'; -import * as S from '../Timeline.styled'; - -interface IProps { - hasOutputs?: boolean; - totalFailedChecks?: number; - totalPassedChecks?: number; -} - -function getOutputsX(totalFailedChecks?: number, totalPassedChecks?: number): number { - if (totalFailedChecks && totalPassedChecks) { - return 44; - } - if (totalFailedChecks || totalPassedChecks) { - return 24; - } - - return 0; -} - -const Header = ({hasOutputs, totalFailedChecks, totalPassedChecks}: IProps) => { - const failedChecksX = totalPassedChecks ? 20 : 0; - const outputsX = getOutputsX(totalFailedChecks, totalPassedChecks); - - return ( - <> - - {!!totalPassedChecks && ( - <> - - - {totalPassedChecks} - - - )} - {!!totalFailedChecks && ( - <> - - - {totalFailedChecks} - - - )} - - - {hasOutputs && ( - <> - - - O - - - )} - - - ); -}; - -export default Header; diff --git a/web/src/components/Visualization/components/Timeline/TestSpanNode/SelectAsCurrent.tsx b/web/src/components/Visualization/components/Timeline/TestSpanNode/SelectAsCurrent.tsx deleted file mode 100644 index cc19df7d63..0000000000 --- a/web/src/components/Visualization/components/Timeline/TestSpanNode/SelectAsCurrent.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import {Group} from '@visx/group'; -import * as S from '../Timeline.styled'; - -interface IProps { - isLoading: boolean; - onSelectAsCurrent(): void; - positionTop: number; -} - -const SelectAsCurrent = ({isLoading, onSelectAsCurrent, positionTop}: IProps) => ( - !isLoading && onSelectAsCurrent()}> - - - {isLoading ? 'Updating selected span' : 'Select as current span'} - - -); - -export default SelectAsCurrent; diff --git a/web/src/components/Visualization/components/Timeline/TestSpanNode/TestSpanNode.tsx b/web/src/components/Visualization/components/Timeline/TestSpanNode/TestSpanNode.tsx deleted file mode 100644 index aa6ccc64dd..0000000000 --- a/web/src/components/Visualization/components/Timeline/TestSpanNode/TestSpanNode.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import {NodeHeight} from 'constants/Timeline.constants'; -import useSpanData from 'hooks/useSpanData'; -import Header from './Header'; -import SelectAsCurrent from './SelectAsCurrent'; -import BaseSpanNode from '../BaseSpanNode/BaseSpanNode'; -import {IPropsComponent} from '../SpanNodeFactory'; -import useSelectAsCurrent from '../../../hooks/useSelectAsCurrent'; - -const TestSpanNode = (props: IPropsComponent) => { - const {index, isMatched, isSelected, node} = props; - const {span, testSpecs, testOutputs} = useSpanData(node.data.id); - const {isLoading, onSelectAsCurrent, showSelectAsCurrent} = useSelectAsCurrent({ - selected: isSelected ?? false, - matched: isMatched ?? false, - span, - }); - const positionTop = index * NodeHeight; - - return ( - <> - - } - span={span} - /> - {showSelectAsCurrent && ( - - )} - - ); -}; - -export default TestSpanNode; diff --git a/web/src/components/Visualization/components/Timeline/Timeline.styled.ts b/web/src/components/Visualization/components/Timeline/Timeline.styled.ts index 285dcd96f6..42ef21dce2 100644 --- a/web/src/components/Visualization/components/Timeline/Timeline.styled.ts +++ b/web/src/components/Visualization/components/Timeline/Timeline.styled.ts @@ -1,132 +1,151 @@ -import {Group} from '@visx/group'; +import {Typography} from 'antd'; +import {SemanticGroupNames, SemanticGroupNamesToColor} from 'constants/SemanticGroupNames.constants'; import styled, {css} from 'styled-components'; -import { - SemanticGroupNames, - SemanticGroupNamesToColor, - SemanticGroupNamesToLightColor, -} from 'constants/SemanticGroupNames.constants'; +export const Container = styled.div` + padding: 50px 24px 0 24px; + min-width: 1000px; +`; -export const Container = styled.div<{$showMatched: boolean}>` - height: 100%; - padding: 24px; - padding-left: 50px; - position: relative; +export const Row = styled.div<{$isEven: boolean; $isMatched: boolean; $isSelected: boolean}>` + background-color: ${({theme, $isEven}) => ($isEven ? theme.color.background : theme.color.white)}; + display: grid; + grid-template-columns: 300px 1fr; + grid-template-rows: 32px; + padding: 0px 16px; + + :hover { + background-color: ${({theme}) => theme.color.backgroundInteractive}; + } + + ${({$isMatched}) => + $isMatched && + css` + background-color: ${({theme}) => theme.color.alertYellow}; + `}; - ${({$showMatched}) => - $showMatched && + ${({$isSelected}) => + $isSelected && css` - .timeline-node-traceSpan > g:not(.matched), - .timeline-node-testSpan > g:not(.matched):not(.selectedAsCurrent) { - opacity: 0.5; + background: rgba(97, 23, 94, 0.1); + + :hover { + background: rgba(97, 23, 94, 0.1); } - `} + `}; `; -export const CircleArrow = styled.circle` - fill: transparent; +export const Col = styled.div` + display: grid; + grid-template-columns: 1fr 8px; `; -export const CircleCheck = styled.circle<{$passed: boolean}>` - fill: ${({$passed, theme}) => ($passed ? theme.color.success : theme.color.error)}; +export const ColDuration = styled.div` + overflow: hidden; + position: relative; `; -export const CircleNumber = styled.circle` - fill: ${({theme}) => theme.color.borderLight}; +export const Header = styled.div` + align-items: center; + display: flex; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; -export const GroupCollapse = styled(Group)` - cursor: pointer; +export const NameContainer = styled.div` + overflow: hidden; + text-overflow: ellipsis; `; -export const Image = styled.image<{$width?: number; $height?: number}>` - height: ${({$height}) => $height ?? 8}px; - width: ${({$width}) => $width ?? 8}px; +export const Separator = styled.div` + border-left: 1px solid rgb(222, 227, 236); + cursor: ew-resize; + height: 32px; + padding: 0px 3px; + width: 1px; `; -export const LineConnector = styled.line` - stroke: ${({theme}) => theme.color.textLight}; +export const Title = styled(Typography.Text)` + color: ${({theme}) => theme.color.text}; + font-size: ${({theme}) => theme.size.sm}; + font-weight: 400; `; -export const PathArrow = styled.path` - fill: ${({theme}) => theme.color.textLight}; - transform: scale(0.6); +export const Connector = styled.svg` + flex-shrink: 0; + overflow: hidden; + overflow-clip-margin: content-box; `; -export const RectBadge = styled.rect<{$type: SemanticGroupNames}>` - fill: ${({$type}) => SemanticGroupNamesToLightColor[$type]}; - height: 12px; - width: 50px; - pointer-events: none; +export const SpanBar = styled.div<{$type: SemanticGroupNames}>` + background-color: ${({$type}) => SemanticGroupNamesToColor[$type]}; + border-radius: 3px; + height: 18px; + min-width: 2px; + position: absolute; + top: 7px; `; -export const RectDuration = styled.rect<{$type: SemanticGroupNames}>` - fill: ${({$type}) => SemanticGroupNamesToColor[$type]}; - height: 6px; - pointer-events: none; -`; +export const SpanBarLabel = styled.div<{$side: 'left' | 'right'}>` + color: ${({theme}) => theme.color.textSecondary}; + font-size: ${({theme}) => theme.size.xs}; + padding: 1px 4px 0 4px; + position: absolute; -export const RectDurationGuideline = styled.rect` - fill: ${({theme}) => theme.color.borderLight}; - height: 1px; - pointer-events: none; - width: 100%; + ${({$side}) => + $side === 'left' + ? css` + right: 100%; + ` + : css` + left: 100%; + `}; `; -export const RectSelectAsCurrent = styled.rect` - cursor: pointer; - fill: ${({theme}) => theme.color.interactive}; - height: 12px; - width: 124px; +export const TextConnector = styled.text<{$isActive?: boolean}>` + fill: ${({theme, $isActive}) => ($isActive ? theme.color.white : theme.color.text)}; + font-size: ${({theme}) => theme.size.xs}; `; -export const RectOutput = styled.rect` - fill: ${({theme}) => theme.color.warningYellow}; - height: 12px; - width: 12px; +export const CircleDot = styled.circle` + fill: ${({theme}) => theme.color.textSecondary}; + stroke-width: 2; + stroke: ${({theme}) => theme.color.white}; `; -export const RectOverlay = styled.rect<{$isMatched: boolean; $isSelected: boolean}>` - cursor: grab; - fill: ${({$isSelected, theme}) => ($isSelected ? theme.color.backgroundInteractive : 'transparent')}; - stroke: ${({$isMatched, theme}) => $isMatched && theme.color.text}; - stroke: ${({$isSelected, theme}) => $isSelected && theme.color.interactive}; - width: 100%; - - :hover { - fill: ${({theme}) => theme.color.backgroundInteractive}; - } +export const LineBase = styled.line` + stroke: ${({theme}) => theme.color.borderLight}; `; -export const TextBadge = styled.text` - fill: ${({theme}) => theme.color.text}; - font-size: 8px; - pointer-events: none; - text-transform: uppercase; +export const RectBase = styled.rect<{$isActive?: boolean}>` + fill: ${({theme, $isActive}) => ($isActive ? theme.color.primary : theme.color.white)}; + stroke: ${({theme}) => theme.color.textSecondary}; `; -export const TextDescription = styled.text` - fill: ${({theme}) => theme.color.text}; - font-size: ${({theme}) => theme.size.xs}; - pointer-events: none; +export const RectBaseTransparent = styled(RectBase)` + cursor: pointer; + fill: transparent; `; -export const TextName = styled.text` - fill: ${({theme}) => theme.color.text}; - font-size: ${({theme}) => theme.size.sm}; - font-weight: 600; - pointer-events: none; +export const HeaderRow = styled.div` + background-color: ${({theme}) => theme.color.white}; + display: grid; + grid-template-columns: 300px 1fr; + grid-template-rows: 32px; + padding: 0px 16px; `; -export const TextNumber = styled.text` - fill: ${({theme}) => theme.color.textLight}; - font-size: ${({theme}) => theme.size.sm}; - pointer-events: none; +export const HeaderContent = styled.div` + align-items: center; + display: flex; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; -export const TextOutput = styled.text` - fill: ${({theme}) => theme.color.white}; - font-size: ${({theme}) => theme.size.xs}; - font-weight: bold; - pointer-events: none; +export const HeaderTitle = styled(Typography.Title)` + && { + margin: 0; + } `; diff --git a/web/src/components/Visualization/components/Timeline/Timeline.tsx b/web/src/components/Visualization/components/Timeline/Timeline.tsx index da80cd16cf..b3c72c9078 100644 --- a/web/src/components/Visualization/components/Timeline/Timeline.tsx +++ b/web/src/components/Visualization/components/Timeline/Timeline.tsx @@ -1,31 +1,36 @@ -import {ParentSize} from '@visx/responsive'; import {NodeTypesEnum} from 'constants/Visualization.constants'; import Span from 'models/Span.model'; -import * as S from './Timeline.styled'; -import Visualization from './Visualization'; -import Navigation from '../Navigation'; +import {useRef} from 'react'; +import {FixedSizeList as List} from 'react-window'; +import NavigationWrapper from './NavigationWrapper'; +import TimelineProvider from './Timeline.provider'; +import ListWrapper from './ListWrapper'; export interface IProps { - isMatchedMode: boolean; - matchedSpans: string[]; nodeType: NodeTypesEnum; - onNavigateToSpan(spanId: string): void; - onNodeClick(spanId: string): void; - selectedSpan: string; spans: Span[]; - width?: number; + onNavigate(spanId: string): void; + onClick(spanId: string): void; + matchedSpans: string[]; + selectedSpan: string; } -const Timeline = (props: IProps) => { - const {isMatchedMode, matchedSpans, onNavigateToSpan, selectedSpan} = props; +const Timeline = ({nodeType, spans, onClick, onNavigate, matchedSpans, selectedSpan}: IProps) => { + const listRef = useRef(null); return ( - - - - {({width}) => } - - + + + + ); }; diff --git a/web/src/components/Visualization/components/Timeline/TimelineV2.styled.ts b/web/src/components/Visualization/components/Timeline/TimelineV2.styled.ts deleted file mode 100644 index 827d7b88a3..0000000000 --- a/web/src/components/Visualization/components/Timeline/TimelineV2.styled.ts +++ /dev/null @@ -1,150 +0,0 @@ -import {Typography} from 'antd'; -import {SemanticGroupNames, SemanticGroupNamesToColor} from 'constants/SemanticGroupNames.constants'; -import styled, {css} from 'styled-components'; - -export const Container = styled.div` - padding: 50px 24px 0 24px; -`; - -export const Row = styled.div<{$isEven: boolean; $isMatched: boolean; $isSelected: boolean}>` - background-color: ${({theme, $isEven}) => ($isEven ? theme.color.background : theme.color.white)}; - display: grid; - grid-template-columns: 300px 1fr; - grid-template-rows: 32px; - padding: 0px 16px; - - :hover { - background-color: ${({theme}) => theme.color.backgroundInteractive}; - } - - ${({$isMatched}) => - $isMatched && - css` - background-color: ${({theme}) => theme.color.alertYellow}; - `}; - - ${({$isSelected}) => - $isSelected && - css` - background: rgba(97, 23, 94, 0.1); - - :hover { - background: rgba(97, 23, 94, 0.1); - } - `}; -`; - -export const Col = styled.div` - display: grid; - grid-template-columns: 1fr 8px; -`; - -export const ColDuration = styled.div` - overflow: hidden; - position: relative; -`; - -export const Header = styled.div` - align-items: center; - display: flex; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; - -export const NameContainer = styled.div` - overflow: hidden; - text-overflow: ellipsis; -`; - -export const Separator = styled.div` - border-left: 1px solid rgb(222, 227, 236); - cursor: ew-resize; - height: 32px; - padding: 0px 3px; - width: 1px; -`; - -export const Title = styled(Typography.Text)` - color: ${({theme}) => theme.color.text}; - font-size: ${({theme}) => theme.size.sm}; - font-weight: 400; -`; - -export const Connector = styled.svg` - flex-shrink: 0; - overflow: hidden; - overflow-clip-margin: content-box; -`; - -export const SpanBar = styled.div<{$type: SemanticGroupNames}>` - background-color: ${({$type}) => SemanticGroupNamesToColor[$type]}; - border-radius: 3px; - height: 18px; - min-width: 2px; - position: absolute; - top: 7px; -`; - -export const SpanBarLabel = styled.div<{$side: 'left' | 'right'}>` - color: ${({theme}) => theme.color.textSecondary}; - font-size: ${({theme}) => theme.size.xs}; - padding: 1px 4px 0 4px; - position: absolute; - - ${({$side}) => - $side === 'left' - ? css` - right: 100%; - ` - : css` - left: 100%; - `}; -`; - -export const TextConnector = styled.text<{$isActive?: boolean}>` - fill: ${({theme, $isActive}) => ($isActive ? theme.color.white : theme.color.text)}; - font-size: ${({theme}) => theme.size.xs}; -`; - -export const CircleDot = styled.circle` - fill: ${({theme}) => theme.color.textSecondary}; - stroke-width: 2; - stroke: ${({theme}) => theme.color.white}; -`; - -export const LineBase = styled.line` - stroke: ${({theme}) => theme.color.borderLight}; -`; - -export const RectBase = styled.rect<{$isActive?: boolean}>` - fill: ${({theme, $isActive}) => ($isActive ? theme.color.primary : theme.color.white)}; - stroke: ${({theme}) => theme.color.textSecondary}; -`; - -export const RectBaseTransparent = styled(RectBase)` - cursor: pointer; - fill: transparent; -`; - -export const HeaderRow = styled.div` - background-color: ${({theme}) => theme.color.white}; - display: grid; - grid-template-columns: 300px 1fr; - grid-template-rows: 32px; - padding: 0px 16px; -`; - -export const HeaderContent = styled.div` - align-items: center; - display: flex; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; - -export const HeaderTitle = styled(Typography.Title)` - && { - margin: 0; - } -`; diff --git a/web/src/components/Visualization/components/Timeline/TimelineV2.tsx b/web/src/components/Visualization/components/Timeline/TimelineV2.tsx deleted file mode 100644 index b3c72c9078..0000000000 --- a/web/src/components/Visualization/components/Timeline/TimelineV2.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import {NodeTypesEnum} from 'constants/Visualization.constants'; -import Span from 'models/Span.model'; -import {useRef} from 'react'; -import {FixedSizeList as List} from 'react-window'; -import NavigationWrapper from './NavigationWrapper'; -import TimelineProvider from './Timeline.provider'; -import ListWrapper from './ListWrapper'; - -export interface IProps { - nodeType: NodeTypesEnum; - spans: Span[]; - onNavigate(spanId: string): void; - onClick(spanId: string): void; - matchedSpans: string[]; - selectedSpan: string; -} - -const Timeline = ({nodeType, spans, onClick, onNavigate, matchedSpans, selectedSpan}: IProps) => { - const listRef = useRef(null); - - return ( - - - - - ); -}; - -export default Timeline; diff --git a/web/src/components/Visualization/components/Timeline/TraceSpanNode/Header.tsx b/web/src/components/Visualization/components/Timeline/TraceSpanNode/Header.tsx deleted file mode 100644 index 39ab0384d5..0000000000 --- a/web/src/components/Visualization/components/Timeline/TraceSpanNode/Header.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import errorIcon from 'assets/error.svg'; -import * as S from '../Timeline.styled'; - -interface IProps { - hasAnalyzerErrors: boolean; -} - -const Header = ({hasAnalyzerErrors}: IProps) => { - if (!hasAnalyzerErrors) return null; - - return ( - <> - - - Analyzer errors - - - ); -}; - -export default Header; diff --git a/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNode.tsx b/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNode.tsx index 2ac57bf17d..e8bde17f1f 100644 --- a/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNode.tsx +++ b/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNode.tsx @@ -1,20 +1,12 @@ import useSpanData from 'hooks/useSpanData'; -import Header from './Header'; import BaseSpanNode from '../BaseSpanNode/BaseSpanNode'; import {IPropsComponent} from '../SpanNodeFactory'; const TraceSpanNode = (props: IPropsComponent) => { - const {isMatched, node} = props; - const {span, analyzerErrors} = useSpanData(node.data.id); + const {node} = props; + const {span} = useSpanData(node.data.id); - return ( - } - span={span} - /> - ); + return ; }; export default TraceSpanNode; diff --git a/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNodeV2.tsx b/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNodeV2.tsx deleted file mode 100644 index 539eda5ae2..0000000000 --- a/web/src/components/Visualization/components/Timeline/TraceSpanNode/TraceSpanNodeV2.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import useSpanData from 'hooks/useSpanData'; -// import Header from './Header'; -import BaseSpanNode from '../BaseSpanNode/BaseSpanNodeV2'; -import {IPropsComponent} from '../SpanNodeFactoryV2'; - -const TraceSpanNode = (props: IPropsComponent) => { - const {node} = props; - const {span} = useSpanData(node.data.id); - - return ( - } - span={span} - /> - ); -}; - -export default TraceSpanNode; diff --git a/web/src/components/Visualization/components/Timeline/Visualization.tsx b/web/src/components/Visualization/components/Timeline/Visualization.tsx deleted file mode 100644 index ef54e513ba..0000000000 --- a/web/src/components/Visualization/components/Timeline/Visualization.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {Axis, Orientation} from '@visx/axis'; -import {Group} from '@visx/group'; -import {scaleLinear} from '@visx/scale'; -import without from 'lodash/without'; -import {useCallback, useMemo, useState} from 'react'; -import {useTheme} from 'styled-components'; - -import {AxisHeight, AxisOffset, NodeHeight} from 'constants/Timeline.constants'; -import TimelineModel from 'models/Timeline.model'; -import TimelineService from 'services/Timeline.service'; -import SpanNodeFactory from './SpanNodeFactory'; -import {IProps} from './Timeline'; - -function tickLabelProps() { - return { - fill: '#687492', - fontSize: 12, - textAnchor: 'middle', - } as const; -} - -const Visualization = ({matchedSpans, nodeType, onNodeClick, selectedSpan, spans, width = 600}: IProps) => { - const theme = useTheme(); - const [collapsed, setCollapsed] = useState([]); - - const nodes = useMemo(() => TimelineModel(spans, nodeType), [spans, nodeType]); - const filteredNodes = useMemo(() => TimelineService.getFilteredNodes(nodes, collapsed), [collapsed, nodes]); - const [min, max] = useMemo(() => TimelineService.getMinMax(nodes), [nodes]); - - const xScale = scaleLinear({ - domain: [0, max - min], - range: [0, width - AxisOffset], - }); - - const handleOnCollapse = useCallback((id: string) => { - setCollapsed(prevCollapsed => { - if (prevCollapsed.includes(id)) { - return without(prevCollapsed, id); - } - return [...prevCollapsed, id]; - }); - }, []); - - return width < AxisOffset ? null : ( - - - - - - - {filteredNodes.map((node, index) => ( - filteredNode.data.id === node.data.parentId)} - isCollapsed={collapsed.includes(node.data.id)} - isMatched={matchedSpans.includes(node.data.id)} - isSelected={selectedSpan === node.data.id} - key={node.data.id} - minStartTime={min} - node={node} - onClick={onNodeClick} - onCollapse={handleOnCollapse} - xScale={xScale} - type={node.type} - /> - ))} - - - ); -}; - -export default Visualization;