Skip to content

Commit

Permalink
Chore performance improvements tech debt 1 (#3607)
Browse files Browse the repository at this point in the history
* adding async functionality

* chore(frontend): DAG improvements

* chore(frontend): DAG improvements

* chore(frontend): fixing spinner styles

* chore(frontend): fixing spinner styles

* feat(frontend): fixing tests

* chore(frontend): fixing spinner styles
  • Loading branch information
xoscar authored Feb 8, 2024
1 parent d713747 commit 9a34b5e
Show file tree
Hide file tree
Showing 23 changed files with 247 additions and 127 deletions.
9 changes: 1 addition & 8 deletions web/src/components/AnalyzerResult/Rule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {CaretUpFilled} from '@ant-design/icons';
import {Space, Tooltip, Typography} from 'antd';
import {LinterResultPluginRule} from 'models/LinterResult.model';
import Trace from 'models/Trace.model';
import Span from 'models/Span.model';
import {LinterRuleErrorLevel} from 'models/Linter.model';
import {useAppDispatch} from 'redux/hooks';
import {selectSpan} from 'redux/slices/Trace.slice';
Expand All @@ -17,12 +16,6 @@ interface IProps {
trace: Trace;
}

function getSpanName(spans: Span[], spanId: string) {
// TODO: find an easier way to get the span name
const span = spans.find(s => s.id === spanId);
return span?.name ?? '';
}

const Rule = ({
rule: {id, tips, passed, description, name, errorDescription, results = [], level, weight = 0},
trace,
Expand Down Expand Up @@ -69,7 +62,7 @@ const Rule = ({
type="link"
$error={!result.passed}
>
{getSpanName(trace.spans, result.spanId)}
{trace.flat[result.spanId].name ?? ''}
</S.SpanButton>

{!result.passed && result.errors.length > 1 && (
Expand Down
8 changes: 8 additions & 0 deletions web/src/components/LoadingSpinner/LoadingSpinner.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import styled from 'styled-components';

export const SpinnerContainer = styled.div`
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
4 changes: 4 additions & 0 deletions web/src/components/LoadingSpinner/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
import {SpinnerContainer} from './LoadingSpinner.styled';

export {SpinnerContainer};

// eslint-disable-next-line no-restricted-exports
export {default} from './LoadingSpinner';
62 changes: 62 additions & 0 deletions web/src/components/RunDetailTest/TestDAG.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {useCallback, useEffect} from 'react';
import {Node, NodeChange} from 'react-flow-renderer';

import DAG from 'components/Visualization/components/DAG';
import {useSpan} from 'providers/Span/Span.provider';
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import {initNodes, onNodesChange as onNodesChangeAction} from 'redux/slices/DAG.slice';
import DAGSelectors from 'selectors/DAG.selectors';
import TraceDiagramAnalyticsService from 'services/Analytics/TraceDiagramAnalytics.service';
import Trace from 'models/Trace.model';
import {useTestSpecForm} from '../TestSpecForm/TestSpecForm.provider';
import LoadingSpinner, {SpinnerContainer} from '../LoadingSpinner';

export interface IProps {
trace: Trace;
onNavigateToSpan(spanId: string): void;
}

const TestDAG = ({trace: {spans}, onNavigateToSpan}: IProps) => {
const dispatch = useAppDispatch();
const edges = useAppSelector(DAGSelectors.selectEdges);
const nodes = useAppSelector(DAGSelectors.selectNodes);
const {onSelectSpan, matchedSpans, focusedSpan} = useSpan();
const {isOpen} = useTestSpecForm();

useEffect(() => {
dispatch(initNodes({spans}));
}, [dispatch, spans]);

const onNodesChange = useCallback((changes: NodeChange[]) => dispatch(onNodesChangeAction({changes})), [dispatch]);

const onNodeClick = useCallback(
(event, {id}: Node) => {
TraceDiagramAnalyticsService.onClickSpan(id);
onSelectSpan(id);
},
[onSelectSpan]
);

if (spans.length && !nodes.length) {
return (
<SpinnerContainer>
<LoadingSpinner />
</SpinnerContainer>
);
}

return (
<DAG
edges={edges}
isMatchedMode={matchedSpans.length > 0 || isOpen}
matchedSpans={matchedSpans}
nodes={nodes}
onNavigateToSpan={onNavigateToSpan}
onNodesChange={onNodesChange}
onNodeClick={onNodeClick}
selectedSpan={focusedSpan}
/>
);
};

export default TestDAG;
2 changes: 1 addition & 1 deletion web/src/components/RunDetailTest/TestPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const TestPanel = ({run, testId, runEvents}: IProps) => {
isDAGDisabled={isDAGDisabled}
runEvents={runEvents}
runState={run.state}
spans={run?.trace?.spans ?? []}
trace={run.trace}
type={visualizationType}
/>
</S.SectionLeft>
Expand Down
46 changes: 7 additions & 39 deletions web/src/components/RunDetailTest/Visualization.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,38 @@
import {useCallback, useEffect} from 'react';
import {Node, NodeChange} from 'react-flow-renderer';

import {VisualizationType} from 'components/RunDetailTrace/RunDetailTrace';
import RunEvents from 'components/RunEvents';
import {useTestSpecForm} from 'components/TestSpecForm/TestSpecForm.provider';
import DAG from 'components/Visualization/components/DAG';
import Timeline from 'components/Visualization/components/Timeline';
import {TestRunStage} from 'constants/TestRunEvents.constants';
import {NodeTypesEnum} from 'constants/Visualization.constants';
import Span from 'models/Span.model';
import TestRunEvent from 'models/TestRunEvent.model';
import {useSpan} from 'providers/Span/Span.provider';
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import {initNodes, onNodesChange as onNodesChangeAction} from 'redux/slices/DAG.slice';
import DAGSelectors from 'selectors/DAG.selectors';
import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import TraceDiagramAnalyticsService from 'services/Analytics/TraceDiagramAnalytics.service';
import Trace from 'models/Trace.model';
import TestRunService from 'services/TestRun.service';
import {TTestRunState} from 'types/TestRun.types';
import TestDAG from './TestDAG';

export interface IProps {
isDAGDisabled: boolean;
runEvents: TestRunEvent[];
runState: TTestRunState;
spans: Span[];
type: VisualizationType;
trace: Trace;
}

const Visualization = ({isDAGDisabled, runEvents, runState, spans, type}: IProps) => {
const dispatch = useAppDispatch();
const edges = useAppSelector(DAGSelectors.selectEdges);
const nodes = useAppSelector(DAGSelectors.selectNodes);
const {onSelectSpan, matchedSpans, onSetFocusedSpan, focusedSpan, selectedSpan} = useSpan();
const Visualization = ({isDAGDisabled, runEvents, runState, trace, trace: {spans}, type}: IProps) => {
const {onSelectSpan, matchedSpans, onSetFocusedSpan, selectedSpan} = useSpan();

const {isOpen} = useTestSpecForm();

useEffect(() => {
if (isDAGDisabled) return;
dispatch(initNodes({spans}));
}, [dispatch, isDAGDisabled, spans]);

useEffect(() => {
if (selectedSpan) return;
const firstSpan = spans.find(span => !span.parentId);
onSelectSpan(firstSpan?.id ?? '');
}, [onSelectSpan, selectedSpan, spans]);

const onNodesChange = useCallback((changes: NodeChange[]) => dispatch(onNodesChangeAction({changes})), [dispatch]);

const onNodeClick = useCallback(
(event, {id}: Node) => {
TraceDiagramAnalyticsService.onClickSpan(id);
onSelectSpan(id);
},
[onSelectSpan]
);

const onNodeClickTimeline = useCallback(
(spanId: string) => {
TraceAnalyticsService.onTimelineSpanClick(spanId);
Expand All @@ -76,17 +53,8 @@ const Visualization = ({isDAGDisabled, runEvents, runState, spans, type}: IProps
return <RunEvents events={runEvents} stage={TestRunStage.Trace} state={runState} />;
}

return type === VisualizationType.Dag ? (
<DAG
edges={edges}
isMatchedMode={matchedSpans.length > 0 || isOpen}
matchedSpans={matchedSpans}
nodes={nodes}
onNavigateToSpan={onNavigateToSpan}
onNodesChange={onNodesChange}
onNodeClick={onNodeClick}
selectedSpan={focusedSpan}
/>
return type === VisualizationType.Dag && !isDAGDisabled ? (
<TestDAG trace={trace} onNavigateToSpan={onNavigateToSpan} />
) : (
<Timeline
isMatchedMode={matchedSpans.length > 0 || isOpen}
Expand Down
62 changes: 62 additions & 0 deletions web/src/components/RunDetailTrace/TraceDAG.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import TraceSelectors from 'selectors/Trace.selectors';
import {Node, NodeChange} from 'react-flow-renderer';
import {changeNodes, initNodes, selectSpan} from 'redux/slices/Trace.slice';
import TraceDiagramAnalyticsService from 'services/Analytics/TraceDiagramAnalytics.service';
import {useCallback, useEffect} from 'react';
import Trace from 'models/Trace.model';
import DAG from '../Visualization/components/DAG';
import LoadingSpinner, {SpinnerContainer} from '../LoadingSpinner';

interface IProps {
trace: Trace;
onNavigateToSpan(spanId: string): void;
}

const TraceDAG = ({trace: {spans}, onNavigateToSpan}: IProps) => {
const matchedSpans = useAppSelector(TraceSelectors.selectMatchedSpans);
const selectedSpan = useAppSelector(TraceSelectors.selectSelectedSpan);
const nodes = useAppSelector(TraceSelectors.selectNodes);
const edges = useAppSelector(TraceSelectors.selectEdges);
const isMatchedMode = Boolean(matchedSpans.length);
const dispatch = useAppDispatch();

// TODO: Trace will never change, we can calculate this once and then keep using it
useEffect(() => {
dispatch(initNodes({spans}));
}, [dispatch, spans]);

const onNodesChange = useCallback((changes: NodeChange[]) => dispatch(changeNodes({changes})), [dispatch]);

const onNodeClick = useCallback(
(event: React.MouseEvent, {id}: Node) => {
event.stopPropagation();
TraceDiagramAnalyticsService.onClickSpan(id);
dispatch(selectSpan({spanId: id}));
},
[dispatch]
);

if (spans.length && !nodes.length) {
return (
<SpinnerContainer>
<LoadingSpinner />
</SpinnerContainer>
);
}

return (
<DAG
edges={edges}
isMatchedMode={isMatchedMode}
matchedSpans={matchedSpans}
nodes={nodes}
onNavigateToSpan={onNavigateToSpan}
onNodesChange={onNodesChange}
onNodeClick={onNodeClick}
selectedSpan={selectedSpan}
/>
);
};

export default TraceDAG;
2 changes: 1 addition & 1 deletion web/src/components/RunDetailTrace/TracePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const TracePanel = ({run, testId, runEvents, skipTraceCollection}: TProps) => {
isDAGDisabled={isDAGDisabled}
runEvents={runEvents}
runState={run.state}
spans={run?.trace?.spans ?? []}
trace={run.trace}
type={visualizationType}
/>
</S.VisualizationContainer>
Expand Down
50 changes: 9 additions & 41 deletions web/src/components/RunDetailTrace/Visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,36 @@ import {TestRunStage} from 'constants/TestRunEvents.constants';
import {NodeTypesEnum} from 'constants/Visualization.constants';
import TestRunEvent from 'models/TestRunEvent.model';
import {useCallback, useEffect} from 'react';
import {Node, NodeChange} from 'react-flow-renderer';
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import {changeNodes, initNodes, selectSpan} from 'redux/slices/Trace.slice';
import {selectSpan} from 'redux/slices/Trace.slice';
import TraceSelectors from 'selectors/Trace.selectors';
import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import TraceDiagramAnalyticsService from 'services/Analytics/TraceDiagramAnalytics.service';
import TestRunService from 'services/TestRun.service';
import Trace from 'models/Trace.model';
import {TTestRunState} from 'types/TestRun.types';
import Span from 'models/Span.model';
import DAG from '../Visualization/components/DAG';
import Timeline from '../Visualization/components/Timeline';
import {VisualizationType} from './RunDetailTrace';
import TraceDAG from './TraceDAG';

interface IProps {
isDAGDisabled: boolean;
runEvents: TestRunEvent[];
runState: TTestRunState;
spans: Span[];
trace: Trace;
type: VisualizationType;
}

const Visualization = ({isDAGDisabled, runEvents, runState, spans, type}: IProps) => {
const Visualization = ({isDAGDisabled, runEvents, runState, trace, trace: {spans, rootSpan}, type}: IProps) => {
const dispatch = useAppDispatch();
const edges = useAppSelector(TraceSelectors.selectEdges);
const matchedSpans = useAppSelector(TraceSelectors.selectMatchedSpans);
const nodes = useAppSelector(TraceSelectors.selectNodes);
const selectedSpan = useAppSelector(TraceSelectors.selectSelectedSpan);
const isMatchedMode = Boolean(matchedSpans.length);

// TODO: Trace will never change, we can calculate this once and then keep using it
useEffect(() => {
if (isDAGDisabled) return;
dispatch(initNodes({spans}));
}, [dispatch, isDAGDisabled, spans]);

useEffect(() => {
if (selectedSpan) return;

// TODO: Find an easier way to get the first span
const firstSpan = spans.find(span => !span.parentId);
dispatch(selectSpan({spanId: firstSpan?.id ?? ''}));
}, [dispatch, selectedSpan, spans]);

const onNodesChange = useCallback((changes: NodeChange[]) => dispatch(changeNodes({changes})), [dispatch]);

const onNodeClick = useCallback(
(event: React.MouseEvent, {id}: Node) => {
event.stopPropagation();
TraceDiagramAnalyticsService.onClickSpan(id);
dispatch(selectSpan({spanId: id}));
},
[dispatch]
);
dispatch(selectSpan({spanId: rootSpan.id ?? ''}));
}, [dispatch, rootSpan.id, selectedSpan, spans]);

const onNodeClickTimeline = useCallback(
(spanId: string) => {
Expand All @@ -76,17 +53,8 @@ const Visualization = ({isDAGDisabled, runEvents, runState, spans, type}: IProps
return <RunEvents events={runEvents} stage={TestRunStage.Trace} state={runState} />;
}

return type === VisualizationType.Dag ? (
<DAG
edges={edges}
isMatchedMode={isMatchedMode}
matchedSpans={matchedSpans}
nodes={nodes}
onNavigateToSpan={onNavigateToSpan}
onNodesChange={onNodesChange}
onNodeClick={onNodeClick}
selectedSpan={selectedSpan}
/>
return type === VisualizationType.Dag && !isDAGDisabled ? (
<TraceDAG trace={trace} onNavigateToSpan={onNavigateToSpan} />
) : (
<Timeline
isMatchedMode={isMatchedMode}
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/Visualization/components/DAG/DAG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Actions from './Actions';
import * as S from './DAG.styled';
import TestSpanNode from './TestSpanNode/TestSpanNode';
import TraceSpanNode from './TraceSpanNode/TraceSpanNode';
import {MAX_DAG_NODES} from '../../../../constants/Visualization.constants';

/** Important to define the nodeTypes outside the component to prevent re-renderings */
const nodeTypes = {traceSpan: TraceSpanNode, testSpan: TestSpanNode};
Expand Down Expand Up @@ -46,15 +47,17 @@ const DAG = ({
edges={edges}
nodes={nodes}
deleteKeyCode={null}
fitView
minZoom={0.1}
multiSelectionKeyCode={null}
nodesConnectable={false}
nodeTypes={nodeTypes}
onInit={() => nodes.length >= MAX_DAG_NODES && onNavigateToSpan(nodes[0]?.id)}
onNodeClick={onNodeClick}
onNodeDragStop={onNodeClick}
onNodesChange={onNodesChange}
onlyRenderVisibleElements
selectionKeyCode={null}
fitView={nodes.length <= MAX_DAG_NODES}
>
{isMiniMapActive && <MiniMap />}
</ReactFlow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useSelectAsCurrent from '../../../hooks/useSelectAsCurrent';

interface IProps extends NodeProps<INodeDataSpan> {}

const TestSpanNode = ({data, id, selected}: IProps) => {
const TestSpanNode = ({data, id, selected, ...props}: IProps) => {
const {span, testSpecs, testOutputs} = useSpanData(id);
const {isLoading, onSelectAsCurrent, showSelectAsCurrent} = useSelectAsCurrent({
selected,
Expand Down
Loading

0 comments on commit 9a34b5e

Please sign in to comment.