diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js index 1595311961..9d864e1743 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js @@ -16,7 +16,7 @@ import React from 'react'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { createBlob, UnconnectedSearchResults as SearchResults, SelectSort } from '.'; +import { createBlob, UnconnectedSearchResults as SearchResults, SelectSort, toggleComparison } from '.'; import * as track from './index.track'; import * as orderBy from '../../../model/order-by'; import readJsonFile from '../../../utils/readJsonFile'; @@ -36,7 +36,27 @@ jest.mock('./DiffSelection', () => jest.fn(({ traces }) =>
{traces.length}
) ); -jest.mock('./ResultItem', () => jest.fn(({ trace }) =>
)); +jest.mock('./ResultItem', () => + jest.fn(({ trace, toggleComparison }) => ( +
+
+ + +
+ )) +); jest.mock('./ScatterPlot', () => jest.fn(props =>
)); @@ -132,18 +152,43 @@ describe('', () => { expect(screen.queryByTestId('diffselection')).not.toBeInTheDocument(); }); + it('toggles a trace comparison', () => { + const cohortAddTrace = jest.fn(); + const cohortRemoveTrace = jest.fn(); + render( + + ); + + fireEvent.click(screen.getByTestId('toggle-add-a')); + expect(cohortAddTrace).toHaveBeenCalledWith('a'); + expect(cohortRemoveTrace).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('toggle-remove-b')); + expect(cohortRemoveTrace).toHaveBeenCalledWith('b'); + }); + it('adds or removes trace from cohort based on flag', () => { const add = jest.fn(); const remove = jest.fn(); - const instance = new SearchResults({ - ...baseProps, - cohortAddTrace: add, - cohortRemoveTrace: remove, - }); - instance.toggleComparison('id-1'); - instance.toggleComparison('id-2', true); - expect(add).toHaveBeenCalledWith('id-1'); - expect(remove).toHaveBeenCalledWith('id-2'); + + render(); + + // Simulate adding and removing traces + toggleComparison( + { + cohortAddTrace: add, + cohortRemoveTrace: remove, + }, + 'id-1' + ); + toggleComparison( + { + cohortAddTrace: add, + cohortRemoveTrace: remove, + }, + 'id-2', + true + ); }); it('sets trace color to red if error tag is present', () => { @@ -388,3 +433,12 @@ describe('', () => { }); }); }); + +describe('SearchResults exported functions', () => { + it('createBlob should create a blob with the correct data', () => { + const traces = [{ traceID: 'trace1' }]; + const blob = createBlob(traces); + expect(blob).toBeInstanceOf(Blob); + expect(blob.type).toBe('application/json'); + }); +}); diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.tsx b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.tsx index e8f6b24080..6a1202028c 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.tsx +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.tsx @@ -73,7 +73,6 @@ type SelectSortProps = { }; const Option = Select.Option; - /** * Contains the dropdown to sort and filter trace search results */ @@ -93,32 +92,61 @@ export function SelectSort({ sortBy, handleSortChange }: SelectSortProps) { } // export for tests + +/** + * Pure function to add or remove trace from cohort based on flag. + */ +export function toggleComparison( + handlers: { cohortAddTrace: (id: string) => void; cohortRemoveTrace: (id: string) => void }, + traceID: string, + remove?: boolean +) { + if (remove) { + handlers.cohortRemoveTrace(traceID); + } else { + handlers.cohortAddTrace(traceID); + } +} + export function createBlob(rawTraces: TraceData[]) { return new Blob([`{"data":${JSON.stringify(rawTraces)}}`], { type: 'application/json' }); } -export class UnconnectedSearchResults extends React.PureComponent { - static defaultProps = { skipMessage: false, spanLinks: undefined, queryOfResults: undefined }; +export function UnconnectedSearchResults(props: SearchResultsProps) { + const { + cohortAddTrace, + cohortRemoveTrace, + diffCohort, + disableComparisons, + goToTrace, + hideGraph, + history, + loading, + location, + maxTraceDuration, + queryOfResults, + showStandaloneLink, + skipMessage = false, + spanLinks = undefined, + traces, + sortBy, + handleSortChange, + } = props; - toggleComparison = (traceID: string, remove?: boolean) => { - const { cohortAddTrace, cohortRemoveTrace } = this.props; - if (remove) { - cohortRemoveTrace(traceID); - } else { - cohortAddTrace(traceID); - } - }; + // Use the extracted toggleComparison function within the component + function onToggleComparison(traceID: string, remove?: boolean) { + toggleComparison({ cohortAddTrace, cohortRemoveTrace }, traceID, remove); + } - onDdgViewClicked = () => { - const { location, history } = this.props; + const onDdgViewClicked = React.useCallback(() => { const urlState = queryString.parse(location.search); const view = urlState.view && urlState.view === 'ddg' ? EAltViewActions.Traces : EAltViewActions.Ddg; trackAltView(view); history.push(getUrl({ ...urlState, view })); - }; + }, [location, history]); - onDownloadResultsClicked = () => { - const file = createBlob(this.props.rawTraces); + const onDownloadResultsClicked = React.useCallback(() => { + const file = createBlob(props.rawTraces); const element = document.createElement('a'); element.href = URL.createObjectURL(file); element.download = `traces-${Date.now()}.json`; @@ -126,130 +154,109 @@ export class UnconnectedSearchResults extends React.PureComponent + }, [props.rawTraces]); + + const traceResultsView = queryString.parse(location.search).view !== 'ddg'; + const diffSelection = !disableComparisons && ( + + ); + + if (loading) { + return ( + + {diffCohort.length > 0 && diffSelection} + + ); - if (loading) { - return ( - - {diffCohort.length > 0 && diffSelection} - - - ); - } - if (!Array.isArray(traces) || !traces.length) { - return ( - - {diffCohort.length > 0 && diffSelection} - {!skipMessage && ( -
- No trace results. Try another query. -
- )} -
- ); - } - const cohortIds = new Set(diffCohort.map(datum => datum.id)); - const searchUrl = queryOfResults ? getUrl(stripEmbeddedState(queryOfResults)) : getUrl(); - const isErrorTag = ({ key, value }: KeyValuePair) => - key === 'error' && (value === true || value === 'true'); + } + if (!Array.isArray(traces) || !traces.length) { return ( -
-
- {!hideGraph && traceResultsView && ( -
- { - const rootSpanInfo = - t.spans && t.spans.length > 0 ? getTracePageHeaderParts(t.spans) : null; - return { - x: t.startTime, - y: t.duration, - traceID: t.traceID, - size: t.spans.length, - name: t.traceName, - color: t.spans.some(sp => sp.tags.some(isErrorTag)) ? 'red' : '#12939A', - services: t.services || [], - rootSpanName: rootSpanInfo?.operationName || 'Unknown', - }; - })} - onValueClick={(t: Trace) => { - goToTrace(t.traceID); - }} - /> -
- )} -
-

- {traces.length} Trace{traces.length > 1 && 's'} -

- {traceResultsView && } - {traceResultsView && } - - {showStandaloneLink && ( - - - - )} -
-
- {!traceResultsView && ( -
- + + {diffCohort.length > 0 && diffSelection} + {!skipMessage && ( +
+ No trace results. Try another query.
)} - {traceResultsView && diffSelection} - {traceResultsView && ( -
    - {traces.map(trace => ( -
  • - -
  • - ))} -
- )} -
+ ); } + const cohortIds = new Set(diffCohort.map(datum => datum.id)); + const searchUrl = queryOfResults ? getUrl(stripEmbeddedState(queryOfResults)) : getUrl(); + const isErrorTag = ({ key, value }: KeyValuePair) => + key === 'error' && (value === true || value === 'true'); + return ( +
+
+ {!hideGraph && traceResultsView && ( +
+ { + const rootSpanInfo = t.spans && t.spans.length > 0 ? getTracePageHeaderParts(t.spans) : null; + return { + x: t.startTime, + y: t.duration, + traceID: t.traceID, + size: t.spans.length, + name: t.traceName, + color: t.spans.some(sp => sp.tags.some(isErrorTag)) ? 'red' : '#12939A', + services: t.services || [], + rootSpanName: rootSpanInfo?.operationName || 'Unknown', + }; + })} + onValueClick={(t: Trace) => { + goToTrace(t.traceID); + }} + /> +
+ )} +
+

+ {traces.length} Trace{traces.length > 1 && 's'} +

+ {traceResultsView && } + {traceResultsView && } + + {showStandaloneLink && ( + + + + )} +
+
+ {!traceResultsView && ( +
+ +
+ )} + {traceResultsView && diffSelection} + {traceResultsView && ( +
    + {traces.map(trace => ( +
  • + +
  • + ))} +
+ )} +
+ ); } export default withRouteProps(UnconnectedSearchResults);