Skip to content

Commit

Permalink
feature(frontned): adding FE text event log (#2396)
Browse files Browse the repository at this point in the history
* feature(frontned): adding FE text event log

* feature(frontend): final details
  • Loading branch information
xoscar committed Apr 17, 2023
1 parent 95c738c commit c56b6c1
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 3 deletions.
3 changes: 3 additions & 0 deletions web/src/assets/terminal.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions web/src/components/EventLogPopover/EventLogContent.tsx
@@ -0,0 +1,29 @@
import {useEffect, useRef} from 'react';
import TestRunEvent from 'models/TestRunEvent.model';
import EventLogService from 'services/EventLog.service';
import * as S from './EventLogPopover.styled';

interface IProps {
runEvents: TestRunEvent[];
}

const EventLogContent = ({runEvents}: IProps) => {
const bottomRef = useRef<HTMLDivElement>(null);

useEffect(() => {
bottomRef.current?.scrollIntoView({behavior: 'smooth'});
}, [runEvents]);

return (
<S.Container>
{runEvents.map(event => (
<S.EventEntry key={`${event.type}-${event.createdAt}`} $logLevel={event.logLevel}>
<b>{EventLogService.typeToString(event)}</b> {EventLogService.detailsToString(event)}
</S.EventEntry>
))}
<div ref={bottomRef} />
</S.Container>
);
};

export default EventLogContent;
62 changes: 62 additions & 0 deletions web/src/components/EventLogPopover/EventLogPopover.styled.ts
@@ -0,0 +1,62 @@
import {CopyOutlined} from '@ant-design/icons';
import {Typography} from 'antd';
import styled, {DefaultTheme, createGlobalStyle} from 'styled-components';
import {LogLevel} from 'constants/TestRunEvents.constants';
import terminalIcon from 'assets/terminal.svg';

function getLogLevelColor(logLevel: LogLevel, theme: DefaultTheme): string {
if (logLevel === LogLevel.Error) return theme.color.error;
if (logLevel === LogLevel.Success) return theme.color.success;
if (logLevel === LogLevel.Warning) return theme.color.warningYellow;
return theme.color.text;
}

export const GlobalStyle = createGlobalStyle`
#eventlog-popover {
.ant-popover-inner-content {
padding: 5px 16px;
}
}
`;

export const Container = styled.div`
margin: 0;
max-height: calc(100vh - 200px);
width: 550px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 2px;
position: relative;
`;

export const CopyIcon = styled(CopyOutlined)`
color: ${({theme}) => theme.color.primary};
cursor: pointer;
`;

export const EventEntry = styled(Typography.Text)<{$logLevel?: LogLevel}>`
font-size: ${({theme}) => theme.size.sm};
color: ${({theme, $logLevel = LogLevel.Info}) => getLogLevelColor($logLevel, theme)};
margin: 0;
`;

export const TerminalIcon = styled.img.attrs({src: terminalIcon})`
width: 20px;
height: 20px;
cursor: pointer;
`;

export const TitleContainer = styled.div`
padding: 5px 0;
display: flex;
justify-content: space-between;
`;

export const Title = styled(Typography.Title)`
&& {
margin: 0;
font-size: ${({theme}) => theme.size.md};
}
`;
38 changes: 38 additions & 0 deletions web/src/components/EventLogPopover/EventLogPopover.tsx
@@ -0,0 +1,38 @@
import {Popover, Tooltip} from 'antd';
import TestRunEvent from 'models/TestRunEvent.model';
import useCopy from 'hooks/useCopy';
import EventLogService from 'services/EventLog.service';
import EventLogContent from './EventLogContent';
import * as S from './EventLogPopover.styled';

interface IProps {
runEvents: TestRunEvent[];
}

const EventLogPopover = ({runEvents}: IProps) => {
const copy = useCopy();

return (
<>
<S.GlobalStyle />
<Popover
id="eventlog-popover"
content={<EventLogContent runEvents={runEvents} />}
trigger="click"
placement="bottomLeft"
title={
<S.TitleContainer>
<S.Title>Event Log</S.Title>
<Tooltip title="Copy Text">
<S.CopyIcon onClick={() => copy(EventLogService.listToString(runEvents))} />
</Tooltip>
</S.TitleContainer>
}
>
<S.TerminalIcon />
</Popover>
</>
);
};

export default EventLogPopover;
2 changes: 2 additions & 0 deletions web/src/components/EventLogPopover/index.ts
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './EventLogPopover';
4 changes: 3 additions & 1 deletion web/src/components/RunDetailLayout/HeaderRight.tsx
Expand Up @@ -10,6 +10,7 @@ import {useTestRun} from 'providers/TestRun/TestRun.provider';
import {useTestSpecs} from 'providers/TestSpecs/TestSpecs.provider';
import {useTestOutput} from 'providers/TestOutput/TestOutput.provider';
import * as S from './RunDetailLayout.styled';
import EventLogPopover from '../EventLogPopover/EventLogPopover';

interface IProps {
testId: string;
Expand All @@ -20,7 +21,7 @@ const HeaderRight = ({testId, testVersion}: IProps) => {
const {isDraftMode: isTestSpecsDraftMode} = useTestSpecs();
const {isDraftMode: isTestOutputsDraftMode} = useTestOutput();
const isDraftMode = isTestSpecsDraftMode || isTestOutputsDraftMode;
const {isLoadingStop, run, stopRun} = useTestRun();
const {isLoadingStop, run, stopRun, runEvents} = useTestRun();
const {onRun} = useTest();
const state = run.state;

Expand Down Expand Up @@ -51,6 +52,7 @@ const HeaderRight = ({testId, testVersion}: IProps) => {
Run Test
</Button>
)}
<EventLogPopover runEvents={runEvents} />
<RunActionsMenu
isRunView
resultId={run.id}
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/RunEvents/RunEventsTrace.tsx
Expand Up @@ -19,7 +19,7 @@ const HeaderComponentMap: Record<TestStateType, () => React.ReactElement> = {
[TestState.STOPPED]: StoppedHeader,
};

type TraceEventTypeWithoutFetching = Exclude<
export type TraceEventTypeWithoutFetching = Exclude<
TraceEventType,
TraceEventType.FETCHING_START | TraceEventType.FETCHING_ERROR | TraceEventType.FETCHING_SUCCESS
>;
Expand Down
2 changes: 1 addition & 1 deletion web/src/models/TestRunEvent.model.ts
Expand Up @@ -13,7 +13,7 @@ type TestRunEvent = Model<
{logLevel: LogLevel; dataStoreConnection?: ConnectionResult; polling?: PollingInfo; outputs?: OutputInfo[]}
>;

function PollingInfo({
export function PollingInfo({
type = PollingInfoType.Periodic,
isComplete = false,
periodic = {},
Expand Down
55 changes: 55 additions & 0 deletions web/src/services/EventLog.service.ts
@@ -0,0 +1,55 @@
import {parseISO, formatISO} from 'date-fns';
import TestRunEvent, {PollingInfo} from 'models/TestRunEvent.model';
import ConnectionResult from 'models/ConnectionResult.model';
import {TraceEventType} from 'constants/TestRunEvents.constants';

type TEventToStringFn = (event: TestRunEvent) => string;

const eventToString = ({title, description}: TestRunEvent): string => {
return `${title} - ${description}`;
};

const dataStoreEventToString = (event: TestRunEvent): string => {
const {dataStoreConnection: {allPassed, ...dataStoreConnection} = ConnectionResult({})} = event;
const baseText = eventToString(event);
const configValidText = allPassed ? 'Data store configuration is valid.' : 'Data store configuration is not valid.';

const connectionStepsDetailsText = Object.entries(dataStoreConnection || {})
.map(([key, {message, error}]) => `${key.toUpperCase()} - ${message} ${error ? ` - ${error}` : ''}`, '')
.join(' - ');

return `${baseText} - ${configValidText} - ${connectionStepsDetailsText}`;
};

const pollingEventToString = (event: TestRunEvent): string => {
const {polling: {type: pollingType, isComplete, periodic} = PollingInfo({})} = event;
const baseText = eventToString(event);
const pollingTypeText = `Polling type: ${pollingType}`;
const pollingCompleteText = `Polling complete: ${isComplete}`;
const periodicText = `Periodic polling - number of spans: ${periodic?.numberSpans}, number of iterations: ${periodic?.numberIterations}`;

return `${baseText} - ${pollingTypeText} - ${pollingCompleteText} - ${periodicText}`;
};

const eventToStringMap: Record<string, TEventToStringFn> = {
[TraceEventType.DATA_STORE_CONNECTION_INFO]: dataStoreEventToString,
[TraceEventType.POLLING_ITERATION_INFO]: pollingEventToString,
};

const EventLogService = () => ({
detailsToString(event: TestRunEvent) {
const eventToStringFn = eventToStringMap[event.type] || eventToString;

return eventToStringFn(event);
},
typeToString({type, createdAt}: TestRunEvent) {
const createdAtDate = parseISO(createdAt);

return `[${formatISO(createdAtDate)} - ${type}]`;
},
listToString(events: TestRunEvent[]) {
return events.map(event => `${this.typeToString(event)} ${this.detailsToString(event)}`).join('\r');
},
});

export default EventLogService();

0 comments on commit c56b6c1

Please sign in to comment.