Skip to content

Commit

Permalink
🏭 376 adding component factories for span detail, diagrams and trace …
Browse files Browse the repository at this point in the history
…node (#393)
  • Loading branch information
xoscar authored May 3, 2022
1 parent ad42add commit f52fe54
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 94 deletions.
26 changes: 26 additions & 0 deletions web/src/components/Diagram/Diagram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ISpan } from '../../types/Span.types';
import { ITrace } from '../../types/Trace.types';
import DAGComponent from './components/DAG';

export enum SupportedDiagrams {
DAG = 'dag',
}

export interface IDiagramProps {
type: SupportedDiagrams;
trace: ITrace;
selectedSpan?: ISpan;
onSelectSpan?(spanId: string): void;
}

const ComponentMap: Record<string, typeof DAGComponent> = {
[SupportedDiagrams.DAG]: DAGComponent,
};

const Diagram: React.FC<IDiagramProps> = ({type, ...props}) => {
const Component = ComponentMap[type || ''] || DAGComponent;

return <Component type={type} {...props} />;
};

export default Diagram;
5 changes: 5 additions & 0 deletions web/src/components/Diagram/components/DAG.styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styled from 'styled-components';

export const Container = styled.div`
position: relative;
`;
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
import {useCallback, useEffect, useMemo} from 'react';
import ReactFlow, {Background, BackgroundVariant, FlowElement} from 'react-flow-renderer';
import {useDAGChart} from 'hooks/useDAGChart';
import TraceNode from './TraceNode';
import {TSpanInfo, TSpanMap} from '../Trace/Trace';
import * as S from './TraceDiagram.styled';
import TraceDiagramAnalyticsService from '../../services/Analytics/TraceDiagramAnalytics.service';
import {ITrace} from '../../types/Trace.types';
import TraceNode from '../../TraceNode';
import * as S from './DAG.styled';
import TraceDiagramAnalyticsService from '../../../services/Analytics/TraceDiagramAnalytics.service';
import {IDiagramProps} from '../Diagram';
import { ISpan } from '../../../types/Span.types';

export type TSpanInfo = {
id: string;
parentIds: string[];
data: ISpan;
};

export type TSpanMap = Record<string, TSpanInfo>;

const {onClickSpan} = TraceDiagramAnalyticsService;

interface IPropsTraceDiagram {
spanMap: TSpanMap;
selectedSpan?: TSpanInfo;
trace: ITrace;
onSelectSpan(spanId: string): void;
}
const Diagram: React.FC<IDiagramProps> = ({trace, selectedSpan, onSelectSpan}): JSX.Element => {
const spanMap = useMemo<TSpanMap>(() => {
return (
trace?.spans?.reduce<TSpanMap>((acc, span) => {
acc[span.spanId] = acc[span.spanId] || {id: span.spanId, parentIds: [], data: span};
if (span.parentSpanId) acc[span.spanId].parentIds.push(span.parentSpanId);

return acc;
}, {}) || {}
);
}, [trace?.spans]);

const TraceDiagram = ({spanMap, trace, selectedSpan, onSelectSpan}: IPropsTraceDiagram): JSX.Element => {
const dagLayout = useDAGChart(spanMap);

const handleElementClick = useCallback(
(event, {id}: FlowElement) => {
onClickSpan(id);
onSelectSpan(id);
if (onSelectSpan) onSelectSpan(id);
},
[onSelectSpan]
);
Expand All @@ -32,9 +44,7 @@ const TraceDiagram = ({spanMap, trace, selectedSpan, onSelectSpan}: IPropsTraceD
const [dragNode] = dagLayout.dag.descendants();
const span = spanMap[dragNode?.data.id];

if (!selectedSpan && span) {
onSelectSpan(span.id);
}
if (!selectedSpan && span && onSelectSpan) onSelectSpan(span.id);
}
}, [dagLayout, onSelectSpan, selectedSpan, spanMap]);

Expand All @@ -46,11 +56,11 @@ const TraceDiagram = ({spanMap, trace, selectedSpan, onSelectSpan}: IPropsTraceD
return {
id: data.id,
type: 'TraceNode',
data: {span, trace},
data: span,
position: {x, y: parseFloat(String(y))},
selected: data.id === selectedSpan?.id,
selected: data.id === selectedSpan?.spanId,
sourcePosition: 'top',
className: `${data.id === selectedSpan?.id ? 'selected' : ''}`,
className: `${data.id === selectedSpan?.spanId ? 'selected' : ''}`,
};
});

Expand All @@ -70,7 +80,7 @@ const TraceDiagram = ({spanMap, trace, selectedSpan, onSelectSpan}: IPropsTraceD
}

return [];
}, [dagLayout, spanMap, trace, selectedSpan?.id]);
}, [dagLayout, spanMap, selectedSpan?.spanId]);

return (
<S.Container style={{height: Math.max(dagLayout?.layout?.height || 0, 900) + 100}}>
Expand All @@ -86,4 +96,4 @@ const TraceDiagram = ({spanMap, trace, selectedSpan, onSelectSpan}: IPropsTraceD
);
};

export default TraceDiagram;
export default Diagram;
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './TraceDiagram';
export {default} from './Diagram';
21 changes: 21 additions & 0 deletions web/src/components/SpanDetail/SpanDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {SemanticGroupNames} from '../../constants/SemanticGroupNames.constants';
import {ISpan} from '../../types/Span.types';
import GenericSpanDetail from './components/GenericSpanDetail';

export interface ISpanDetailProps {
testId?: string;
span?: ISpan;
resultId?: string;
}

const ComponentMap: Record<string, typeof GenericSpanDetail> = {
[SemanticGroupNames.Http]: GenericSpanDetail,
};

const SpanDetail: React.FC<ISpanDetailProps> = ({span, ...props}) => {
const Component = ComponentMap[span?.type || ''] || GenericSpanDetail;

return <Component span={span} {...props} />;
};

export default SpanDetail;
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@ import {FC, useState} from 'react';
import AssertionsResultTable from 'components/AssertionsTable/AssertionsTable';
import CreateAssertionModal from 'components/CreateAssertionModal';
import SkeletonTable from 'components/SkeletonTable';
import * as S from './SpanDetail.styled';
import Attributes from './Attributes';
import TraceAnalyticsService from '../../services/Analytics/TraceAnalytics.service';
import {useAppSelector} from '../../redux/hooks';
import AssertionSelectors from '../../selectors/Assertion.selectors';
import {ISpan} from '../../types/Span.types';
import * as S from '../SpanDetail.styled';
import Attributes from '../../Trace/Attributes';
import TraceAnalyticsService from '../../../services/Analytics/TraceAnalytics.service';
import {useAppSelector} from '../../../redux/hooks';
import AssertionSelectors from '../../../selectors/Assertion.selectors';
import {ISpanDetailProps} from '../SpanDetail';

const {onAddAssertionButtonClick} = TraceAnalyticsService;

type TSpanDetailProps = {
testId?: string;
targetSpan?: ISpan;
resultId?: string;
};

const SpanDetail: FC<TSpanDetailProps> = ({testId, targetSpan, resultId}) => {
const GenericSpanDetail: FC<ISpanDetailProps> = ({testId, span, resultId}) => {
const [openCreateAssertion, setOpenCreateAssertion] = useState(false);

const assertionsResultList = useAppSelector(
AssertionSelectors.selectAssertionResultListBySpan(testId, resultId, targetSpan?.spanId)
AssertionSelectors.selectAssertionResultListBySpan(testId, resultId, span?.spanId)
);

return (
Expand All @@ -42,7 +36,7 @@ const SpanDetail: FC<TSpanDetailProps> = ({testId, targetSpan, resultId}) => {
Add Assertion
</Button>
</S.DetailsHeader>
<SkeletonTable loading={!targetSpan}>
<SkeletonTable loading={!span}>
{assertionsResultList.length ? (
assertionsResultList
.sort((a, b) => (a.assertion.assertionId > b.assertion.assertionId ? -1 : 1))
Expand All @@ -52,7 +46,7 @@ const SpanDetail: FC<TSpanDetailProps> = ({testId, targetSpan, resultId}) => {
assertionResults={assertionResultList}
sort={index + 1}
assertion={assertion}
span={targetSpan!}
span={span!}
resultId={resultId!}
testId={testId!}
/>
Expand All @@ -65,13 +59,13 @@ const SpanDetail: FC<TSpanDetailProps> = ({testId, targetSpan, resultId}) => {
)}
</SkeletonTable>
</S.DetailsContainer>
<Attributes spanAttributeList={targetSpan?.attributeList} />
<Attributes spanAttributeList={span?.attributeList} />

{targetSpan?.spanId && testId && (
{span && testId && (
<CreateAssertionModal
key={`KEY_${targetSpan?.spanId}`}
key={`KEY_${span?.spanId}`}
testId={testId}
span={targetSpan!}
span={span}
resultId={resultId!}
open={openCreateAssertion}
onClose={() => setOpenCreateAssertion(false)}
Expand All @@ -81,4 +75,4 @@ const SpanDetail: FC<TSpanDetailProps> = ({testId, targetSpan, resultId}) => {
);
};

export default SpanDetail;
export default GenericSpanDetail;
2 changes: 2 additions & 0 deletions web/src/components/SpanDetail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './SpanDetail';
41 changes: 12 additions & 29 deletions web/src/components/Trace/Trace.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useEffect, useMemo, useState} from 'react';
import {useCallback, useEffect, useState} from 'react';
import styled from 'styled-components';

import {useStoreActions} from 'react-flow-renderer';
Expand All @@ -11,14 +11,14 @@ import {CloseCircleFilled} from '@ant-design/icons';
import 'react-reflex/styles.css';

import {useGetTestByIdQuery, useGetResultByIdQuery, useRunTestMutation} from 'redux/apis/Test.api';
import TraceDiagram from 'components/TraceDiagram/TraceDiagram';
import Diagram from 'components/Diagram';

import GuidedTourService, {GuidedTours} from 'services/GuidedTour.service';
import {Steps} from 'components/GuidedTour/traceStepList';
import useGuidedTour from 'hooks/useGuidedTour';
import * as S from './Trace.styled';

import SpanDetail from './SpanDetail';
import SpanDetail from '../SpanDetail';
import TestResults from './TestResults';
import {ISpan} from '../../types/Span.types';
import {ITestRunResult} from '../../types/TestRunResult.types';
Expand All @@ -28,6 +28,7 @@ import TraceAnalyticsService from '../../services/Analytics/TraceAnalytics.servi
import usePolling from '../../hooks/usePolling';
import {useAppDispatch} from '../../redux/hooks';
import {replace, updateTestResult} from '../../redux/slices/ResultList.slice';
import { SupportedDiagrams } from '../Diagram/Diagram';

const {onChangeTab} = TraceAnalyticsService;

Expand All @@ -37,14 +38,6 @@ const Grid = styled.div`
overflow: scroll;
`;

export type TSpanInfo = {
id: string;
parentIds: string[];
data: ISpan;
};

export type TSpanMap = Record<string, TSpanInfo>;

type TraceProps = {
testId: string;
testResultId: string;
Expand All @@ -53,7 +46,7 @@ type TraceProps = {
};

const Trace: React.FC<TraceProps> = ({testId, testResultId, onDismissTrace, onRunTest}) => {
const [selectedSpan, setSelectedSpan] = useState<TSpanInfo | undefined>();
const [selectedSpan, setSelectedSpan] = useState<ISpan | undefined>();
const [isFirstLoad, setIsFirstLoad] = useState(true);
const {data: test} = useGetTestByIdQuery(testId);
const [runNewTest] = useRunTestMutation();
Expand All @@ -65,25 +58,15 @@ const Trace: React.FC<TraceProps> = ({testId, testResultId, onDismissTrace, onRu
refetch: refetchTrace,
} = useGetResultByIdQuery({testId, resultId: testResultId});

const spanMap = useMemo<TSpanMap>(() => {
return (
testResultDetails?.trace?.spans?.reduce<TSpanMap>((acc, span) => {
acc[span.spanId] = acc[span.spanId] || {id: span.spanId, parentIds: [], data: span};
if (span.parentSpanId) acc[span.spanId].parentIds.push(span.parentSpanId);

return acc;
}, {}) || {}
);
}, [testResultDetails]);

const addSelected = useStoreActions(actions => actions.addSelectedElements);

const handleOnSpanSelected = useCallback(
(spanId: string) => {
addSelected([{id: spanId}]);
setSelectedSpan(spanMap[spanId]);
const span = testResultDetails?.trace?.spans.find(({spanId: id}) => id === spanId);
setSelectedSpan(span);
},
[addSelected, spanMap]
[addSelected, testResultDetails?.trace?.spans]
);

useGuidedTour(GuidedTours.Trace);
Expand Down Expand Up @@ -162,11 +145,11 @@ const Trace: React.FC<TraceProps> = ({testId, testResultId, onDismissTrace, onRu
data-tour={GuidedTourService.getStep(GuidedTours.Trace, Steps.Diagram)}
>
<div className="pane-content">
<TraceDiagram
spanMap={spanMap}
<Diagram
type={SupportedDiagrams.DAG}
trace={testResultDetails?.trace!}
onSelectSpan={handleOnSpanSelected}
selectedSpan={selectedSpan}
trace={testResultDetails?.trace!}
/>
</div>
</ReflexElement>
Expand All @@ -184,7 +167,7 @@ const Trace: React.FC<TraceProps> = ({testId, testResultId, onDismissTrace, onRu
<SpanDetail
resultId={testResultDetails?.resultId}
testId={test?.testId}
targetSpan={selectedSpan?.data}
span={selectedSpan}
/>
</Tabs.TabPane>
<Tabs.TabPane
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,3 @@ export const TextContainer = styled.div`
justify-content: center;
align-items: center;
`;

export const Container = styled.div`
position: relative;
`;
18 changes: 18 additions & 0 deletions web/src/components/TraceNode/TraceNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {NodeProps} from 'react-flow-renderer';
import {SemanticGroupNames} from '../../constants/SemanticGroupNames.constants';
import {ISpan} from '../../types/Span.types';
import GenericTraceNode from './components/GenericTraceNode';

export type TTraceNodeProps = NodeProps<ISpan>;

const ComponentMap: Record<string, typeof GenericTraceNode> = {
[SemanticGroupNames.Http]: GenericTraceNode,
};

const TraceNode: React.FC<TTraceNodeProps> = ({data: span, ...props}) => {
const Component = ComponentMap[span?.type || ''] || GenericTraceNode;

return <Component data={span} {...props} />;
};

export default TraceNode;
Loading

0 comments on commit f52fe54

Please sign in to comment.