Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(frontend): FE Improvements for the Test View #3613

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {useCallback} from 'react';
import {noop, uniqBy} from 'lodash';
import {noop} from 'lodash';
import {Completion, CompletionContext} from '@codemirror/autocomplete';
import {useAppStore} from 'redux/hooks';
import AssertionSelectors from 'selectors/Assertion.selectors';
import VariableSetSelectors from 'selectors/VariableSet.selectors';
import SpanSelectors from 'selectors/Span.selectors';
import {selectExpressionAttributeList} from 'selectors/Editor.selectors';
import EditorService from 'services/Editor.service';
import {SupportedEditors} from 'constants/Editor.constants';

Expand All @@ -18,14 +17,10 @@ interface IProps {
const useAutoComplete = ({testId, runId, onSelect = noop, autocompleteCustomValues}: IProps) => {
const {getState} = useAppStore();

const getAttributeList = useCallback(() => {
const state = getState();
const spanIdList = SpanSelectors.selectMatchedSpans(state);
// TODO: this list is calculated multiple times while typing, we should memoize it
const attributeList = AssertionSelectors.selectAttributeList(state, testId, runId, spanIdList);

return uniqBy(attributeList, 'key');
}, [getState, runId, testId]);
const getAttributeList = useCallback(
() => selectExpressionAttributeList(getState(), testId, runId),
[getState, runId, testId]
);

const getSelectedVariableSetEntryList = useCallback(() => {
const state = getState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
Tokens,
} from 'constants/Editor.constants';
import {useAppStore} from 'redux/hooks';
import AssertionSelectors from 'selectors/Assertion.selectors';
import {escapeString} from 'utils/Common';
import {selectSelectorAttributeList} from 'selectors/Editor.selectors';

interface IProps {
testId: string;
Expand All @@ -22,13 +22,10 @@ interface IProps {
const useAutoComplete = ({testId, runId}: IProps) => {
const {getState} = useAppStore();

const getAttributeList = useCallback(() => {
const state = getState();
// TODO: this list is calculated multiple times while typing, we should memoize it
const defaultList = AssertionSelectors.selectAllAttributeList(state, testId, runId);

return defaultList;
}, [getState, runId, testId]);
const getAttributeList = useCallback(
() => selectSelectorAttributeList(getState(), testId, runId),
[getState, runId, testId]
);

return useCallback(
async (context: CompletionContext) => {
Expand Down
7 changes: 3 additions & 4 deletions web/src/components/RunDetailTest/Visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ export interface IProps {
trace: Trace;
}

const Visualization = ({isDAGDisabled, runEvents, runState, trace, trace: {spans}, type}: IProps) => {
const Visualization = ({isDAGDisabled, runEvents, runState, trace, trace: {spans, rootSpan}, type}: IProps) => {
const {onSelectSpan, matchedSpans, onSetFocusedSpan, selectedSpan} = useSpan();

const {isOpen} = useTestSpecForm();

useEffect(() => {
if (selectedSpan) return;
const firstSpan = spans.find(span => !span.parentId);
onSelectSpan(firstSpan?.id ?? '');
}, [onSelectSpan, selectedSpan, spans]);
onSelectSpan(rootSpan.id);
}, [onSelectSpan, rootSpan, selectedSpan, spans]);

const onNodeClickTimeline = useCallback(
(spanId: string) => {
Expand Down
27 changes: 7 additions & 20 deletions web/src/components/RunDetailTrace/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ import {useCallback, useMemo, useState} from 'react';

import {Editor} from 'components/Inputs';
import {SupportedEditors} from 'constants/Editor.constants';
import {useTestRun} from 'providers/TestRun/TestRun.provider';
import TracetestAPI from 'redux/apis/Tracetest';
import {useAppDispatch, useAppSelector} from 'redux/hooks';
import {matchSpans, selectSpan, setSearchText} from 'redux/slices/Trace.slice';
import TraceSelectors from 'selectors/Trace.selectors';
import SpanService from 'services/Span.service';
import EditorService from 'services/Editor.service';
import * as S from './RunDetailTrace.styled';

const {useLazyGetSelectedSpansQuery} = TracetestAPI.instance;
const {useGetSearchedSpansMutation} = TracetestAPI.instance;

interface IProps {
runId: number;
Expand All @@ -25,35 +22,25 @@ const Search = ({runId, testId}: IProps) => {
const [search, setSearch] = useState('');
const dispatch = useAppDispatch();
const matchedSpans = useAppSelector(TraceSelectors.selectMatchedSpans);
const {
run: {trace: {spans = []} = {}},
} = useTestRun();
const [getSelectedSpans] = useLazyGetSelectedSpansQuery();
const [getSearchedSpans] = useGetSearchedSpansMutation();

const handleSearch = useCallback(
async (query: string) => {
const isValidSelector = EditorService.getIsQueryValid(SupportedEditors.Selector, query || '');
if (!query) {
dispatch(matchSpans({spanIds: []}));
dispatch(selectSpan({spanId: ''}));
return;
}

let spanIds = [];
if (isValidSelector) {
const selectedSpansData = await getSelectedSpans({query, runId, testId}).unwrap();
spanIds = selectedSpansData.spanIds;
} else {
dispatch(setSearchText({searchText: query}));
spanIds = SpanService.searchSpanList(spans, query);
}

const {spanIds} = await getSearchedSpans({query, runId, testId}).unwrap();
dispatch(setSearchText({searchText: query}));
dispatch(matchSpans({spanIds}));

if (spanIds.length) {
dispatch(selectSpan({spanId: spanIds[0]}));
}
},
[dispatch, getSelectedSpans, runId, spans, testId]
[dispatch, getSearchedSpans, runId, testId]
);

const onSearch = useMemo(() => debounce(handleSearch, 500), [handleSearch]);
Expand All @@ -67,7 +54,7 @@ const Search = ({runId, testId}: IProps) => {
<Col flex="auto">
<Editor
type={SupportedEditors.Selector}
placeholder="Search in trace"
placeholder={'Try `span[tracetest.span.type="general" name="Tracetest trigger"]` or just "Tracetest trigger"'}
onChange={query => {
onSearch(query);
setSearch(query);
Expand Down
1 change: 0 additions & 1 deletion web/src/components/RunDetailTrace/TraceDAG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const TraceDAG = ({trace: {spans}, onNavigateToSpan}: IProps) => {
const isMatchedMode = Boolean(matchedSpans.length);
const dispatch = useAppDispatch();

// TODO: Trace will never change, we can calculate this once and then keep using it
useEffect(() => {
dispatch(initNodes({spans}));
}, [dispatch, spans]);
Expand Down
1 change: 0 additions & 1 deletion web/src/models/DAG.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function getNodesDatumFromSpans(spans: Span[], type: NodeTypesEnum): INodeDatum<
}

function DAG(spans: Span[], type: NodeTypesEnum) {
// TODO: this runs twice for the list of spans
const nodesDatum = getNodesDatumFromSpans(spans, type).sort((a, b) => {
if (b.data.startTime !== a.data.startTime) return b.data.startTime - a.data.startTime;
if (b.id < a.id) return -1;
Expand Down
22 changes: 22 additions & 0 deletions web/src/models/SearchSpansResult.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Model, TTestSchemas} from '../types/Common.types';

export type TRawSearchSpansResult = TTestSchemas['SearchSpansResult'];
type SearchSpansResult = Model<
TRawSearchSpansResult,
{
spanIds: string[];
spansIds?: undefined;
}
>;

const defaultSearchSpansResult: TRawSearchSpansResult = {
spansIds: [],
};

function SearchSpansResult({spansIds = []} = defaultSearchSpansResult): SearchSpansResult {
return {
spanIds: spansIds,
};
}

export default SearchSpansResult;
9 changes: 9 additions & 0 deletions web/src/redux/apis/Tracetest/endpoints/TestRun.endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import SelectedSpans, {TRawSelectedSpans} from 'models/SelectedSpans.model';
import Test from 'models/Test.model';
import TestRun, {TRawTestRun} from 'models/TestRun.model';
import TestRunEvent, {TRawTestRunEvent} from 'models/TestRunEvent.model';
import SearchSpansResult, {TRawSearchSpansResult} from 'models/SearchSpansResult.model';
import {KnownSources} from 'models/RunMetadata.model';
import {TRawTestSpecs} from 'models/TestSpecs.model';
import {TTestApiEndpointBuilder} from '../Tracetest.api';
Expand Down Expand Up @@ -113,6 +114,14 @@ export const testRunEndpoints = (builder: TTestApiEndpointBuilder) => ({
providesTags: (result, error, {query}) => (result ? [{type: TracetestApiTags.SPAN, id: `${query}-LIST`}] : []),
transformResponse: (rawSpanList: TRawSelectedSpans) => SelectedSpans(rawSpanList),
}),
getSearchedSpans: builder.mutation<SearchSpansResult, {query: string; testId: string; runId: number}>({
query: ({query, testId, runId}) => ({
url: `/tests/${testId}/run/${runId}/search-spans`,
method: HTTP_METHOD.POST,
body: JSON.stringify({query}),
}),
transformResponse: (raw: TRawSearchSpansResult) => SearchSpansResult(raw),
}),

getRunEvents: builder.query<TestRunEvent[], {runId: number; testId: string}>({
query: ({runId, testId}) => `/tests/${testId}/run/${runId}/events`,
Expand Down
2 changes: 2 additions & 0 deletions web/src/redux/apis/Tracetest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const {
useLazyTestOtlpConnectionQuery,
useTestOtlpConnectionQuery,
useResetTestOtlpConnectionMutation,
useGetSearchedSpansMutation,

endpoints,
} = TracetestAPI.instance;
Expand Down Expand Up @@ -129,5 +130,6 @@ export {
useLazyTestOtlpConnectionQuery,
useTestOtlpConnectionQuery,
useResetTestOtlpConnectionMutation,
useGetSearchedSpansMutation,
endpoints,
};
2 changes: 1 addition & 1 deletion web/src/selectors/Assertion.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const selectMatchedSpanList = createSelector(stateSelector, paramsSelector, (sta
const {data: {trace} = {}} = TracetestAPI.instance.endpoints.getRunById.select({testId, runId})(state);
if (!spanIdList.length) return trace?.spans || [];

return trace?.spans.filter(({id}) => spanIdList.includes(id)) || [];
return spanIdList.map((spanId) => trace!.flat[spanId]);
});

const AssertionSelectors = () => {
Expand Down
26 changes: 26 additions & 0 deletions web/src/selectors/Editor.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {uniqBy} from 'lodash';
import {createSelector} from '@reduxjs/toolkit';
import {RootState} from 'redux/store';
import AssertionSelectors from './Assertion.selectors';
import SpanSelectors from './Span.selectors';

const stateSelector = (state: RootState) => state;
const paramsSelector = (state: RootState, testId: string, runId: number) => ({
testId,
runId,
});

export const selectSelectorAttributeList = createSelector(stateSelector, paramsSelector, (state, {testId, runId}) =>
AssertionSelectors.selectAllAttributeList(state, testId, runId)
);

export const selectExpressionAttributeList = createSelector(
stateSelector,
paramsSelector,
SpanSelectors.selectMatchedSpans,
(state, {testId, runId}, spanIds) => {
const attributeList = AssertionSelectors.selectAttributeList(state, testId, runId, spanIds);

return uniqBy(attributeList, 'key');
}
);
5 changes: 1 addition & 4 deletions web/src/selectors/Span.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ const SpanSelectors = () => ({
selectMatchedSpans,
selectSpanById: createSelector(stateSelector, paramsSelector, (state, {spanId, testId, runId}) => {
const {data: {trace} = {}} = TracetestAPI.instance.endpoints.getRunById.select({testId, runId})(state);

// TODO: look for a simpler way of getting the span by id
const spanList = trace?.spans || [];

return spanList.find(span => span.id === spanId);
return trace?.flat[spanId];
}),
selectSelectedSpan: createSelector(spansStateSelector, ({selectedSpan}) => selectedSpan),
selectFocusedSpan: createSelector(spansStateSelector, ({focusedSpan}) => focusedSpan),
Expand Down
1 change: 0 additions & 1 deletion web/src/selectors/TestRun.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const selectTestRun = (state: RootState, params: {testId: string; runId: number;
return data ?? TestRun({});
};

// TODO: look for a simpler way of getting the span by id
export const selectSpanById = createSelector([selectTestRun, selectParams], (testRun, params) => {
const {trace} = testRun;
return trace.flat[params.spanId] || Span({id: params.spanId});
Expand Down
Loading