Skip to content

Commit

Permalink
feat(frontend): add error handling for trace section (#2282)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgeepc committed Mar 31, 2023
1 parent baf4b14 commit a5d365c
Show file tree
Hide file tree
Showing 22 changed files with 262 additions and 236 deletions.
4 changes: 2 additions & 2 deletions web/src/components/RunDetailLayout/RunDetailLayout.tsx
Expand Up @@ -72,10 +72,10 @@ const RunDetailLayout = ({test: {id, name, trigger, version = 1}, test}: IProps)
<RunDetailTrigger test={test} run={run} runEvents={runEvents} isError={isError} />
</Tabs.TabPane>
<Tabs.TabPane tab={renderTab('Trace', id, run.id, mode)} key={RunDetailModes.TRACE}>
<RunDetailTrace run={run} testId={id} />
<RunDetailTrace run={run} runEvents={runEvents} testId={id} />
</Tabs.TabPane>
<Tabs.TabPane tab={renderTab('Test', id, run.id, mode)} key={RunDetailModes.TEST}>
<RunDetailTest run={run} testId={id} />
<RunDetailTest run={run} runEvents={runEvents} testId={id} />
</Tabs.TabPane>
</Tabs>
</S.Container>
Expand Down
15 changes: 11 additions & 4 deletions web/src/components/RunDetailTest/RunDetailTest.tsx
Expand Up @@ -13,15 +13,16 @@ import TestSpecDetail from 'components/TestSpecDetail';
import TestSpecForm from 'components/TestSpecForm';
import {useTestSpecForm} from 'components/TestSpecForm/TestSpecForm.provider';
import Switch from 'components/Visualization/components/Switch';
import {TAssertionResultEntry} from 'models/AssertionResults.model';
import TestRun from 'models/TestRun.model';
import TestRunEvent from 'models/TestRunEvent.model';
import {useGuidedTour} from 'providers/GuidedTour/GuidedTour.provider';
import {useSpan} from 'providers/Span/Span.provider';
import {useTestOutput} from 'providers/TestOutput/TestOutput.provider';
import {useTestSpecs} from 'providers/TestSpecs/TestSpecs.provider';
import AssertionAnalyticsService from 'services/Analytics/AssertionAnalytics.service';
import TestRunAnalytics from 'services/Analytics/TestRunAnalytics.service';
import AssertionService from 'services/Assertion.service';
import TestRun from 'models/TestRun.model';
import {TAssertionResultEntry} from 'models/AssertionResults.model';
import * as S from './RunDetailTest.styled';
import Visualization from './Visualization';

Expand All @@ -32,10 +33,11 @@ const TABS = {

interface IProps {
run: TestRun;
runEvents: TestRunEvent[];
testId: string;
}

const RunDetailTest = ({run, testId}: IProps) => {
const RunDetailTest = ({run, runEvents, testId}: IProps) => {
const [query, updateQuery] = useSearchParams();
const {selectedSpan, onSetFocusedSpan, onSelectSpan} = useSpan();
const {remove, revert, selectedTestSpec, setSelectedSpec, setSelectorSuggestions, setPrevSelector, specs} =
Expand Down Expand Up @@ -125,7 +127,12 @@ const RunDetailTest = ({run, testId}: IProps) => {
/>
</S.SwitchContainer>

<Visualization runState={run.state} spans={run?.trace?.spans ?? []} type={visualizationType} />
<Visualization
runEvents={runEvents}
runState={run.state}
spans={run?.trace?.spans ?? []}
type={visualizationType}
/>
</S.SectionLeft>

<S.SectionRight $shouldScroll={!selectedTestSpec}>
Expand Down
11 changes: 7 additions & 4 deletions web/src/components/RunDetailTest/Visualization.tsx
Expand Up @@ -2,27 +2,30 @@ import {useCallback, useEffect} from 'react';
import {Node, NodeChange} from 'react-flow-renderer';

import {VisualizationType} from 'components/RunDetailTrace/RunDetailTrace';
import SkeletonDiagram from 'components/SkeletonDiagram';
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 {TestState} from 'constants/TestRun.constants';
import {TestRunStage} from 'constants/TestRunEvents.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 {TTestRunState} from 'types/TestRun.types';
import Span from 'models/Span.model';

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

const Visualization = ({runState, spans, type}: IProps) => {
const Visualization = ({runEvents, runState, spans, type}: IProps) => {
const dispatch = useAppDispatch();
const edges = useAppSelector(DAGSelectors.selectEdges);
const nodes = useAppSelector(DAGSelectors.selectNodes);
Expand Down Expand Up @@ -67,7 +70,7 @@ const Visualization = ({runState, spans, type}: IProps) => {
);

if (runState !== TestState.FINISHED) {
return <SkeletonDiagram />;
return <RunEvents events={runEvents} stage={TestRunStage.Trace} state={runState} />;
}

return type === VisualizationType.Dag ? (
Expand Down
13 changes: 10 additions & 3 deletions web/src/components/RunDetailTrace/RunDetailTrace.tsx
Expand Up @@ -5,17 +5,19 @@ import Drawer from 'components/Drawer';
import SpanDetail from 'components/SpanDetail';
import Switch from 'components/Visualization/components/Switch';
import {TestState} from 'constants/TestRun.constants';
import TestRun from 'models/TestRun.model';
import TestRunEvent from 'models/TestRunEvent.model';
import SpanSelectors from 'selectors/Span.selectors';
import TraceSelectors from 'selectors/Trace.selectors';
import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import TestRun from 'models/TestRun.model';
import * as S from './RunDetailTrace.styled';
import Search from './Search';
import Visualization from './Visualization';
import SetupAlert from '../SetupAlert';

interface IProps {
run: TestRun;
runEvents: TestRunEvent[];
testId: string;
}

Expand All @@ -24,7 +26,7 @@ export enum VisualizationType {
Timeline,
}

const RunDetailTrace = ({run, testId}: IProps) => {
const RunDetailTrace = ({run, runEvents, testId}: IProps) => {
const selectedSpan = useAppSelector(TraceSelectors.selectSelectedSpan);
const searchText = useAppSelector(TraceSelectors.selectSearchText);
const span = useAppSelector(state => SpanSelectors.selectSpanById(state, selectedSpan, testId, run.id));
Expand Down Expand Up @@ -58,7 +60,12 @@ const RunDetailTrace = ({run, testId}: IProps) => {
/>
)}
</S.SwitchContainer>
<Visualization runState={run.state} spans={run?.trace?.spans ?? []} type={visualizationType} />
<Visualization
runEvents={runEvents}
runState={run.state}
spans={run?.trace?.spans ?? []}
type={visualizationType}
/>
</S.VisualizationContainer>
</S.Section>
}
Expand Down
9 changes: 6 additions & 3 deletions web/src/components/RunDetailTrace/Visualization.tsx
@@ -1,5 +1,7 @@
import SkeletonDiagram from 'components/SkeletonDiagram';
import RunEvents from 'components/RunEvents';
import {TestState} from 'constants/TestRun.constants';
import {TestRunStage} from 'constants/TestRunEvents.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';
Expand All @@ -15,12 +17,13 @@ import Timeline from '../Visualization/components/Timeline';
import {VisualizationType} from './RunDetailTrace';

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

const Visualization = ({runState, spans, type}: IProps) => {
const Visualization = ({runEvents, runState, spans, type}: IProps) => {
const dispatch = useAppDispatch();
const edges = useAppSelector(TraceSelectors.selectEdges);
const matchedSpans = useAppSelector(TraceSelectors.selectMatchedSpans);
Expand Down Expand Up @@ -67,7 +70,7 @@ const Visualization = ({runState, spans, type}: IProps) => {
);

if (runState !== TestState.FINISHED) {
return <SkeletonDiagram />;
return <RunEvents events={runEvents} stage={TestRunStage.Trace} state={runState} />;
}

return type === VisualizationType.Dag ? (
Expand Down
5 changes: 3 additions & 2 deletions web/src/components/RunDetailTrigger/RunDetailTrigger.tsx
Expand Up @@ -3,9 +3,10 @@ import RunDetailTriggerResponseFactory from 'components/RunDetailTriggerResponse
import RunEvents from 'components/RunEvents';
import {TriggerTypes} from 'constants/Test.constants';
import {TestState} from 'constants/TestRun.constants';
import {TestRunStage} from 'constants/TestRunEvents.constants';
import Test from 'models/Test.model';
import TestRun from 'models/TestRun.model';
import TestRunEvent, {TestRunStage} from 'models/TestRunEvent.model';
import TestRunEvent from 'models/TestRunEvent.model';
import * as S from './RunDetailTrigger.styled';

interface IProps {
Expand All @@ -25,7 +26,7 @@ const RunDetailTrigger = ({test, run: {state, triggerResult, triggerTime}, runEv
</S.SectionLeft>
<S.SectionRight>
{shouldDisplayError ? (
<RunEvents events={runEvents} stage={TestRunStage.Trigger} />
<RunEvents events={runEvents} stage={TestRunStage.Trigger} state={state} />
) : (
<RunDetailTriggerResponseFactory
state={state}
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/RunEvents/RunEvent.tsx
Expand Up @@ -2,11 +2,11 @@ import {Typography} from 'antd';
import TestRunEvent from 'models/TestRunEvent.model';
import * as S from './RunEvents.styled';

interface IProps {
export interface IPropsEvent {
event: TestRunEvent;
}

const RunEvent = ({event}: IProps) => (
const RunEvent = ({event}: IPropsEvent) => (
<S.EventContainer>
<S.Dot $logLevel={event.logLevel} />
<Typography.Title level={3}>{event.title}</Typography.Title>
Expand Down
24 changes: 24 additions & 0 deletions web/src/components/RunEvents/RunEventDataStore.tsx
@@ -0,0 +1,24 @@
import {Typography} from 'antd';
import TestConnectionNotification from 'components/TestConnectionNotification/TestConnectionNotification';
import {IPropsEvent} from './RunEvent';
import * as S from './RunEvents.styled';

const RunEventDataStore = ({event}: IPropsEvent) => (
<S.EventContainer>
<S.Dot $logLevel={event.logLevel} />
<Typography.Title level={3}>{event.title}</Typography.Title>
<Typography.Text>{event.description}</Typography.Text>
{!!event.dataStoreConnection && (
<S.Content>
<Typography.Title level={3}>
{event.dataStoreConnection?.allPassed
? 'Data store configuration is valid.'
: 'Data store configuration is not valid.'}
</Typography.Title>
<TestConnectionNotification result={event.dataStoreConnection} />
</S.Content>
)}
</S.EventContainer>
);

export default RunEventDataStore;
38 changes: 38 additions & 0 deletions web/src/components/RunEvents/RunEventPolling.tsx
@@ -0,0 +1,38 @@
import {Typography} from 'antd';
import {IPropsEvent} from './RunEvent';
import * as S from './RunEvents.styled';

const RunEventPolling = ({event}: IPropsEvent) => (
<S.EventContainer>
<S.Dot $logLevel={event.logLevel} />
<Typography.Title level={3}>{event.title}</Typography.Title>
<Typography.Text>{event.description}</Typography.Text>
{!!event.polling && (
<S.Content>
<S.Column>
<S.InfoIcon />
<div>
<Typography.Title level={3}>Number of spans collected:</Typography.Title>
<Typography.Text>{event.polling.periodic.numberSpans}</Typography.Text>
</div>
</S.Column>
<S.Column>
<S.InfoIcon />
<div>
<Typography.Title level={3}>Iteration number:</Typography.Title>
<Typography.Text>{event.polling.periodic.numberIterations}</Typography.Text>
</div>
</S.Column>
<S.Column>
<S.InfoIcon />
<div>
<Typography.Title level={3}>Reason why the next iteration will be executed:</Typography.Title>
<Typography.Text>{event.polling.reasonNextIteration}</Typography.Text>
</div>
</S.Column>
</S.Content>
)}
</S.EventContainer>
);

export default RunEventPolling;
41 changes: 34 additions & 7 deletions web/src/components/RunEvents/RunEvents.styled.ts
@@ -1,8 +1,7 @@
import {CloseCircleFilled} from '@ant-design/icons';
import {CloseCircleFilled, InfoCircleFilled, LoadingOutlined} from '@ant-design/icons';
import {Typography} from 'antd';
import styled, {DefaultTheme} from 'styled-components';

import {LogLevel} from 'models/TestRunEvent.model';
import styled, {css, DefaultTheme} from 'styled-components';
import {LogLevel} from 'constants/TestRunEvents.constants';

function getLogLevelColor(logLevel: LogLevel, theme: DefaultTheme): string {
if (logLevel === LogLevel.Error) return theme.color.error;
Expand All @@ -11,12 +10,29 @@ function getLogLevelColor(logLevel: LogLevel, theme: DefaultTheme): string {
return theme.color.textLight;
}

export const Container = styled.div`
export const Column = styled.div`
align-items: start;
display: flex;
gap: 6px;
margin-bottom: 8px;
`;

export const Container = styled.div<{$hasScroll?: boolean}>`
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
padding: 70px 120px;
padding: 70px 20%;
${({$hasScroll}) =>
$hasScroll &&
css`
height: 100%;
overflow-y: auto;
`}
`;

export const Content = styled.div`
margin: 16px;
`;

export const Dot = styled.div<{$logLevel?: LogLevel}>`
Expand All @@ -42,6 +58,11 @@ export const EventContainer = styled.div`
position: relative;
`;

export const InfoIcon = styled(InfoCircleFilled)`
color: ${({theme}) => theme.color.textLight};
margin-top: 3px;
`;

export const Link = styled(Typography.Link)`
font-weight: bold;
`;
Expand All @@ -50,6 +71,12 @@ export const ListContainer = styled.div`
padding: 24px 0;
`;

export const LoadingIcon = styled(LoadingOutlined)`
color: ${({theme}) => theme.color.primary};
font-size: 32px;
margin-bottom: 26px;
`;

export const Paragraph = styled(Typography.Paragraph)`
text-align: center;
`;
14 changes: 9 additions & 5 deletions web/src/components/RunEvents/RunEvents.tsx
@@ -1,9 +1,13 @@
import TestRunEvent, {TestRunStage} from 'models/TestRunEvent.model';
import {TestRunStage} from 'constants/TestRunEvents.constants';
import TestRunEvent from 'models/TestRunEvent.model';
import TestRunService from 'services/TestRun.service';
import {TTestRunState} from 'types/TestRun.types';
import RunEventsTrace from './RunEventsTrace';
import RunEventsTrigger from './RunEventsTrigger';

export interface IPropsComponent {
events: TestRunEvent[];
state: TTestRunState;
}

interface IProps extends IPropsComponent {
Expand All @@ -12,15 +16,15 @@ interface IProps extends IPropsComponent {

const ComponentMap: Record<TestRunStage, (props: IPropsComponent) => React.ReactElement> = {
[TestRunStage.Trigger]: RunEventsTrigger,
[TestRunStage.Trace]: RunEventsTrigger,
[TestRunStage.Test]: RunEventsTrigger,
[TestRunStage.Trace]: RunEventsTrace,
[TestRunStage.Test]: RunEventsTrace,
};

const RunEvents = ({events, stage}: IProps) => {
const RunEvents = ({events, stage, state}: IProps) => {
const Component = ComponentMap[stage];
const filteredEvents = TestRunService.getTestRunEventsByStage(events, stage);

return <Component events={filteredEvents} />;
return <Component events={filteredEvents} state={state} />;
};

export default RunEvents;

0 comments on commit a5d365c

Please sign in to comment.