Skip to content

Commit

Permalink
chore(frontend): FE Improvements for the Test View (#3613)
Browse files Browse the repository at this point in the history
* adding async functionality

* chore(frontend): DAG improvements

* chore(frontend): DAG improvements

* chore(frontend): fixing spinner styles

* chore(frontend): fixing spinner styles

* feat(frontend): fixing tests

* chore(frontend): fixing spinner styles

* chore(frontend): FE Improvements for the Test View

* chore(frontend): reverting editor changes

* chore(frontend): Adding memoization for useComplete hook

* chore(frontend): Adding Search Service usage

* chore(frontend): cleanup
  • Loading branch information
xoscar authored Feb 8, 2024
1 parent 2471d30 commit 2f025cf
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 51 deletions.
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

0 comments on commit 2f025cf

Please sign in to comment.