diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent.tsx b/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent.tsx index 1ede00c53c..d1fefd2561 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent.tsx @@ -36,7 +36,7 @@ type TProps = { vertexKey: string; }; -// While browsers suport URLs of unlimited length, many server clients do not handle more than this max +// While browsers support URLs of unlimited length, many server clients do not handle more than this max const MAX_LENGTH = 2083; const MAX_LINKED_TRACES = 35; const MIN_LENGTH = getSearchUrl().length; @@ -55,13 +55,17 @@ export default class DdgNodeContent extends React.PureComponent { getVisiblePathElems: (vertexKey: string) => PathElem[] | undefined, setViewModifier: (vertexKey: string, viewModifier: EViewModifier, enable: boolean) => void, density: EDdgDensity, - showOp: boolean + showOp: boolean, + baseUrl: string, + extraUrlArgs: { [key: string]: unknown } | undefined ) { return function renderNode(vertex: TDdgVertex, utils: TRendererUtils, lv: TLayoutVertex | null) { const { isFocalNode, key, operation, service } = vertex; return ( | undefined; vertices: TDdgVertex[]; verticesViewModifiers: Map; + baseUrl: string; + extraUrlArgs?: { [key: string]: unknown }; }; // The dichotomy between w/ & w/o VMs assumes that any edge VM neccesitates unmodified edges are de-emphasized @@ -82,6 +84,8 @@ export default class Graph extends PureComponent { uiFindMatches, vertices, verticesViewModifiers, + baseUrl, + extraUrlArgs, } = this.props; const nodeRenderers = this.getNodeRenderers(uiFindMatches || this.emptyFindSet, verticesViewModifiers); @@ -134,7 +138,14 @@ export default class Graph extends PureComponent { layerType: 'html', measurable: true, measureNode: DdgNodeContent.measureNode, - renderNode: this.getNodeContentRenderer(getVisiblePathElems, setViewModifier, density, showOp), + renderNode: this.getNodeContentRenderer( + getVisiblePathElems, + setViewModifier, + density, + showOp, + baseUrl, + extraUrlArgs + ), }, ]} /> diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.tsx b/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.tsx index 5463cc5f31..956095764f 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.tsx @@ -22,6 +22,7 @@ type TProps = { distanceToPathElems?: TDdgDistanceToPathElems; handleClick: (distance: number, direction: EDirection) => void; visEncoding?: string; + extraUrlArgs?: { [key: string]: unknown }; }; export default memo(function HopsSelector({ distanceToPathElems, handleClick, visEncoding }: TProps) { diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx index cbb1ec0abe..49fe515e36 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx @@ -41,10 +41,16 @@ type TProps = { toggleShowOperations: (enable: boolean) => void; uiFindCount: number | undefined; visEncoding?: string; + showParameters?: boolean; + extraUrlArgs?: { [key: string]: unknown }; }; export default class Header extends React.PureComponent { private _uiFindInput: React.RefObject = React.createRef(); + static defaultProps = { + showParameters: true, + }; + focusUiFindInput = () => { if (this._uiFindInput.current) { this._uiFindInput.current.focus(); @@ -103,30 +109,34 @@ export default class Header extends React.PureComponent { showOperations, toggleShowOperations, visEncoding, + showParameters, + extraUrlArgs, } = this.props; return (
-
- - {service && ( + {showParameters && ( +
- )} -
+ {service && ( + + )} +
+ )}
{ distanceToPathElems={distanceToPathElems} handleClick={setDistance} visEncoding={visEncoding} + extraUrlArgs={extraUrlArgs} />
diff --git a/packages/jaeger-ui/src/components/DeepDependencies/index.test.js b/packages/jaeger-ui/src/components/DeepDependencies/index.test.js index 1de8724a93..58c4ee611f 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/index.test.js +++ b/packages/jaeger-ui/src/components/DeepDependencies/index.test.js @@ -104,7 +104,7 @@ describe('DeepDependencyGraphPage', () => { const value = `new ${propName}`; const kwarg = { [propName]: value }; ddgPageImpl.updateUrlState(kwarg); - expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, props.urlState, kwarg)); + expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, props.urlState, kwarg), undefined); expect(props.history.push).toHaveBeenCalledTimes(i + 1); }); }); @@ -115,7 +115,7 @@ describe('DeepDependencyGraphPage', () => { start: 'new start', }; ddgPageImpl.updateUrlState(kwarg); - expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, props.urlState, kwarg)); + expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, props.urlState, kwarg), undefined); expect(props.history.push).toHaveBeenCalledTimes(1); }); @@ -130,7 +130,7 @@ describe('DeepDependencyGraphPage', () => { }; const ddgPageWithFewerProps = new DeepDependencyGraphPageImpl(otherProps); ddgPageWithFewerProps.updateUrlState(kwarg); - expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, otherUrlState, kwarg)); + expect(getUrlSpy).toHaveBeenLastCalledWith(Object.assign({}, otherUrlState, kwarg), undefined); expect(getUrlSpy).not.toHaveBeenLastCalledWith(expect.objectContaining({ start: expect.anything() })); expect(props.history.push).toHaveBeenCalledTimes(1); }); @@ -172,7 +172,8 @@ describe('DeepDependencyGraphPage', () => { prevVisEncoding: visEncoding, }); expect(getUrlSpy).toHaveBeenLastCalledWith( - Object.assign({}, props.urlState, { visEncoding: mockNewEncoding }) + Object.assign({}, props.urlState, { visEncoding: mockNewEncoding }), + undefined ); expect(props.history.push).toHaveBeenCalledTimes(1); }); @@ -183,7 +184,8 @@ describe('DeepDependencyGraphPage', () => { const operation = 'newOperation'; ddgPageImpl.setOperation(operation); expect(getUrlSpy).toHaveBeenLastCalledWith( - Object.assign({}, props.urlState, { operation, visEncoding: undefined }) + Object.assign({}, props.urlState, { operation, visEncoding: undefined }), + undefined ); expect(props.history.push).toHaveBeenCalledTimes(1); }); @@ -199,7 +201,8 @@ describe('DeepDependencyGraphPage', () => { it('updates service and clears operation and visEncoding', () => { ddgPageImpl.setService(service); expect(getUrlSpy).toHaveBeenLastCalledWith( - Object.assign({}, props.urlState, { operation: undefined, service, visEncoding: undefined }) + Object.assign({}, props.urlState, { operation: undefined, service, visEncoding: undefined }), + undefined ); expect(props.history.push).toHaveBeenCalledTimes(1); }); diff --git a/packages/jaeger-ui/src/components/DeepDependencies/index.tsx b/packages/jaeger-ui/src/components/DeepDependencies/index.tsx index 5d638fdab2..32f061fde1 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/index.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/index.tsx @@ -20,7 +20,7 @@ import { connect } from 'react-redux'; import Header from './Header'; import Graph from './Graph'; -import { getUrl, getUrlState } from './url'; +import { getUrl, getUrlState, ROUTE_PATH } from './url'; import ErrorMessage from '../common/ErrorMessage'; import LoadingIndicator from '../common/LoadingIndicator'; import { extractUiFindFromState, TExtractUiFindFromStateReturn } from '../common/UiFindInput'; @@ -43,7 +43,7 @@ import { TDdgStateEntry } from '../../types/TDdgState'; import './index.css'; -type TDispatchProps = { +export type TDispatchProps = { addViewModifier: (kwarg: TDdgModelParams & { viewModifier: number; visibilityIndices: number[] }) => void; fetchDeepDependencyGraph: (query: TDdgModelParams) => void; fetchServices: () => void; @@ -53,7 +53,7 @@ type TDispatchProps = { ) => void; }; -type TReduxProps = TExtractUiFindFromStateReturn & { +export type TReduxProps = TExtractUiFindFromStateReturn & { graph: GraphModel | undefined; graphState?: TDdgStateEntry; operationsForService: Record; @@ -61,15 +61,23 @@ type TReduxProps = TExtractUiFindFromStateReturn & { urlState: TDdgSparseUrlState; }; -type TOwnProps = { +export type TOwnProps = { history: RouterHistory; location: Location; + showServicesOpsHeader: boolean; + baseUrl: string; + extraUrlArgs?: { [key: string]: unknown }; }; -type TProps = TDispatchProps & TReduxProps & TOwnProps; +export type TProps = TDispatchProps & TReduxProps & TOwnProps; // export for tests export class DeepDependencyGraphPageImpl extends React.PureComponent { + static defaultProps = { + showServicesOpsHeader: true, + baseUrl: ROUTE_PATH, + }; + static fetchModelIfStale(props: TProps) { const { fetchDeepDependencyGraph, graphState = null, urlState } = props; const { service, operation } = urlState; @@ -173,12 +181,22 @@ export class DeepDependencyGraphPageImpl extends React.PureComponent { toggleShowOperations = (enable: boolean) => this.updateUrlState({ showOp: enable }); updateUrlState = (newValues: Partial) => { - const { uiFind, urlState, history } = this.props; - history.push(getUrl({ uiFind, ...urlState, ...newValues })); + const { uiFind, urlState, history, baseUrl, extraUrlArgs } = this.props; + history.push(getUrl({ uiFind, ...urlState, ...newValues, ...extraUrlArgs }, baseUrl)); }; render() { - const { graph, graphState, operationsForService, services, uiFind, urlState } = this.props; + const { + graph, + graphState, + operationsForService, + services, + uiFind, + urlState, + showServicesOpsHeader, + baseUrl, + extraUrlArgs, + } = this.props; const { density, operation, service, showOp, visEncoding } = urlState; const distanceToPathElems = graphState && graphState.state === fetchedState.DONE ? graphState.model.distanceToPathElems : undefined; @@ -208,6 +226,8 @@ export class DeepDependencyGraphPageImpl extends React.PureComponent { uiFindMatches={uiFindMatches} vertices={vertices} verticesViewModifiers={verticesViewModifiers} + baseUrl={baseUrl} + extraUrlArgs={extraUrlArgs} /> ); } else if (graphState.state === fetchedState.LOADING) { @@ -227,6 +247,7 @@ export class DeepDependencyGraphPageImpl extends React.PureComponent {
): TDispatchProps { + const { fetchDeepDependencyGraph, fetchServiceOperations, fetchServices } = bindActionCreators( + jaegerApiActions, + dispatch + ); + const { addViewModifier, removeViewModifierFromIndices } = bindActionCreators(ddgActions, dispatch); + + return { + addViewModifier, + fetchDeepDependencyGraph, + fetchServiceOperations, + fetchServices, + removeViewModifierFromIndices, + }; +} + +export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxProps { + const { trace } = state; + const urlState = getUrlState(ownProps.location.search); + const { density, operation, service, showOp } = urlState; + let graphState: TDdgStateEntry | undefined; + let graph: GraphModel | undefined; + let payload: TDdgPayload; + if (service) { + payload = transformTracesToPaths(trace.traces, service, operation); + graphState = { + model: transformDdgData(payload, { service, operation }), + state: fetchedState.DONE, + viewModifiers: new Map(), + }; + graph = makeGraph(graphState.model, showOp, density); + } + + return { + graph, + graphState, + services: undefined, + operationsForService: {}, + urlState, + ...extractUiFindFromState(state), + }; +} + +function tracesDDG() { + return class extends React.PureComponent { + render(): React.ReactNode { + const { location } = this.props; + const urlArgs = queryString.parse(location.search); + const { end, start, limit, lookback, maxDuration, minDuration, view } = urlArgs; + const extraArgs = { end, start, limit, lookback, maxDuration, minDuration, view }; + return ( + + ); + } + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(tracesDDG()); diff --git a/packages/jaeger-ui/src/components/DeepDependencies/url.tsx b/packages/jaeger-ui/src/components/DeepDependencies/url.tsx index 74e630da31..e824e1678d 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/url.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/url.tsx @@ -27,7 +27,11 @@ export function matches(path: string) { return Boolean(matchPath(path, ROUTE_MATCHER)); } -export function getUrl(args?: { [key: string]: unknown; showOp?: boolean }) { +export function getUrl(args?: { [key: string]: unknown; showOp?: boolean }, baseUrl?: string) { + let basePath: string | undefined = baseUrl; + if (!basePath) { + basePath = ROUTE_PATH; + } if (args && !_isEmpty(args)) { const stringifyArgs = Reflect.has(args, 'showOp') ? { @@ -35,9 +39,9 @@ export function getUrl(args?: { [key: string]: unknown; showOp?: boolean }) { showOp: args.showOp ? 1 : 0, } : args; - return `${ROUTE_PATH}?${queryString.stringify(stringifyArgs)}`; + return `${basePath}?${queryString.stringify(stringifyArgs)}`; } - return ROUTE_PATH; + return basePath; } function firstParam(arg: string | string[]): string { diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/AltViewOptions.tsx b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/AltViewOptions.tsx new file mode 100644 index 0000000000..8be5a1374b --- /dev/null +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/AltViewOptions.tsx @@ -0,0 +1,30 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as React from 'react'; +import { Button } from 'antd'; + +type Props = { + traceResultsView: boolean; + onTraceGraphViewClicked: () => void; +}; + +export default function AltViewOptions(props: Props) { + const { onTraceGraphViewClicked, traceResultsView } = props; + return ( + + ); +} diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.css b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.css index ed61fc1911..e729e9f2f2 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.css +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.css @@ -35,3 +35,7 @@ limitations under the License. .SearchResults--resultLink:hover { color: inherit; } + +.SearchResults--ddg-container { + min-height: 600px; +} diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js index 82c6d343cd..c5d71dcc47 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js @@ -16,14 +16,17 @@ import * as React from 'react'; import { Select } from 'antd'; -import { Link } from 'react-router-dom'; -import { Field, reduxForm, formValueSelector } from 'redux-form'; +import { Link, withRouter } from 'react-router-dom'; +import { Field, formValueSelector, reduxForm } from 'redux-form'; +import { History as RouterHistory, Location } from 'history'; +import queryString from 'query-string'; import DiffSelection from './DiffSelection'; import * as markers from './index.markers'; import ResultItem from './ResultItem'; import ScatterPlot from './ScatterPlot'; import { getUrl } from '../url'; + import LoadingIndicator from '../../common/LoadingIndicator'; import NewWindowIcon from '../../common/NewWindowIcon'; import { getLocation } from '../../TracePage/url'; @@ -36,6 +39,8 @@ import type { FetchedTrace } from '../../../types'; import type { SearchQuery } from '../../../types/search'; import './index.css'; +import AltViewOptions from './AltViewOptions'; +import SearchResultsDGG from '../../DeepDependencies/traces'; type SearchResultsProps = { cohortAddTrace: string => void, @@ -50,6 +55,8 @@ type SearchResultsProps = { showStandaloneLink: boolean, skipMessage?: boolean, traces: TraceSummary[], + history: RouterHistory, + location: Location, }; const Option = Select.Option; @@ -81,7 +88,7 @@ const SelectSort = reduxForm({ export const sortFormSelector = formValueSelector('traceResultsSort'); -export default class SearchResults extends React.PureComponent { +export class UnconnectedSearchResults extends React.PureComponent { props: SearchResultsProps; static defaultProps = { skipMessage: false, queryOfResults: undefined }; @@ -95,6 +102,19 @@ export default class SearchResults extends React.PureComponent { + const { location, history } = this.props; + const urlState = queryString.parse(location.search); + history.push(getUrl({ ...urlState, view: 'ddg' })); + }; + + onTraceGraphViewClicked = () => { + const { location, history } = this.props; + const urlState = queryString.parse(location.search); + const view = urlState.view && urlState.view === 'ddg' ? 'traces' : 'ddg'; + history.push(getUrl({ ...urlState, view })); + }; + render() { const { diffCohort, @@ -107,7 +127,16 @@ export default class SearchResults extends React.PureComponent ); @@ -137,7 +166,7 @@ export default class SearchResults extends React.PureComponent
- {!hideGraph && ( + {!hideGraph && traceResultsView && (
({ @@ -157,7 +186,11 @@ export default class SearchResults extends React.PureComponent {traces.length} Trace{traces.length > 1 && 's'} - + {traceResultsView && } + {showStandaloneLink && (
- {diffSelection} -
    - {traces.map(trace => ( -
  • - -
  • - ))} -
+ {!traceResultsView && ( +
+ +
+ )} + {traceResultsView && diffSelection} + {traceResultsView && ( +
    + {traces.map(trace => ( +
  • + +
  • + ))} +
+ )}
); } } + +export default withRouter(UnconnectedSearchResults); 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 0cc7bfa82c..08ebd3d34c 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js @@ -15,7 +15,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import SearchResults from '.'; +import { UnconnectedSearchResults as SearchResults } from '.'; import * as markers from './index.markers'; import ResultItem from './ResultItem'; import ScatterPlot from './ScatterPlot'; diff --git a/packages/jaeger-ui/src/components/SearchTracePage/index.js b/packages/jaeger-ui/src/components/SearchTracePage/index.js index cd4426736a..ad4e546002 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/index.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/index.js @@ -94,6 +94,7 @@ export class SearchTracePageImpl extends Component { const hasTraceResults = traceResults && traceResults.length > 0; const showErrors = errors && !loadingTraces; const showLogo = isHomepage && !hasTraceResults && !loadingTraces && !errors; + return (
@@ -248,9 +249,11 @@ export function mapStateToProps(state) { if (serviceError) { errors.push(serviceError); } + const location = router.location; const sortBy = sortFormSelector(state, 'sortBy'); const traceResults = sortedTracesXformer(traces, sortBy); return { + location, queryOfResults, diffCohort, embedded, diff --git a/packages/jaeger-ui/src/components/SearchTracePage/index.test.js b/packages/jaeger-ui/src/components/SearchTracePage/index.test.js index 65ca7277bd..95035816fa 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/index.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/index.test.js @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { MemoryRouter } from 'react-router-dom'; + jest.mock('redux-form', () => { function reduxForm() { return component => component; @@ -23,7 +25,6 @@ jest.mock('redux-form', () => { return { Field, formValueSelector, reduxForm }; }); -jest.mock('react-router-dom'); jest.mock('store'); /* eslint-disable import/first */ @@ -76,7 +77,11 @@ describe('', () => { props.fetchServiceOperations.mockClear(); const oldFn = store.get; store.get = jest.fn(() => ({ service: 'svc-b' })); - wrapper = mount(); + wrapper = mount( + + + + ); expect(props.fetchServices.mock.calls.length).toBe(1); expect(props.fetchServiceOperations.mock.calls.length).toBe(1); store.get = oldFn; @@ -87,8 +92,16 @@ describe('', () => { const query = 'some-query'; const historyPush = jest.fn(); const historyMock = { push: historyPush }; - wrapper = mount(); - wrapper.instance().goToTrace(traceID); + wrapper = mount( + + + + ); + wrapper + .find(SearchTracePage) + .first() + .instance() + .goToTrace(traceID); expect(historyPush.mock.calls.length).toBe(1); expect(historyPush.mock.calls[0][0]).toEqual({ pathname: `/trace/${traceID}`, @@ -155,9 +168,14 @@ describe('mapStateToProps()', () => { services: stateServices, }; - const { maxTraceDuration, traceResults, diffCohort, numberOfTraceResults, ...rest } = mapStateToProps( - state - ); + const { + maxTraceDuration, + traceResults, + diffCohort, + numberOfTraceResults, + location, + ...rest + } = mapStateToProps(state); expect(traceResults).toHaveLength(stateTrace.search.results.length); expect(traceResults[0].traceID).toBe(trace.traceID); expect(maxTraceDuration).toBe(trace.duration); diff --git a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js new file mode 100644 index 0000000000..9f17500310 --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js @@ -0,0 +1,66 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import transformTracesToPaths from './transformTracesToPaths'; +import transformTraceData from '../transform-trace-data'; +import { fetchedState } from '../../constants'; + +describe('transform traces to ddg paths', () => { + const tracesResults = [ + { + traceID: 'Trace-1', + spans: [ + { + traceID: '1', + spanID: '1', + startTime: 1570040938479366, + operationName: 'HTTP GET /customer', + references: [ + { + refType: 'CHILD_OF', + traceID: '1', + spanID: '2', + }, + ], + processID: 'p1', + }, + { + traceID: '1', + spanID: '2', + startTime: 1570040938479369, + operationName: 'HTTP GET /', + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'customer', + }, + }, + }, + ]; + it('transforms trace results payload', () => { + const processed = tracesResults.map(transformTraceData); + const resultTraces = {}; + for (let i = 0; i < processed.length; i++) { + const data = processed[i]; + const id = data.traceID; + resultTraces[id] = { data, id, state: fetchedState.DONE }; + } + + const payload = transformTracesToPaths(resultTraces, 'customer'); + expect(payload.length).toBe(1); + expect(payload[0].path.length).toBe(2); + }); +}); diff --git a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx new file mode 100644 index 0000000000..3bf106c5fd --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx @@ -0,0 +1,74 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { TDdgPayloadEntry, TDdgPayload } from './types'; +import { Span, Trace } from '../../types/trace'; +import { FetchedTrace } from '../../types'; +import spanAncestorIds from '../../utils/span-ancestor-ids'; + +type Node = { + value?: Span; + children: Node[]; +}; + +const hasFocal = (path: TDdgPayloadEntry[], focalService: string, focalOperation: string | undefined) => { + for (let i = 0; i < path.length; ++i) { + if ( + focalService === path[i].service && + (focalOperation === undefined || focalOperation === path[i].operation) + ) { + return true; + } + } + return false; +}; + +function convertSpan(span: Span, trace: Trace): TDdgPayloadEntry { + const serviceName = trace.processes[span.processID].serviceName; + const operationName = span.operationName; + return { service: serviceName, operation: operationName }; +} + +export default function( + traces: Record, + focalService: string, + focalOperation: string | undefined +) { + const paths: TDdgPayload = []; + Object.values(traces).forEach(trace => { + const map: Map = new Map(); + if (trace.data) { + const traceId = trace.data.traceID; + trace.data.spans + .filter(span => { + map.set(span.spanID, span); + return !span.hasChildren; + }) + .forEach(leaf => { + const path = spanAncestorIds(leaf) + // @ts-ignore + .map(id => convertSpan(map.get(id), trace.data)); + // @ts-ignore + path.push(convertSpan(map.get(leaf.spanID), trace.data)); + if (hasFocal(path, focalService, focalOperation)) { + paths.push({ + path, + trace_id: traceId, + }); + } + }); + } + }); + return paths; +} diff --git a/packages/jaeger-ui/src/model/ddg/types.tsx b/packages/jaeger-ui/src/model/ddg/types.tsx index 8de824774f..350248ee0e 100644 --- a/packages/jaeger-ui/src/model/ddg/types.tsx +++ b/packages/jaeger-ui/src/model/ddg/types.tsx @@ -50,10 +50,12 @@ export type TDdgPayloadEntry = { service: string; }; -export type TDdgPayload = { +export type TDdgPayloadPath = { path: TDdgPayloadEntry[]; trace_id: string; // eslint-disable-line camelcase -}[]; +}; + +export type TDdgPayload = TDdgPayloadPath[]; export type TDdgService = { name: string;