Skip to content

Commit

Permalink
1503 [Transactions] Allow users create outputs from attributes (#1508)
Browse files Browse the repository at this point in the history
1503 allowing create output from attribute row
  • Loading branch information
xoscar committed Nov 16, 2022
1 parent bbb9a31 commit 8cd0be2
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 96 deletions.
4 changes: 3 additions & 1 deletion web/src/components/AttributeList/AttributeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ interface IProps {
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
searchText?: string;
semanticConventions: OtelReference;
onCreateOutput(attribute: TSpanFlatAttribute): void;
}

const AttributeList = ({assertions, attributeList, onCreateTestSpec, searchText, semanticConventions}: IProps) => {
const AttributeList = ({assertions, attributeList, onCreateTestSpec, onCreateOutput, searchText, semanticConventions}: IProps) => {
const onCopy = (value: string) => {
TraceAnalyticsService.onAttributeCopy();
navigator.clipboard.writeText(value);
Expand All @@ -31,6 +32,7 @@ const AttributeList = ({assertions, attributeList, onCreateTestSpec, searchText,
key={attribute.key}
onCopy={onCopy}
onCreateTestSpec={onCreateTestSpec}
onCreateOutput={onCreateOutput}
semanticConventions={semanticConventions}
/>
))}
Expand Down
11 changes: 11 additions & 0 deletions web/src/components/AttributeRow/AttributeRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ interface IProps {
searchText?: string;
onCopy(value: string): void;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
onCreateOutput(attribute: TSpanFlatAttribute): void;
semanticConventions: OtelReference;
}

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

const AttributeRow = ({
Expand All @@ -36,6 +38,7 @@ const AttributeRow = ({
onCreateTestSpec,
searchText,
semanticConventions,
onCreateOutput,
}: IProps) => {
const passedCount = assertionsPassed?.length ?? 0;
const failedCount = assertionsFailed?.length ?? 0;
Expand All @@ -52,6 +55,10 @@ const AttributeRow = ({
if (option === Action.Create_Spec) {
return onCreateTestSpec(attribute);
}

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

const cypressKey = key.toLowerCase().replace('.', '-');
Expand All @@ -63,6 +70,10 @@ const AttributeRow = ({
label: 'Copy value',
key: Action.Copy,
},
{
label: 'Create Output',
key: Action.Create_Output,
},
{
label: 'Create test spec',
key: Action.Create_Spec,
Expand Down
8 changes: 3 additions & 5 deletions web/src/components/OutputModal/OutputModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {Form} from 'antd';
import {useEffect} from 'react';

import {useAppSelector} from 'redux/hooks';
import {selectTestOutputByIndex} from 'redux/testOutputs/selectors';
import SpanSelectors from 'selectors/Span.selectors';
import {TTestOutput} from 'types/TestOutput.types';
import useValidateOutput from './hooks/useValidateOutput';
Expand All @@ -11,20 +10,19 @@ import OutputModalFooter from './OutputModalFooter';
import OutputModalForm from './OutputModalForm';

interface IProps {
index: number;
isOpen: boolean;
onClose(): void;
onSubmit(values: TTestOutput, isEditing: boolean): void;
runId: string;
testId: string;
output?: TTestOutput;
isEditing?: boolean;
}

const OutputModal = ({index, isOpen, onClose, onSubmit, runId, testId}: IProps) => {
const OutputModal = ({isOpen, onClose, onSubmit, runId, testId, output, isEditing = false}: IProps) => {
const [form] = Form.useForm<TTestOutput>();
const output = useAppSelector(state => selectTestOutputByIndex(state, index));
const spanIdList = useAppSelector(SpanSelectors.selectMatchedSpans);
const {isValid, onValidate} = useValidateOutput({spanIdList});
const isEditing = index !== -1;

useEffect(() => {
if (output && isOpen) form.setFieldsValue(output);
Expand Down
1 change: 0 additions & 1 deletion web/src/components/RunDetailLayout/RunDetailLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Tabs, TabsProps} from 'antd';
import {useMemo} from 'react';
import {useNavigate, useParams} from 'react-router-dom';

import RunDetailTest from 'components/RunDetailTest';
import RunDetailTrace from 'components/RunDetailTrace';
import RunDetailTrigger from 'components/RunDetailTrigger';
Expand Down
88 changes: 14 additions & 74 deletions web/src/components/RunDetailTriggerResponse/ResponseOutputs.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import {PlusOutlined} from '@ant-design/icons';
import {Button} from 'antd';
import React, {useCallback, useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom';

import OutputModal from 'components/OutputModal/OutputModal';
import OutputRow from 'components/OutputRow';
import {toRawTestOutputs} from 'models/TestOutput.model';
import {useConfirmationModal} from 'providers/ConfirmationModal/ConfirmationModal.provider';
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import {useSetTestOutputsMutation, useReRunMutation} from 'redux/apis/TraceTest.api';
import {selectTestOutputs, selectIsPending} from 'redux/testOutputs/selectors';
import {outputsInitiated, outputAdded, outputUpdated, outputDeleted, outputsReverted} from 'redux/testOutputs/slice';
import {useTest} from 'providers/Test/Test.provider';
import {useTestRun} from 'providers/TestRun/TestRun.provider';
import {TTestOutput} from 'types/TestOutput.types';
import {useAppSelector} from 'redux/hooks';
import {useTestOutput} from 'providers/TestOutput/TestOutput.provider';
import * as S from './RunDetailTriggerResponse.styled';
import SkeletonResponse from './SkeletonResponse';

Expand All @@ -22,76 +15,32 @@ const ResponseOutputs = () => {
run: {id: runId},
} = useTestRun();
const {
test: {id: testId, outputs: testOutputs},
test: {id: testId},
} = useTest();

const dispatch = useAppDispatch();
const outputs = useAppSelector(state => selectTestOutputs(state, testId, runId));
const isPending = useAppSelector(selectIsPending);
const [setTestOutputs, {isLoading}] = useSetTestOutputsMutation();
const [reRunTest, {isLoading: isLoadingReRub}] = useReRunMutation();
const [selectedIndex, setSelectedIndex] = useState(-1);
const [isModalOpen, setIsModalOpen] = useState(false);
const {onOpen} = useConfirmationModal();
const navigate = useNavigate();

useEffect(() => {
dispatch(outputsInitiated(testOutputs || []));
}, [dispatch, testOutputs]);

const handleOnDelete = useCallback(
(index: number) => {
onOpen(`Are you sure you want to delete the output?`, () => {
dispatch(outputDeleted(index));
});
},
[dispatch, onOpen]
);

const handleOnEdit = useCallback((index: number) => {
setSelectedIndex(index);
setIsModalOpen(true);
}, []);

const handleOnSubmit = useCallback(
(values: TTestOutput, isEditing: boolean) => {
setIsModalOpen(false);
if (isEditing) {
dispatch(outputUpdated({index: selectedIndex, output: values}));
return;
}
dispatch(outputAdded(values));
},
[dispatch, selectedIndex]
);

const handleOnCancel = useCallback(() => {
dispatch(outputsReverted());
}, [dispatch]);

const handleOnSave = useCallback(async () => {
await setTestOutputs({testId, testOutputs: toRawTestOutputs(outputs)}).unwrap();
const run = await reRunTest({testId, runId}).unwrap();
navigate(`/test/${testId}/run/${run.id}/trigger`);
}, [navigate, outputs, reRunTest, runId, setTestOutputs, testId]);
const {onModalOpenFromIndex, onDelete, onCancel, onSave, isLoading, onModalOpen} = useTestOutput();

return !outputs ? (
<SkeletonResponse />
) : (
<>
<S.HeadersList>
{outputs.map((output, index) => (
<OutputRow index={index} key={output.name} output={output} onDelete={handleOnDelete} onEdit={handleOnEdit} />
<OutputRow
index={index}
key={output.name}
output={output}
onDelete={onDelete}
onEdit={onModalOpenFromIndex}
/>
))}
</S.HeadersList>

<div>
<Button
icon={<PlusOutlined />}
onClick={() => {
setIsModalOpen(true);
setSelectedIndex(-1);
}}
onClick={() => onModalOpen()}
style={{fontWeight: 600, height: 'auto', padding: 0}}
type="link"
>
Expand All @@ -101,23 +50,14 @@ const ResponseOutputs = () => {

{isPending && (
<S.Actions>
<Button ghost loading={isLoading || isLoadingReRub} onClick={handleOnCancel} type="primary">
<Button ghost loading={isLoading} onClick={onCancel} type="primary">
Reset
</Button>
<Button loading={isLoading || isLoadingReRub} onClick={handleOnSave} type="primary">
<Button loading={isLoading} onClick={() => onSave(outputs)} type="primary">
Save & Run
</Button>
</S.Actions>
)}

<OutputModal
index={selectedIndex}
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onSubmit={handleOnSubmit}
runId={runId}
testId={testId}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Tabs} from 'antd';
import {useSearchParams} from 'react-router-dom';
import {TriggerTypes} from 'constants/Test.constants';
import {TestState} from 'constants/TestRun.constants';
import TestRunAnalyticsService from 'services/Analytics/TestRunAnalytics.service';
Expand All @@ -21,6 +22,12 @@ interface IProps {
triggerTime?: number;
}

const TabsKeys = {
Body: 'body',
Headers: 'headers',
Outputs: 'outputs',
};

const RunDetailTriggerResponse = ({
state,
triggerTime = 0,
Expand All @@ -31,6 +38,8 @@ const RunDetailTriggerResponse = ({
bodyMimeType: '',
},
}: IProps) => {
const [query, updateQuery] = useSearchParams();

return (
<S.Container data-tour={GuidedTourService.getStep(GuidedTours.Trace, Steps.Graph)}>
<S.TitleContainer>
Expand All @@ -49,19 +58,22 @@ const RunDetailTriggerResponse = ({
</S.TitleContainer>
<S.TabsContainer>
<Tabs
defaultActiveKey="1"
defaultActiveKey={query.get('tab') || TabsKeys.Body}
data-cy="run-detail-trigger-response"
size="small"
onChange={newTab => TestRunAnalyticsService.onTriggerResponseTabChange(newTab)}
onChange={newTab => {
TestRunAnalyticsService.onTriggerResponseTabChange(newTab);
updateQuery([['tab', newTab]]);
}}
>
<Tabs.TabPane key="1" tab="Body">
<Tabs.TabPane key={TabsKeys.Body} tab="Body">
<ResponseBody body={body} bodyMimeType={bodyMimeType} />
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="Headers">
<Tabs.TabPane key={TabsKeys.Headers} tab="Headers">
<ResponseHeaders headers={headers} />
</Tabs.TabPane>
{isTransactionsEnabled && (
<Tabs.TabPane key="3" tab="Outputs">
<Tabs.TabPane key={TabsKeys.Outputs} tab="Outputs">
<ResponseOutputs />
</Tabs.TabPane>
)}
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/SpanDetail/Attributes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ interface IProps {
attributeList: TSpanFlatAttribute[];
searchText?: string;
onCreateTestSpec(attribute: TSpanFlatAttribute): void;
onCreateOutput(attribute: TSpanFlatAttribute): void;
semanticConventions: OtelReference;
}

const Attributes = ({assertions, attributeList, onCreateTestSpec, searchText, semanticConventions}: IProps) => {
const Attributes = ({assertions, attributeList, onCreateTestSpec, onCreateOutput, searchText, semanticConventions}: IProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const [topPosition, setTopPosition] = useState(0);

Expand All @@ -28,6 +29,7 @@ const Attributes = ({assertions, attributeList, onCreateTestSpec, searchText, se
assertions={assertions}
attributeList={attributeList}
onCreateTestSpec={onCreateTestSpec}
onCreateOutput={onCreateOutput}
searchText={searchText}
semanticConventions={semanticConventions}
/>
Expand Down
20 changes: 20 additions & 0 deletions web/src/components/SpanDetail/SpanDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import AssertionService from 'services/Assertion.service';
import SpanService from 'services/Span.service';
import SpanAttributeService from 'services/SpanAttribute.service';
import {TSpan, TSpanFlatAttribute} from 'types/Span.types';
import {useTestOutput} from 'providers/TestOutput/TestOutput.provider';
import TestOutput from 'models/TestOutput.model';
import Attributes from './Attributes';
import Header from './Header';
import * as S from './SpanDetail.styled';
Expand All @@ -24,6 +26,7 @@ interface IProps {

const SpanDetail = ({onCreateTestSpec = noop, searchText, span}: IProps) => {
const {open} = useTestSpecForm();
const {onNavigateAndOpenModal} = useTestOutput();
const spansResult = useAppSelector(TestSpecsSelectors.selectSpansResult);
const assertions = useAppSelector(state => TestSpecsSelectors.selectAssertionResultsBySpan(state, span?.id || ''));
const [search, setSearch] = useState('');
Expand Down Expand Up @@ -54,6 +57,22 @@ const SpanDetail = ({onCreateTestSpec = noop, searchText, span}: IProps) => {
[onCreateTestSpec, open, span]
);

const handleCreateOutput = useCallback(
({key}: TSpanFlatAttribute) => {
TraceAnalyticsService.onAddAssertionButtonClick();
const selector = SpanService.getSelectorInformation(span!);

const output = TestOutput({
selector: {query: selector},
name: key,
value: `attr:${key}`,
});

onNavigateAndOpenModal(output);
},
[onNavigateAndOpenModal, span]
);

const handleOnSearch = useCallback((value: string) => {
setSearch(value);
}, []);
Expand All @@ -80,6 +99,7 @@ const SpanDetail = ({onCreateTestSpec = noop, searchText, span}: IProps) => {
assertions={assertions}
attributeList={filteredAttributes}
onCreateTestSpec={handleCreateTestSpec}
onCreateOutput={handleCreateOutput}
searchText={searchText}
semanticConventions={semanticConventions}
/>
Expand Down
2 changes: 1 addition & 1 deletion web/src/models/Test.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Test = ({
definition: TestSpecs(specs || {}),
trigger: Trigger(rawTrigger || {}),
summary: TestSummary(summary),
outputs: outputs?.map(rawOutput => TestOutput(rawOutput)),
outputs: outputs?.map((rawOutput, index) => TestOutput(rawOutput, index)),
});

export default Test;
3 changes: 2 additions & 1 deletion web/src/models/TestOutput.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {TRawTestOutput, TRawTestRunOutput, TTestOutput, TTestRunOutput} from 'types/TestOutput.types';

function TestOutput({name = '', selector = {}, value = ''}: TRawTestOutput): TTestOutput {
function TestOutput({name = '', selector = {}, value = ''}: TRawTestOutput, id = -1): TTestOutput {
return {
id,
isDeleted: false,
isDraft: false,
name,
Expand Down

0 comments on commit 8cd0be2

Please sign in to comment.