Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(frontend): FE Improvements for the Test View #3613

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {useCallback} from 'react';
import {noop, uniqBy} from 'lodash';
import {noop} from 'lodash';
import {Completion, CompletionContext} from '@codemirror/autocomplete';
import {useAppStore} from 'redux/hooks';
import AssertionSelectors from 'selectors/Assertion.selectors';
import VariableSetSelectors from 'selectors/VariableSet.selectors';
import SpanSelectors from 'selectors/Span.selectors';
import {selectExpressionAttributeList} from 'selectors/Editor.selectors';
import EditorService from 'services/Editor.service';
import {SupportedEditors} from 'constants/Editor.constants';

Expand All @@ -18,14 +17,10 @@ interface IProps {
const useAutoComplete = ({testId, runId, onSelect = noop, autocompleteCustomValues}: IProps) => {
const {getState} = useAppStore();

const getAttributeList = useCallback(() => {
const state = getState();
const spanIdList = SpanSelectors.selectMatchedSpans(state);
// TODO: this list is calculated multiple times while typing, we should memoize it
const attributeList = AssertionSelectors.selectAttributeList(state, testId, runId, spanIdList);

return uniqBy(attributeList, 'key');
}, [getState, runId, testId]);
const getAttributeList = useCallback(
() => selectExpressionAttributeList(getState(), testId, runId),
[getState, runId, testId]
);

const getSelectedVariableSetEntryList = useCallback(() => {
const state = getState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
Tokens,
} from 'constants/Editor.constants';
import {useAppStore} from 'redux/hooks';
import AssertionSelectors from 'selectors/Assertion.selectors';
import {escapeString} from 'utils/Common';
import {selectSelectorAttributeList} from 'selectors/Editor.selectors';

interface IProps {
testId: string;
Expand All @@ -22,13 +22,10 @@ interface IProps {
const useAutoComplete = ({testId, runId}: IProps) => {
const {getState} = useAppStore();

const getAttributeList = useCallback(() => {
const state = getState();
// TODO: this list is calculated multiple times while typing, we should memoize it
const defaultList = AssertionSelectors.selectAllAttributeList(state, testId, runId);

return defaultList;
}, [getState, runId, testId]);
const getAttributeList = useCallback(
() => selectSelectorAttributeList(getState(), testId, runId),
[getState, runId, testId]
);

return useCallback(
async (context: CompletionContext) => {
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
51 changes: 9 additions & 42 deletions web/src/components/RunDetailTest/Visualization.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,36 @@
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, rootSpan}, 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]
);
onSelectSpan(rootSpan.id);
}, [onSelectSpan, rootSpan, selectedSpan, spans]);

const onNodeClickTimeline = useCallback(
(spanId: string) => {
Expand All @@ -76,17 +52,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
61 changes: 61 additions & 0 deletions web/src/components/RunDetailTrace/TraceDAG.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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();

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
Loading
Loading