Skip to content

Commit e0f422b

Browse files
authored
feature(frontend): adding test spec snippets (#2366)
* feature(frontend): adding test spec snippets * feature(frontend): adding test spec snippets * feature(frontend): updating based on feedback
1 parent b4f9814 commit e0f422b

File tree

5 files changed

+200
-31
lines changed

5 files changed

+200
-31
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
2+
import {CaretDownOutlined} from '@ant-design/icons';
3+
import {Dropdown, Menu} from 'antd';
4+
import SpanService from 'services/Span.service';
5+
import Span from 'models/Span.model';
6+
import {TEST_SPEC_SNIPPETS, TSnippet} from 'constants/TestSpecs.constants';
7+
import * as S from './TestResults.styled';
8+
import {useTestSpecForm} from '../TestSpecForm/TestSpecForm.provider';
9+
10+
interface IProps {
11+
selectedSpan: Span;
12+
visibleByDefault?: boolean;
13+
}
14+
15+
const AddTestSpecButton = ({selectedSpan, visibleByDefault = false}: IProps) => {
16+
const {open} = useTestSpecForm();
17+
const caretRef = useRef<HTMLElement>(null);
18+
const handleEmptyTestSpec = useCallback(() => {
19+
const selector = SpanService.getSelectorInformation(selectedSpan);
20+
21+
open({
22+
isEditing: false,
23+
selector,
24+
defaultValues: {
25+
selector,
26+
},
27+
});
28+
}, [open, selectedSpan]);
29+
30+
const onSnippetClick = useCallback(
31+
(snippet: TSnippet) => {
32+
open({
33+
isEditing: false,
34+
selector: snippet.selector,
35+
defaultValues: snippet,
36+
});
37+
},
38+
[open]
39+
);
40+
41+
useEffect(() => {
42+
if (visibleByDefault && caretRef.current) {
43+
caretRef.current?.click();
44+
}
45+
}, [visibleByDefault]);
46+
47+
const menu = useMemo(
48+
() => (
49+
<Menu
50+
items={[
51+
{
52+
label: 'Try these snippets for quick testing:',
53+
key: 'test',
54+
type: 'group',
55+
children: TEST_SPEC_SNIPPETS.map(snippet => ({
56+
label: snippet.name,
57+
key: snippet.name,
58+
onClick: () => onSnippetClick(snippet),
59+
})),
60+
},
61+
{type: 'divider'},
62+
{
63+
label: 'Empty Test Spec',
64+
key: 'empty-test-spec',
65+
onClick: handleEmptyTestSpec,
66+
},
67+
]}
68+
/>
69+
),
70+
[handleEmptyTestSpec, onSnippetClick]
71+
);
72+
73+
return (
74+
<Dropdown.Button
75+
overlay={menu}
76+
trigger={['click']}
77+
placement="bottomRight"
78+
onClick={handleEmptyTestSpec}
79+
type="primary"
80+
buttonsRender={([leftButton]) => [
81+
React.cloneElement(leftButton as React.ReactElement<any, string>, {'data-cy': 'add-test-spec-button'}),
82+
<S.CaretDropdownButton ref={caretRef} type="primary" data-cy="create-button">
83+
<CaretDownOutlined />
84+
</S.CaretDropdownButton>,
85+
]}
86+
>
87+
Add Test Spec
88+
</Dropdown.Button>
89+
);
90+
};
91+
92+
export default AddTestSpecButton;

web/src/components/TestResults/Header.tsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import {StepsID} from 'components/GuidedTour/testRunSteps';
2-
import {useTestSpecForm} from 'components/TestSpecForm/TestSpecForm.provider';
3-
import SpanService from 'services/Span.service';
41
import {singularOrPlural} from 'utils/Common';
52
import Span from 'models/Span.model';
63
import * as S from './TestResults.styled';
4+
import AddTestSpecButton from './AddTestSpecButton';
75

86
interface IProps {
97
selectedSpan: Span;
@@ -12,18 +10,7 @@ interface IProps {
1210
}
1311

1412
const Header = ({selectedSpan, totalFailedSpecs, totalPassedSpecs}: IProps) => {
15-
const {open} = useTestSpecForm();
16-
17-
const handleAddTestSpecOnClick = () => {
18-
const selector = SpanService.getSelectorInformation(selectedSpan!);
19-
open({
20-
isEditing: false,
21-
selector,
22-
defaultValues: {
23-
selector,
24-
},
25-
});
26-
};
13+
const hasSpecs = !!(totalFailedSpecs || totalPassedSpecs);
2714

2815
return (
2916
<S.HeaderContainer>
@@ -45,9 +32,7 @@ const Header = ({selectedSpan, totalFailedSpecs, totalPassedSpecs}: IProps) => {
4532
</div>
4633
</S.Row>
4734

48-
<S.PrimaryButton data-tour={StepsID.TestSpecs} data-cy="add-test-spec-button" onClick={handleAddTestSpecOnClick}>
49-
Add Test Spec
50-
</S.PrimaryButton>
35+
<AddTestSpecButton selectedSpan={selectedSpan} visibleByDefault={!hasSpecs} />
5136
</S.HeaderContainer>
5237
);
5338
};

web/src/components/TestResults/TestResults.styled.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,15 @@ export const LoadingContainer = styled.div`
4040
text-align: center;
4141
`;
4242

43-
export const PrimaryButton = styled(Button).attrs({
44-
type: 'primary',
45-
})``;
46-
4743
export const Row = styled.div`
4844
align-items: center;
4945
display: flex;
5046
gap: 8px;
5147
`;
48+
49+
export const CaretDropdownButton = styled(Button)`
50+
font-weight: 600;
51+
opacity: 0.7;
52+
width: 32px;
53+
padding: 0px;
54+
`;

web/src/components/TestResults/TestResults.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,27 @@ const TestResults = ({onDelete, onEdit, onRevert}: IProps) => {
3636

3737
return (
3838
<S.Container>
39-
<Header selectedSpan={selectedSpan!} totalFailedSpecs={totalFailedSpecs} totalPassedSpecs={totalPassedSpecs} />
40-
4139
{isLoading && (
4240
<S.LoadingContainer>
4341
<LoadingSpinner />
4442
</S.LoadingContainer>
4543
)}
4644

4745
{!isLoading && (
48-
<TestSpecs
49-
assertionResults={assertionResults}
50-
onDelete={onDelete}
51-
onEdit={onEdit}
52-
onOpen={handleOpen}
53-
onRevert={onRevert}
54-
/>
46+
<>
47+
<Header
48+
selectedSpan={selectedSpan!}
49+
totalFailedSpecs={totalFailedSpecs}
50+
totalPassedSpecs={totalPassedSpecs}
51+
/>
52+
<TestSpecs
53+
assertionResults={assertionResults}
54+
onDelete={onDelete}
55+
onEdit={onEdit}
56+
onOpen={handleOpen}
57+
onRevert={onRevert}
58+
/>
59+
</>
5560
)}
5661
</S.Container>
5762
);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {IValues} from 'components/TestSpecForm/TestSpecForm';
2+
3+
export type TSnippet = Required<IValues>;
4+
5+
export const HTTP_SPANS_STATUS_CODE: TSnippet = {
6+
name: 'All HTTP Spans: Status code is 200',
7+
selector: 'span[tracetest.span.type="http"]',
8+
assertions: [
9+
{
10+
left: 'attr:http.status_code',
11+
comparator: '=',
12+
right: '200',
13+
},
14+
],
15+
};
16+
17+
export const TRIGGER_SPAN_RESPONSE_TIME: TSnippet = {
18+
name: 'Trigger Span: Response time is less than 200ms',
19+
selector: 'span[tracetest.span.type="general" name="Tracetest trigger"]',
20+
assertions: [
21+
{
22+
left: 'attr:tracetest.span.duration',
23+
comparator: '<',
24+
right: '200ms',
25+
},
26+
],
27+
};
28+
29+
export const DB_SPANS_RESPONSE_TIME: TSnippet = {
30+
name: 'All Database Spans: Processing time is less than 100ms',
31+
selector: 'span[tracetest.span.type="database"]',
32+
assertions: [
33+
{
34+
left: 'attr:tracetest.span.duration',
35+
comparator: '<',
36+
right: '100ms',
37+
},
38+
],
39+
};
40+
41+
export const TRIGGER_SPAN_RESPONSE_BODY_CONTAINS: TSnippet = {
42+
name: 'Trigger Span: Response body contains "this string"',
43+
selector: 'span[tracetest.span.type="general" name="Tracetest trigger"]',
44+
assertions: [
45+
{
46+
left: 'attr:tracetest.response.body',
47+
comparator: 'contains',
48+
right: '"this string"',
49+
},
50+
],
51+
};
52+
53+
export const GRPC_SPANS_STATUS_CODE: TSnippet = {
54+
name: 'All gRPC Spans: Status is Ok',
55+
selector: 'span[tracetest.span.type="rpc" rpc.system="grpc"]',
56+
assertions: [
57+
{
58+
left: 'attr:grpc.status_code',
59+
comparator: '=',
60+
right: '0',
61+
},
62+
],
63+
};
64+
65+
export const DB_SPANS_QUALITY_DB_STATEMENT_PRESENT: TSnippet = {
66+
name: 'All Database Spans: db.statement should always be defined (QUALITY)',
67+
selector: 'span[tracetest.span.type="database"]',
68+
assertions: [
69+
{
70+
left: 'attr:db.system',
71+
comparator: '!=',
72+
right: '""',
73+
},
74+
],
75+
};
76+
77+
export const TEST_SPEC_SNIPPETS: TSnippet[] = [
78+
HTTP_SPANS_STATUS_CODE,
79+
GRPC_SPANS_STATUS_CODE,
80+
TRIGGER_SPAN_RESPONSE_TIME,
81+
TRIGGER_SPAN_RESPONSE_BODY_CONTAINS,
82+
DB_SPANS_RESPONSE_TIME,
83+
DB_SPANS_QUALITY_DB_STATEMENT_PRESENT,
84+
];

0 commit comments

Comments
 (0)