diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.css b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.css
new file mode 100644
index 0000000000..374854bd5c
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.css
@@ -0,0 +1,21 @@
+/*
+Copyright (c) 2019 The Jaeger Authors.
+
+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.
+*/
+
+.GraphSearch {
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+}
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.js
new file mode 100644
index 0000000000..fa858eeb01
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/GraphSearch.js
@@ -0,0 +1,63 @@
+// @flow
+
+// 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 { Icon, Input } from 'antd';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+import queryString from 'query-string';
+
+import type { Location, /* Match, */ RouterHistory } from 'react-router-dom';
+
+import prefixUrl from '../../../utils/prefix-url';
+
+import type { ReduxState } from '../../../types/index';
+
+import './GraphSearch.css';
+
+type propsType = {
+ graphSearch?: string,
+ history: RouterHistory,
+ location: Location,
+};
+
+export function UnconnectedGraphSearch(props: propsType) {
+ function inputOnChange(evt) {
+ const { graphSearch, ...queryParams } = queryString.parse(props.location.search);
+ const { value } = evt.target;
+ if (value) {
+ queryParams.graphSearch = value;
+ }
+ props.history.replace(prefixUrl(`?${queryString.stringify(queryParams)}`));
+ }
+ return (
+
+
+
+
+ );
+}
+
+UnconnectedGraphSearch.defaultProps = {
+ graphSearch: null,
+};
+
+export function mapStateToProps(state: ReduxState): { graphSearch?: string } {
+ const { graphSearch } = queryString.parse(state.router.location.search);
+ return { graphSearch };
+}
+
+export default withRouter(connect(mapStateToProps)(UnconnectedGraphSearch));
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
index 2f182644f1..35f70a5e5e 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
@@ -18,6 +18,7 @@ import * as React from 'react';
import { DirectedGraph, LayoutManager } from '@jaegertracing/plexus';
import drawNode from './drawNode';
+import GraphSearch from './GraphSearch';
import ErrorMessage from '../../common/ErrorMessage';
import LoadingIndicator from '../../common/LoadingIndicator';
import { fetchedState } from '../../../constants';
@@ -112,6 +113,7 @@ export default class TraceDiffGraph extends React.PureComponent {
edges={edges}
vertices={vertices}
/>
+
);
}
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.css b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.css
index 13c1c79fd9..cdd8f5ea91 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.css
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.css
@@ -52,6 +52,12 @@ limitations under the License.
color: #fff;
}
+.DiffNode.is-graph-search-match {
+ outline-style: solid;
+ outline-color: green;
+ outline-width: 1px;
+}
+
.DiffNode--metricCell {
padding: 0.3rem 0.5rem;
background: rgba(255, 255, 255, 0.3);
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.js
index cbe2d3b9f6..3ba5bfd041 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.js
@@ -17,14 +17,22 @@
import * as React from 'react';
import { Popover } from 'antd';
import cx from 'classnames';
+import _map from 'lodash/map';
+import _memoize from 'lodash/memoize';
+import queryString from 'query-string';
+import { connect } from 'react-redux';
+import filterSpans from '../../../utils/filter-spans';
import type { PVertex } from '../../../model/trace-dag/types';
+import type { ReduxState } from '../../../types/index';
import './drawNode.css';
type Props = {
a: number,
b: number,
+ graphSearch?: string,
+ members: any[],
operation: string,
service: string,
};
@@ -34,9 +42,18 @@ const max = Math.max;
class DiffNode extends React.PureComponent {
props: Props;
+ filterSpans: typeof filterSpans;
+ static defaultProps = {
+ graphSearch: '',
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.filterSpans = _memoize(filterSpans);
+ }
render() {
- const { a, b, operation, service } = this.props;
+ const { a, b, graphSearch, operation, service } = this.props;
const isSame = a === b;
const className = cx({
'is-same': isSame,
@@ -45,6 +62,7 @@ class DiffNode extends React.PureComponent {
'is-added': a === 0,
'is-less': a > b && b > 0,
'is-removed': b === 0,
+ 'is-graph-search-match': this.filterSpans(graphSearch, _map(this.props.members, 'span')).size,
});
const chgSign = a < b ? '+' : '-';
const table = (
@@ -81,7 +99,15 @@ class DiffNode extends React.PureComponent {
}
}
+// TODO: This mapStateToProps is duplicative in three components
+export function mapStateToProps(state: ReduxState): { graphSearch?: string } {
+ const { graphSearch } = queryString.parse(state.router.location.search);
+ return { graphSearch };
+}
+
+const ConnectedDiffNode = connect(mapStateToProps)(DiffNode);
+
export default function drawNode(vertex: PVertex) {
- const { data, operation, service } = vertex.data;
- return ;
+ const { data, members, operation, service } = vertex.data;
+ return ;
}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.css b/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.css
index 75e2a545cf..d66d8bf09a 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.css
+++ b/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.css
@@ -27,6 +27,11 @@ limitations under the License.
.OpNode th {
border: none;
}
+.OpNode.is-graph-search-match {
+ outline-style: solid;
+ outline-color: green;
+ outline-width: 1px;
+}
.OpMode--mode-service {
background: #bbb;
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.js b/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.js
index 43d7df6bf4..99aae12b0f 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceGraph/OpNode.js
@@ -16,9 +16,17 @@
import * as React from 'react';
import { Popover } from 'antd';
+import cx from 'classnames';
+import _map from 'lodash/map';
+import _memoize from 'lodash/memoize';
+import queryString from 'query-string';
+import { connect } from 'react-redux';
+
+import filterSpans from '../../../utils/filter-spans';
import colorGenerator from '../../../utils/color-generator';
import type { PVertex } from '../../../model/trace-dag/types';
+import type { ReduxState } from '../../../types/index';
import './OpNode.css';
@@ -32,6 +40,8 @@ type Props = {
operation: string,
service: string,
mode: string,
+ graphSearch?: string,
+ members: any[],
};
export const MODE_SERVICE = 'service';
@@ -63,9 +73,29 @@ export function round2(percent: number) {
export default class OpNode extends React.PureComponent {
props: Props;
+ filterSpans: typeof filterSpans;
+ static defaultProps = {
+ graphSearch: '',
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.filterSpans = _memoize(filterSpans);
+ }
render() {
- const { count, errors, time, percent, selfTime, percentSelfTime, operation, service, mode } = this.props;
+ const {
+ count,
+ errors,
+ time,
+ percent,
+ selfTime,
+ percentSelfTime,
+ operation,
+ service,
+ mode,
+ graphSearch,
+ } = this.props;
// Spans over 20 % time are full red - we have probably to reconsider better approach
let backgroundColor;
@@ -81,8 +111,12 @@ export default class OpNode extends React.PureComponent {
.join();
}
+ const className = cx('OpNode', `OpNode--mode-${mode}`, {
+ 'is-graph-search-match': this.filterSpans(graphSearch, _map(this.props.members, 'span')).size,
+ });
+
const table = (
-
+
{
}
}
+export function mapStateToProps(state: ReduxState): { graphSearch?: string } {
+ const { graphSearch } = queryString.parse(state.router.location.search);
+ return { graphSearch };
+}
+
+const ConnectedOpNode = connect(mapStateToProps)(OpNode);
+
export function getNodeDrawer(mode: string) {
return function drawNode(vertex: PVertex) {
- const { data, operation, service } = vertex.data;
- return ;
+ const { data, members, operation, service } = vertex.data;
+ return (
+
+ );
};
}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceGraph/TraceGraph.js b/packages/jaeger-ui/src/components/TracePage/TraceGraph/TraceGraph.js
index f6ee4a5fa6..374394857e 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceGraph/TraceGraph.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceGraph/TraceGraph.js
@@ -20,6 +20,8 @@ import { DirectedGraph, LayoutManager } from '@jaegertracing/plexus';
import DRange from 'drange';
import { getNodeDrawer, MODE_SERVICE, MODE_TIME, MODE_SELFTIME, HELP_TABLE } from './OpNode';
+// TODO: Location implies only used by diff, need to move
+import GraphSearch from '../../TraceDiff/TraceDiffGraph/GraphSearch';
import convPlexus from '../../../model/trace-dag/convPlexus';
import TraceDag from '../../../model/trace-dag/TraceDag';
@@ -336,6 +338,7 @@ export default class TraceGraph extends React.PureComponent {
)}
+
);
}
diff --git a/packages/jaeger-ui/src/components/TracePage/index.js b/packages/jaeger-ui/src/components/TracePage/index.js
index 948f9ffe8e..e8163a506d 100644
--- a/packages/jaeger-ui/src/components/TracePage/index.js
+++ b/packages/jaeger-ui/src/components/TracePage/index.js
@@ -17,6 +17,7 @@
import * as React from 'react';
import { Input } from 'antd';
import _clamp from 'lodash/clamp';
+import _get from 'lodash/get';
import _mapValues from 'lodash/mapValues';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -38,13 +39,14 @@ import ErrorMessage from '../common/ErrorMessage';
import LoadingIndicator from '../common/LoadingIndicator';
import * as jaegerApiActions from '../../actions/jaeger-api';
import { fetchedState } from '../../constants';
+import filterSpans from '../../utils/filter-spans';
import type { CombokeysHandler, ShortcutCallbacks } from './keyboard-shortcuts';
import type { ViewRange, ViewRangeTimeUpdate } from './types';
import type { FetchedTrace, ReduxState } from '../../types';
import type { TraceArchive } from '../../types/archive';
import type { EmbeddedState } from '../../types/embedded';
-import type { KeyValuePair, Span } from '../../types/trace';
+// import type { KeyValuePair, Span } from '../../types/trace';
import './index.css';
@@ -217,60 +219,10 @@ export class TracePageImpl extends React.PureComponent {
- const spans = this.props.trace && this.props.trace.data && this.props.trace.data.spans;
- if (!spans) return null;
-
- // if a span field includes at least one filter in includeFilters, the span is a match
- const includeFilters = [];
-
- // values with keys that include text in any one of the excludeKeys will be ignored
- const excludeKeys = [];
-
- // split textFilter by whitespace, remove empty strings, and extract includeFilters and excludeKeys
- textFilter
- .split(' ')
- .map(s => s.trim())
- .filter(s => s)
- .forEach(w => {
- if (w[0] === '-') {
- excludeKeys.push(w.substr(1).toLowerCase());
- } else {
- includeFilters.push(w.toLowerCase());
- }
- });
-
- const isTextInFilters = (filters: Array, text: string) =>
- filters.some(filter => text.toLowerCase().includes(filter));
-
- const isTextInKeyValues = (kvs: Array) =>
- kvs
- ? kvs.some(kv => {
- // ignore checking key and value for a match if key is in excludeKeys
- if (isTextInFilters(excludeKeys, kv.key)) return false;
- // match if key or value matches an item in includeFilters
- return (
- isTextInFilters(includeFilters, kv.key) || isTextInFilters(includeFilters, kv.value.toString())
- );
- })
- : false;
-
- const isSpanAMatch = (span: Span) =>
- isTextInFilters(includeFilters, span.operationName) ||
- isTextInFilters(includeFilters, span.process.serviceName) ||
- isTextInKeyValues(span.tags) ||
- span.logs.some(log => isTextInKeyValues(log.fields)) ||
- isTextInKeyValues(span.process.tags);
-
- // declare as const because need to disambiguate the type
- const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span) => span.spanID));
- return rv;
- };
-
updateTextFilter = (textFilter: string) => {
let findMatchesIDs;
if (textFilter.trim()) {
- findMatchesIDs = this.filterSpans(textFilter);
+ findMatchesIDs = filterSpans(textFilter, _get(this.props, 'trace.data.spans'));
} else {
findMatchesIDs = null;
}
diff --git a/packages/jaeger-ui/src/model/trace-dag/TraceDag.js b/packages/jaeger-ui/src/model/trace-dag/TraceDag.js
index ae8701b7c3..48e2044067 100644
--- a/packages/jaeger-ui/src/model/trace-dag/TraceDag.js
+++ b/packages/jaeger-ui/src/model/trace-dag/TraceDag.js
@@ -43,6 +43,7 @@ export default class TraceDag {
});
const { data } = node;
data[key] = src.count;
+ node.members.push(...src.members);
node.count = data.b - data.a;
if (!node.parentID) {
dt.rootIDs.add(node.id);
diff --git a/packages/jaeger-ui/src/utils/filter-spans.js b/packages/jaeger-ui/src/utils/filter-spans.js
new file mode 100644
index 0000000000..47b8ed4c3c
--- /dev/null
+++ b/packages/jaeger-ui/src/utils/filter-spans.js
@@ -0,0 +1,66 @@
+/* flow */
+// 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 type { /* KeyValuePair, */ Span } from '../types/trace';
+
+export default function filterSpans(textFilter: string, spans: Span) {
+ // const spans = this.props.trace && this.props.trace.data && this.props.trace.data.spans;
+ if (!spans) return null;
+
+ // if a span field includes at least one filter in includeFilters, the span is a match
+ const includeFilters = [];
+
+ // values with keys that include text in any one of the excludeKeys will be ignored
+ const excludeKeys = [];
+
+ // split textFilter by whitespace, remove empty strings, and extract includeFilters and excludeKeys
+ textFilter
+ .split(' ')
+ .map(s => s.trim())
+ .filter(s => s)
+ .forEach(w => {
+ if (w[0] === '-') {
+ excludeKeys.push(w.substr(1).toLowerCase());
+ } else {
+ includeFilters.push(w.toLowerCase());
+ }
+ });
+
+ const isTextInFilters = (filters: Array, text: string) =>
+ filters.some(filter => text.toLowerCase().includes(filter));
+
+ const isTextInKeyValues = (kvs: Array) =>
+ kvs
+ ? kvs.some(kv => {
+ // ignore checking key and value for a match if key is in excludeKeys
+ if (isTextInFilters(excludeKeys, kv.key)) return false;
+ // match if key or value matches an item in includeFilters
+ return (
+ isTextInFilters(includeFilters, kv.key) || isTextInFilters(includeFilters, kv.value.toString())
+ );
+ })
+ : false;
+
+ const isSpanAMatch = (span: Span) =>
+ isTextInFilters(includeFilters, span.operationName) ||
+ isTextInFilters(includeFilters, span.process.serviceName) ||
+ isTextInKeyValues(span.tags) ||
+ span.logs.some(log => isTextInKeyValues(log.fields)) ||
+ isTextInKeyValues(span.process.tags);
+
+ // declare as const because need to disambiguate the type
+ const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span) => span.spanID));
+ return rv;
+}