Skip to content

Commit

Permalink
Derive DDG from search results
Browse files Browse the repository at this point in the history
Signed-off-by: Ruben Vargas <ruben.vp8510@gmail.com>
  • Loading branch information
rubenvp8510 committed Sep 12, 2019
1 parent 6f78c8d commit 53e1635
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 22 deletions.
154 changes: 154 additions & 0 deletions packages/jaeger-ui/src/components/DeepDependencies/traces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 React, { Component } from 'react';
import { History as RouterHistory, Location } from 'history';
import _get from 'lodash/get';
import { connect } from 'react-redux';
import { getUrlState } from './url';
import { getUrl } from '../SearchTracePage/url';

import Graph from './Graph';
import { extractUiFindFromState, TExtractUiFindFromStateReturn } from '../common/UiFindInput';
import { EDirection, TDdgModel, TDdgModelParams, TDdgSparseUrlState } from '../../model/ddg/types';
import TGraph, { makeGraph } from '../../model/ddg/Graph';
import { encodeDistance } from '../../model/ddg/visibility-codec';
import { ReduxState } from '../../types';

import './index.css';
import transformDdgData from '../../model/ddg/transformDdgData';
import transformTracesToPaths from '../../model/ddg/transformTracesToPaths';
import HopsSelector from './Header/HopsSelector';

type TDispatchProps = {
fetchDeepDependencyGraph: (query: TDdgModelParams) => void;
fetchServices: () => void;
fetchServiceOperations: (service: string) => void;
transformTracesToDDG: (trace: any) => void;
};

type TReduxProps = TExtractUiFindFromStateReturn & {
graph: TGraph | undefined;
services?: string[] | null;
urlState: TDdgSparseUrlState;
trace: any;
model: TDdgModel | undefined;
};

type TOwnProps = {
history: RouterHistory;
location: Location;
};

type TProps = TDispatchProps & TReduxProps & TOwnProps;

// export for tests
export class SearchResultsDDG extends Component<TProps> {
// shouldComponentUpdate is necessary as we don't want the plexus graph to re-render due to a uxStatus change
shouldComponentUpdate(nextProps: TProps) {
const updateCauses = [
'uiFind',
'trace',
'services',
'urlState.service',
'urlState.operation',
'urlState.start',
'urlState.end',
'urlState.visEncoding',
];
return updateCauses.some(cause => _get(nextProps, cause) !== _get(this.props, cause));
}

setDistance = (distance: number, direction: EDirection) => {
const { visEncoding } = this.props.urlState;
const { model } = this.props;
if (model) {
this.updateUrlState({
visEncoding: encodeDistance({
ddgModel: model,
direction,
distance,
prevVisEncoding: visEncoding,
}),
});
}
};

updateUrlState = (newValues: Partial<TDdgSparseUrlState>) => {
const { uiFind, urlState, history } = this.props;
history.push(getUrl({ uiFind, ...urlState, ...newValues }));
};

render() {
const { graph, uiFind, urlState, model } = this.props;
const { visEncoding } = urlState;
const uiFindMatches = graph && graph.getVisibleUiFindMatches(uiFind, visEncoding);

let content = (
<div>
<h1>Unknown graphState:</h1>
<p>${JSON.stringify(graph)}</p>
</div>
);
if (graph && model) {
const { edges, vertices } = graph.getVisible(visEncoding);
const distanceToPathElems = model.distanceToPathElems;
content = (
<div>
<HopsSelector
distanceToPathElems={distanceToPathElems}
handleClick={this.setDistance}
visEncoding={visEncoding}
/>
<div className="Ddg--graphWrapper">
<Graph
edges={edges}
getVisiblePathElems={(key: string) => graph.getVisiblePathElems(key, visEncoding)}
uiFindMatches={uiFindMatches}
vertices={vertices}
/>
</div>
</div>
);
}
return content;
}
}

// export for tests
export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxProps {
const { trace } = state;
const urlState = getUrlState(ownProps.location.search);
const { density, operation, service, showOp } = urlState;
let graph: TGraph | undefined;
let model: TDdgModel | undefined;

if (service) {
const paths = transformTracesToPaths(trace.traces, service, operation);
model = transformDdgData(paths, { service, operation });
graph = makeGraph(model, showOp, density);
} else {
graph = undefined;
}

return {
model,
trace,
graph,
urlState,
...extractUiFindFromState(state),
};
}

export default connect(mapStateToProps)(SearchResultsDDG);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2018 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 (
<Button className="ub-mr2" htmlType="button" onClick={onTraceGraphViewClicked}>
{traceResultsView ? 'Deep dependency graph' : 'Trace Results'}
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import * as React from 'react';
import { Select } from 'antd';
import { Link } from 'react-router-dom';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Field, formValueSelector, reduxForm } from 'redux-form';
import { History as RouterHistory, Location } from 'history';

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';
Expand All @@ -36,6 +38,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,
Expand All @@ -50,6 +54,12 @@ type SearchResultsProps = {
showStandaloneLink: boolean,
skipMessage?: boolean,
traces: TraceSummary[],
history: RouterHistory,
location: Location,
};

type StateProps = {
traceResultsView: boolean,
};

const Option = Select.Option;
Expand Down Expand Up @@ -81,11 +91,16 @@ const SelectSort = reduxForm({

export const sortFormSelector = formValueSelector('traceResultsSort');

export default class SearchResults extends React.PureComponent<SearchResultsProps> {
export default class SearchResults extends React.Component<SearchResultsProps, StateProps> {
props: SearchResultsProps;

static defaultProps = { skipMessage: false, queryOfResults: undefined };

constructor(props) {
super(props);
this.state = { traceResultsView: true };
}

toggleComparison = (traceID: string, remove: boolean) => {
const { cohortAddTrace, cohortRemoveTrace } = this.props;
if (remove) {
Expand All @@ -95,6 +110,13 @@ export default class SearchResults extends React.PureComponent<SearchResultsProp
}
};

onTraceGraphViewClicked = () => {
const { traceResultsView } = this.state;
this.setState({
traceResultsView: !traceResultsView,
});
};

render() {
const {
diffCohort,
Expand All @@ -107,7 +129,12 @@ export default class SearchResults extends React.PureComponent<SearchResultsProp
showStandaloneLink,
skipMessage,
traces,
history,
location,
} = this.props;

const { traceResultsView } = this.state;

const diffSelection = !disableComparisons && (
<DiffSelection toggleComparison={this.toggleComparison} traces={diffCohort} />
);
Expand Down Expand Up @@ -158,6 +185,10 @@ export default class SearchResults extends React.PureComponent<SearchResultsProp
{traces.length} Trace{traces.length > 1 && 's'}
</h2>
<SelectSort />
<AltViewOptions
traceResultsView={traceResultsView}
onTraceGraphViewClicked={this.onTraceGraphViewClicked}
/>
{showStandaloneLink && (
<Link
className="u-tx-inherit ub-nowrap ub-ml3"
Expand All @@ -172,21 +203,24 @@ export default class SearchResults extends React.PureComponent<SearchResultsProp
</div>
</div>
<div>
{diffSelection}
<ul className="ub-list-reset">
{traces.map(trace => (
<li className="ub-my3" key={trace.traceID}>
<ResultItem
durationPercent={getPercentageOfDuration(trace.duration, maxTraceDuration)}
isInDiffCohort={cohortIds.has(trace.traceID)}
linkTo={getLocation(trace.traceID, { fromSearch: searchUrl })}
toggleComparison={this.toggleComparison}
trace={trace}
disableComparision={disableComparisons}
/>
</li>
))}
</ul>
{!traceResultsView && <SearchResultsDGG location={location} history={history} />}
{traceResultsView && diffSelection}
{traceResultsView && (
<ul className="ub-list-reset">
{traces.map(trace => (
<li className="ub-my3" key={trace.traceID}>
<ResultItem
durationPercent={getPercentageOfDuration(trace.duration, maxTraceDuration)}
isInDiffCohort={cohortIds.has(trace.traceID)}
linkTo={getLocation(trace.traceID, { fromSearch: searchUrl })}
toggleComparison={this.toggleComparison}
trace={trace}
disableComparision={disableComparisons}
/>
</li>
))}
</ul>
)}
</div>
</div>
);
Expand Down
9 changes: 9 additions & 0 deletions packages/jaeger-ui/src/components/SearchTracePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,13 @@ export class SearchTracePageImpl extends Component {
traceResults,
queryOfResults,
loadJsonTraces,
location,
history,
} = this.props;
const hasTraceResults = traceResults && traceResults.length > 0;
const showErrors = errors && !loadingTraces;
const showLogo = isHomepage && !hasTraceResults && !loadingTraces && !errors;

return (
<div>
<Row>
Expand Down Expand Up @@ -134,6 +137,8 @@ export class SearchTracePageImpl extends Component {
showStandaloneLink={Boolean(embedded)}
skipMessage={isHomepage}
traces={traceResults}
location={location}
history={history}
/>
)}
{showLogo && (
Expand Down Expand Up @@ -191,6 +196,8 @@ SearchTracePageImpl.propTypes = {
})
),
loadJsonTraces: PropTypes.func,
// eslint-disable-next-line react/forbid-prop-types
location: PropTypes.any,
};

const stateTraceXformer = memoizeOne(stateTrace => {
Expand Down Expand Up @@ -248,9 +255,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,
Expand Down
11 changes: 8 additions & 3 deletions packages/jaeger-ui/src/components/SearchTracePage/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,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);
Expand Down
Loading

0 comments on commit 53e1635

Please sign in to comment.