Skip to content

Commit

Permalink
feat(frontend): support specs and outputs creation from trigger respo…
Browse files Browse the repository at this point in the history
…nse (#2703)
  • Loading branch information
jorgeepc authored Jun 9, 2023
1 parent b588223 commit 00282b5
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 81 deletions.
6 changes: 3 additions & 3 deletions web/cypress/e2e/TestRunDetail/Outputs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('Outputs', () => {
cy.get('[data-cy=toggle-drawer]', {timeout: 25000}).click({force: true});
cy.get('[data-cy=attributes-search-container] input').type('db.name');
cy.get('[data-cy=attribute-row-db-name] .ant-dropdown-trigger').click();
cy.contains('Create output').click();
cy.contains('Create test output').click();

// Save output
cy.wait('@getSelect');
Expand Down Expand Up @@ -49,7 +49,7 @@ describe('Outputs', () => {
cy.get('[data-cy=toggle-drawer]', {timeout: 25000}).click({force: true});
cy.get('[data-cy=attributes-search-container] input').type('db.name');
cy.get('[data-cy=attribute-row-db-name] .ant-dropdown-trigger').click();
cy.contains('Create output').click();
cy.contains('Create test output').click();

// Save output
cy.wait('@getSelect');
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('Outputs', () => {
cy.get('[data-cy=toggle-drawer]', {timeout: 25000}).click({force: true});
cy.get('[data-cy=attributes-search-container] input').type('db.name');
cy.get('[data-cy=attribute-row-db-name] .ant-dropdown-trigger').click();
cy.contains('Create output').click();
cy.contains('Create test output').click();

// Save output
cy.wait('@getSelect');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from 'styled-components';

import moreIcon from 'assets/more.svg';

export const MoreIcon = styled.img.attrs({
src: moreIcon,
})``;
66 changes: 66 additions & 0 deletions web/src/components/AttributeActions/AttributeActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {Dropdown, Menu, MenuProps, Space} from 'antd';

import useCopy from 'hooks/useCopy';
import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import {TSpanFlatAttribute} from 'types/Span.types';
import * as S from './AttributeActions.styled';

const Action = {
Copy: '0',
Create_Spec: '1',
Create_Output: '2',
} as const;

const menuItems: MenuProps['items'] = [
{
key: Action.Copy,
label: 'Copy value',
},
{
key: Action.Create_Output,
label: 'Create test output',
},
{
key: Action.Create_Spec,
label: 'Create test spec',
},
];

interface IProps {
attribute: TSpanFlatAttribute;
children?: React.ReactNode;
onCreateTestOutput(attribute: TSpanFlatAttribute): void;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
}

const AttributeActions = ({attribute, children, onCreateTestOutput, onCreateTestSpec}: IProps) => {
const copy = useCopy();

const handleOnClick = ({key: option}: {key: string}) => {
if (option === Action.Copy) {
TraceAnalyticsService.onAttributeCopy();
copy(attribute.value);
}

if (option === Action.Create_Spec) {
return onCreateTestSpec(attribute);
}

if (option === Action.Create_Output) {
return onCreateTestOutput(attribute);
}
};
return (
<Dropdown overlay={<Menu items={menuItems} onClick={handleOnClick} />}>
{children || (
<a onClick={e => e.preventDefault()}>
<Space>
<S.MoreIcon />
</Space>
</a>
)}
</Dropdown>
);
};

export default AttributeActions;
2 changes: 2 additions & 0 deletions web/src/components/AttributeActions/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 './AttributeActions';
51 changes: 4 additions & 47 deletions web/src/components/AttributeRow/AttributeRow.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {Dropdown, Menu, Popover} from 'antd';
import {Popover} from 'antd';
import parse from 'html-react-parser';
import MarkdownIt from 'markdown-it';
import {useMemo} from 'react';

import AttributeActions from 'components/AttributeActions/AttributeActions';
import AttributeValue from 'components/AttributeValue';
import {OtelReference} from 'components/TestSpecForm/hooks/useGetOTELSemanticConventionAttributesInfo';
import SpanAttributeService from 'services/SpanAttribute.service';
import {TResultAssertions} from 'types/Assertion.types';
import {TSpanFlatAttribute} from 'types/Span.types';
import TestOutput from 'models/TestOutput.model';
import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import useCopy from 'hooks/useCopy';
import * as S from './AttributeRow.styled';
import AssertionResultChecks from '../AssertionResultChecks/AssertionResultChecks';

Expand All @@ -24,12 +23,6 @@ interface IProps {
outputs: TestOutput[];
}

enum Action {
Copy = '0',
Create_Spec = '1',
Create_Output = '2',
}

const AttributeRow = ({
assertions = {},
attribute: {key, value},
Expand All @@ -40,7 +33,6 @@ const AttributeRow = ({
onCreateOutput,
outputs,
}: IProps) => {
const copy = useCopy();
const semanticConvention = SpanAttributeService.getReferencePicker(semanticConventions, key);
const description = useMemo(() => parse(MarkdownIt().render(semanticConvention.description)), [semanticConvention]);
const note = useMemo(() => parse(MarkdownIt().render(semanticConvention.note)), [semanticConvention]);
Expand All @@ -53,43 +45,8 @@ const AttributeRow = ({
[key, outputs]
);

const handleOnClick = ({key: option}: {key: string}) => {
if (option === Action.Copy) {
TraceAnalyticsService.onAttributeCopy();
copy(value);
}

if (option === Action.Create_Spec) {
return onCreateTestSpec(attribute);
}

if (option === Action.Create_Output) {
return onCreateOutput(attribute);
}
};

const cypressKey = key.toLowerCase().replace('.', '-');

const menu = (
<Menu
items={[
{
label: 'Copy value',
key: Action.Copy,
},
{
label: 'Create output',
key: Action.Create_Output,
},
{
label: 'Create test spec',
key: Action.Create_Spec,
},
]}
onClick={handleOnClick}
/>
);

const content = (
<S.DetailContainer>
{description}
Expand Down Expand Up @@ -123,11 +80,11 @@ const AttributeRow = ({
<AssertionResultChecks failed={failed} passed={passed} />
</S.Header>

<Dropdown overlay={menu}>
<AttributeActions attribute={attribute} onCreateTestOutput={onCreateOutput} onCreateTestSpec={onCreateTestSpec}>
<a onClick={e => e.preventDefault()} style={{height: 'fit-content'}}>
<S.MoreIcon />
</a>
</Dropdown>
</AttributeActions>
</S.Container>
);
};
Expand Down
25 changes: 11 additions & 14 deletions web/src/components/HeaderRow/HeaderRow.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import {CopyOutlined} from '@ant-design/icons';
import {useTheme} from 'styled-components';
import AttributeActions from 'components/AttributeActions';
import {TSpanFlatAttribute} from 'types/Span.types';
import {THeader} from 'types/Test.types';
import TestRunAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
import useCopy from 'hooks/useCopy';
import Highlighted from '../Highlighted';
import * as S from './HeaderRow.styled';

interface IProps {
header: THeader;
index: number;
onCreateTestOutput(attribute: TSpanFlatAttribute): void;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
}

const HeaderRow = ({header: {key = '', value = ''}}: IProps) => {
const copy = useCopy();
const handleOnClick = () => {
TestRunAnalyticsService.onTriggerResponseHeaderCopy();
return copy(value);
};

const theme = useTheme();

const HeaderRow = ({header: {key = '', value = ''}, index, onCreateTestOutput, onCreateTestSpec}: IProps) => {
return (
<S.HeaderContainer>
<S.Header>
Expand All @@ -27,7 +20,11 @@ const HeaderRow = ({header: {key = '', value = ''}}: IProps) => {
<Highlighted text={value} highlight="" />
</S.HeaderValue>
</S.Header>
<CopyOutlined style={{color: theme.color.textLight}} onClick={handleOnClick} />
<AttributeActions
attribute={{key: `tracetest.response.headers | json_path '$[${index}].Value'`, value}}
onCreateTestOutput={onCreateTestOutput}
onCreateTestSpec={onCreateTestSpec}
/>
</S.HeaderContainer>
);
};
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/RunDetailTrigger/RunDetailTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface IProps {
isError: boolean;
}

const RunDetailTrigger = ({test, run: {state, triggerResult, triggerTime}, runEvents, isError}: IProps) => {
const RunDetailTrigger = ({test, run: {id, state, triggerResult, triggerTime}, runEvents, isError}: IProps) => {
const shouldDisplayError = isError || state === TestState.TRIGGER_FAILED;

return (
Expand All @@ -29,7 +29,9 @@ const RunDetailTrigger = ({test, run: {state, triggerResult, triggerTime}, runEv
<RunEvents events={runEvents} stage={TestRunStage.Trigger} state={state} />
) : (
<RunDetailTriggerResponseFactory
runId={id}
state={state}
testId={test.id}
triggerResult={triggerResult}
triggerTime={triggerTime}
type={triggerResult?.type ?? TriggerTypes.http}
Expand Down
24 changes: 22 additions & 2 deletions web/src/components/RunDetailTriggerResponse/ResponseBody.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import AttributeActions from 'components/AttributeActions';
import {isRunStateFinished} from 'models/TestRun.model';
import {TSpanFlatAttribute} from 'types/Span.types';
import {TTestRunState} from 'types/TestRun.types';
import SkeletonResponse from './SkeletonResponse';
import CodeBlock from '../CodeBlock';
import * as S from './RunDetailTriggerResponse.styled';

interface IProps {
body?: string;
bodyMimeType?: string;
state: TTestRunState;
onCreateTestOutput(attribute: TSpanFlatAttribute): void;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
}

const ResponseBody = ({body = '', bodyMimeType = '', state}: IProps) =>
isRunStateFinished(state) || !!body ? <CodeBlock value={body} mimeType={bodyMimeType} /> : <SkeletonResponse />;
const ResponseBody = ({body = '', bodyMimeType = '', state, onCreateTestOutput, onCreateTestSpec}: IProps) =>
isRunStateFinished(state) || !!body ? (
<S.ResponseBodyContainer>
<S.ResponseBodyContent>
<CodeBlock value={body} mimeType={bodyMimeType} />
</S.ResponseBodyContent>
<S.ResponseBodyActions>
<AttributeActions
attribute={{key: 'tracetest.response.body', value: body}}
onCreateTestOutput={onCreateTestOutput}
onCreateTestSpec={onCreateTestSpec}
/>
</S.ResponseBodyActions>
</S.ResponseBodyContainer>
) : (
<SkeletonResponse />
);

export default ResponseBody;
18 changes: 16 additions & 2 deletions web/src/components/RunDetailTriggerResponse/ResponseHeaders.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import HeaderRow from 'components/HeaderRow';
import {isRunStateFinished} from 'models/TestRun.model';
import {TSpanFlatAttribute} from 'types/Span.types';
import {THeader} from 'types/Test.types';
import {TTestRunState} from 'types/TestRun.types';
import SkeletonResponse from './SkeletonResponse';
Expand All @@ -8,11 +9,24 @@ import * as S from './RunDetailTriggerResponse.styled';
interface IProps {
headers?: THeader[];
state: TTestRunState;
onCreateTestOutput(attribute: TSpanFlatAttribute): void;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
}

const ResponseHeaders = ({headers, state}: IProps) =>
const ResponseHeaders = ({headers, state, onCreateTestOutput, onCreateTestSpec}: IProps) =>
isRunStateFinished(state) || !!headers ? (
<S.HeadersList>{headers && headers.map(header => <HeaderRow header={header} key={header.key} />)}</S.HeadersList>
<S.HeadersList>
{headers &&
headers.map((header, index) => (
<HeaderRow
header={header}
index={index}
key={header.key}
onCreateTestOutput={onCreateTestOutput}
onCreateTestSpec={onCreateTestSpec}
/>
))}
</S.HeadersList>
) : (
<SkeletonResponse />
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,16 @@ export const EmptyText = styled(Typography.Text)`
`;

export const EmptyTitle = styled(Typography.Title).attrs({level: 3})``;

export const ResponseBodyContainer = styled.div`
display: flex;
width: 100%;
`;

export const ResponseBodyContent = styled.div`
flex: 1;
`;

export const ResponseBodyActions = styled.div`
margin: 16px 0 0 4px;
`;
Loading

0 comments on commit 00282b5

Please sign in to comment.