From e0f422b8aca608ce5bdad3208990f28efea093ef Mon Sep 17 00:00:00 2001 From: Oscar Reyes Date: Tue, 11 Apr 2023 16:02:27 -0600 Subject: [PATCH] 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 --- .../TestResults/AddTestSpecButton.tsx | 92 +++++++++++++++++++ web/src/components/TestResults/Header.tsx | 21 +---- .../TestResults/TestResults.styled.ts | 11 ++- .../components/TestResults/TestResults.tsx | 23 +++-- web/src/constants/TestSpecs.constants.ts | 84 +++++++++++++++++ 5 files changed, 200 insertions(+), 31 deletions(-) create mode 100644 web/src/components/TestResults/AddTestSpecButton.tsx create mode 100644 web/src/constants/TestSpecs.constants.ts diff --git a/web/src/components/TestResults/AddTestSpecButton.tsx b/web/src/components/TestResults/AddTestSpecButton.tsx new file mode 100644 index 0000000000..c2e2dbfe66 --- /dev/null +++ b/web/src/components/TestResults/AddTestSpecButton.tsx @@ -0,0 +1,92 @@ +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {CaretDownOutlined} from '@ant-design/icons'; +import {Dropdown, Menu} from 'antd'; +import SpanService from 'services/Span.service'; +import Span from 'models/Span.model'; +import {TEST_SPEC_SNIPPETS, TSnippet} from 'constants/TestSpecs.constants'; +import * as S from './TestResults.styled'; +import {useTestSpecForm} from '../TestSpecForm/TestSpecForm.provider'; + +interface IProps { + selectedSpan: Span; + visibleByDefault?: boolean; +} + +const AddTestSpecButton = ({selectedSpan, visibleByDefault = false}: IProps) => { + const {open} = useTestSpecForm(); + const caretRef = useRef(null); + const handleEmptyTestSpec = useCallback(() => { + const selector = SpanService.getSelectorInformation(selectedSpan); + + open({ + isEditing: false, + selector, + defaultValues: { + selector, + }, + }); + }, [open, selectedSpan]); + + const onSnippetClick = useCallback( + (snippet: TSnippet) => { + open({ + isEditing: false, + selector: snippet.selector, + defaultValues: snippet, + }); + }, + [open] + ); + + useEffect(() => { + if (visibleByDefault && caretRef.current) { + caretRef.current?.click(); + } + }, [visibleByDefault]); + + const menu = useMemo( + () => ( + ({ + label: snippet.name, + key: snippet.name, + onClick: () => onSnippetClick(snippet), + })), + }, + {type: 'divider'}, + { + label: 'Empty Test Spec', + key: 'empty-test-spec', + onClick: handleEmptyTestSpec, + }, + ]} + /> + ), + [handleEmptyTestSpec, onSnippetClick] + ); + + return ( + [ + React.cloneElement(leftButton as React.ReactElement, {'data-cy': 'add-test-spec-button'}), + + + , + ]} + > + Add Test Spec + + ); +}; + +export default AddTestSpecButton; diff --git a/web/src/components/TestResults/Header.tsx b/web/src/components/TestResults/Header.tsx index b4151cf954..587859b9f8 100644 --- a/web/src/components/TestResults/Header.tsx +++ b/web/src/components/TestResults/Header.tsx @@ -1,9 +1,7 @@ -import {StepsID} from 'components/GuidedTour/testRunSteps'; -import {useTestSpecForm} from 'components/TestSpecForm/TestSpecForm.provider'; -import SpanService from 'services/Span.service'; import {singularOrPlural} from 'utils/Common'; import Span from 'models/Span.model'; import * as S from './TestResults.styled'; +import AddTestSpecButton from './AddTestSpecButton'; interface IProps { selectedSpan: Span; @@ -12,18 +10,7 @@ interface IProps { } const Header = ({selectedSpan, totalFailedSpecs, totalPassedSpecs}: IProps) => { - const {open} = useTestSpecForm(); - - const handleAddTestSpecOnClick = () => { - const selector = SpanService.getSelectorInformation(selectedSpan!); - open({ - isEditing: false, - selector, - defaultValues: { - selector, - }, - }); - }; + const hasSpecs = !!(totalFailedSpecs || totalPassedSpecs); return ( @@ -45,9 +32,7 @@ const Header = ({selectedSpan, totalFailedSpecs, totalPassedSpecs}: IProps) => { - - Add Test Spec - + ); }; diff --git a/web/src/components/TestResults/TestResults.styled.ts b/web/src/components/TestResults/TestResults.styled.ts index dcfb570260..22347a5138 100644 --- a/web/src/components/TestResults/TestResults.styled.ts +++ b/web/src/components/TestResults/TestResults.styled.ts @@ -40,12 +40,15 @@ export const LoadingContainer = styled.div` text-align: center; `; -export const PrimaryButton = styled(Button).attrs({ - type: 'primary', -})``; - export const Row = styled.div` align-items: center; display: flex; gap: 8px; `; + +export const CaretDropdownButton = styled(Button)` + font-weight: 600; + opacity: 0.7; + width: 32px; + padding: 0px; +`; diff --git a/web/src/components/TestResults/TestResults.tsx b/web/src/components/TestResults/TestResults.tsx index d4526b17c0..77968585b9 100644 --- a/web/src/components/TestResults/TestResults.tsx +++ b/web/src/components/TestResults/TestResults.tsx @@ -36,8 +36,6 @@ const TestResults = ({onDelete, onEdit, onRevert}: IProps) => { return ( -
- {isLoading && ( @@ -45,13 +43,20 @@ const TestResults = ({onDelete, onEdit, onRevert}: IProps) => { )} {!isLoading && ( - + <> +
+ + )} ); diff --git a/web/src/constants/TestSpecs.constants.ts b/web/src/constants/TestSpecs.constants.ts new file mode 100644 index 0000000000..c9a0f79cb4 --- /dev/null +++ b/web/src/constants/TestSpecs.constants.ts @@ -0,0 +1,84 @@ +import {IValues} from 'components/TestSpecForm/TestSpecForm'; + +export type TSnippet = Required; + +export const HTTP_SPANS_STATUS_CODE: TSnippet = { + name: 'All HTTP Spans: Status code is 200', + selector: 'span[tracetest.span.type="http"]', + assertions: [ + { + left: 'attr:http.status_code', + comparator: '=', + right: '200', + }, + ], +}; + +export const TRIGGER_SPAN_RESPONSE_TIME: TSnippet = { + name: 'Trigger Span: Response time is less than 200ms', + selector: 'span[tracetest.span.type="general" name="Tracetest trigger"]', + assertions: [ + { + left: 'attr:tracetest.span.duration', + comparator: '<', + right: '200ms', + }, + ], +}; + +export const DB_SPANS_RESPONSE_TIME: TSnippet = { + name: 'All Database Spans: Processing time is less than 100ms', + selector: 'span[tracetest.span.type="database"]', + assertions: [ + { + left: 'attr:tracetest.span.duration', + comparator: '<', + right: '100ms', + }, + ], +}; + +export const TRIGGER_SPAN_RESPONSE_BODY_CONTAINS: TSnippet = { + name: 'Trigger Span: Response body contains "this string"', + selector: 'span[tracetest.span.type="general" name="Tracetest trigger"]', + assertions: [ + { + left: 'attr:tracetest.response.body', + comparator: 'contains', + right: '"this string"', + }, + ], +}; + +export const GRPC_SPANS_STATUS_CODE: TSnippet = { + name: 'All gRPC Spans: Status is Ok', + selector: 'span[tracetest.span.type="rpc" rpc.system="grpc"]', + assertions: [ + { + left: 'attr:grpc.status_code', + comparator: '=', + right: '0', + }, + ], +}; + +export const DB_SPANS_QUALITY_DB_STATEMENT_PRESENT: TSnippet = { + name: 'All Database Spans: db.statement should always be defined (QUALITY)', + selector: 'span[tracetest.span.type="database"]', + assertions: [ + { + left: 'attr:db.system', + comparator: '!=', + right: '""', + }, + ], +}; + +export const TEST_SPEC_SNIPPETS: TSnippet[] = [ + HTTP_SPANS_STATUS_CODE, + GRPC_SPANS_STATUS_CODE, + TRIGGER_SPAN_RESPONSE_TIME, + TRIGGER_SPAN_RESPONSE_BODY_CONTAINS, + DB_SPANS_RESPONSE_TIME, + DB_SPANS_QUALITY_DB_STATEMENT_PRESENT, +];