From 8e50535c94b1ef98f366cdd832ee049292eb5f6b Mon Sep 17 00:00:00 2001 From: Joe Farro Date: Wed, 23 Aug 2017 13:44:49 -0400 Subject: [PATCH] Refactor trace detail (#53) Top level tasks - Maintenance of tests - Cleanup unused vars, imports - Refine utils - Many styles will be moved to CSS SpanGraph - Fix #49 - Span position in graph doesn't not match its position in the detail - Ticks in span graph made to match trace detail (in number and formatting) - Span graph refactored to trim down files and DOM elements - Styling adjustments TracePageHeader - `trace` prop removed - Added props for various title values instead of deriving them from `trace` Trace Detail - Several components split out into separate files - `transformTrace` to use already created span tree to determine span depth - Fix #59 - "Span Name" to "Service & Operation" Span Bar / Detail - Fix uber/jaeger#326: extraneous scrollbars in trace views - Fix #55: Some tags were not being rendered due to clashing keys (observed in a log message) - Tall content scrolls via entire table instead of single table cell - Horizontal scrolling for wide content (e.g. long log values) (Fix #58) - Full width of the header is clickable for tags, process, and logs headers (instead of header text, only) - Service and endpoint are shown on mouseover anywhere in span bar row - Label on span bars no longer off-screen - Clip or hide span bars when zoomed in (instead of flush left) - Add shadow to left / right boundary when span bar view is clipped - Darkened span name column to differentiate from span bar section - Span detail left column color coded by service - Clicking span detail left column collapses detail - Clicking anywhere left of parent span name toggles children visibility - Prevent collision of logs in log entries table SearchTracePage - Scatterplot dots are sized based on number of spans - Scatterplot dots mouseover shows trace name Misc - Several TraceTimelineViewer / utils removed - `TreeNode` `.walk()` method can now be used to calculate the depth, avoiding use of less efficient `.getPath()` - Removed several `console.error` warnings caused by React key issues - `yarn upgrade --latest` - Removed `react-sticky` - Fix #42 - Support URL prefix via homepage in package.json Commits * Update prettier and wrap at 110 instead of 80 * WIP commit for refactoring trace detail view * WIP refactor trace detail, split out css, start fixing tests * WIP refactor trace detail, tests working again * WIP refactor trace, yarn upgrade Sub-page scrolling used for trace detail (TODO: revert this). This lays the ground work for using react-virtualized, but unfortunately has major perf issues, hence the TODO: revert. yarn upgrade --latest. Notable changes: - Removed react-sticky - react-router v4 - react-vis CSS needed to be included via a sym-link Misc tweaks: - Styling adjusted on trace mini-map - Scatterplot dots are sized based on number of spans - Scatterplot dots mouseover shows trace name * WIP refactor trace, test maintenance * remove unused import * add license to css files * Revert sub-page scrolling for trace detail * Support URL prefix via homepage in package.json * Prevent collision of logs in log entries table * Add comments to new utils * Fix #59 - "Span Name" to "Service & Operation" * Fix unreleased regression - ellipsis on span name Add back the styling that adds an ellipsis to truncated span name text. * Address PR comment on search results scatter plot https://github.com/uber/jaeger-ui/pull/53#discussion_r134313013 * Misc cleanup from PR comment https://github.com/uber/jaeger-ui/pull/53#discussion_r134314020 * PR comment - Remove ms and use nano seconds https://github.com/uber/jaeger-ui/pull/53#discussion_r134316418 * PR comment - Adjust export, relates to recompose https://github.com/uber/jaeger-ui/pull/53#discussion_r134318188 * PR comment - Comment getTraceSpanIdsAsTree() https://github.com/uber/jaeger-ui/pull/53#discussion_r134321990 Signed-off-by: vvvprabhakar --- .eslintrc | 1 + .flowconfig | 3 + package.json | 100 +- src/api/jaeger.js | 5 +- src/api/jaeger.test.js | 14 +- src/components/App/App.css | 42 +- src/components/App/NotFound.js | 8 +- src/components/App/Page.css | 30 + src/components/App/Page.js | 21 +- src/components/App/TopNav.css | 29 + src/components/App/TopNav.js | 12 +- src/components/App/TraceIDSearchInput.js | 6 +- src/components/App/index.js | 51 +- .../index.test.js} | 40 +- .../DependencyGraph/DependencyGraph.css | 22 + .../index.test.js} | 21 +- .../TraceResultsScatterPlot.css | 31 + .../TraceResultsScatterPlot.js | 59 +- .../TraceResultsScatterPlot.test.js | 2 - .../SearchTracePage/TraceSearchForm.js | 13 +- .../SearchTracePage/TraceSearchResult.css | 22 + src/components/SearchTracePage/index.js | 23 +- src/components/SearchTracePage/react-vis.css | 1 + src/components/SpanGraph/SpanGraph.css | 16 - src/components/SpanGraph/SpanGraphSpan.js | 108 - .../SpanGraph/SpanGraphSpan.test.js | 145 - src/components/SpanGraph/SpanGraphTick.js | 75 - .../SpanGraph/SpanGraphTick.test.js | 139 - .../SpanGraph/SpanGraphTickHeader.js | 70 - .../SpanGraph/SpanGraphTickHeader.test.js | 80 - src/components/SpanGraph/index.js | 120 - src/components/SpanGraph/index.test.js | 111 - .../SpanGraph/SpanGraphTickHeader.js | 50 + .../SpanGraph/SpanGraphTickHeader.test.js | 65 + src/components/TracePage/SpanGraph/index.css | 38 + src/components/TracePage/SpanGraph/index.js | 82 + .../TracePage/SpanGraph/index.test.js | 66 + src/components/TracePage/TimelineScrubber.js | 5 +- .../TracePage/TimelineScrubber.test.js | 84 +- src/components/TracePage/TracePage.css | 36 - src/components/TracePage/TracePageHeader.js | 91 +- .../TracePage/TracePageHeader.test.js | 92 +- .../TracePage/TracePageTimeline.test.js | 422 -- ...TracePageTimeline.js => TraceSpanGraph.js} | 77 +- .../TracePage/TraceSpanGraph.test.js | 250 + .../TracePage/TraceTimelineViewer/SpanBar.css | 52 + .../TracePage/TraceTimelineViewer/SpanBar.js | 88 + .../TraceTimelineViewer/SpanBarRow.css | 109 + .../TraceTimelineViewer/SpanBarRow.js | 157 + .../TraceTimelineViewer/SpanDetail.js | 96 +- .../TraceTimelineViewer/SpanDetailRow.css | 71 + .../TraceTimelineViewer/SpanDetailRow.js | 61 + .../TraceTimelineViewer/SpanTreeOffset.css | 41 + .../TraceTimelineViewer/SpanTreeOffset.js | 50 + .../TracePage/TraceTimelineViewer/Ticks.css | 37 + .../TracePage/TraceTimelineViewer/Ticks.js | 56 + .../TraceTimelineViewer/TimelineRow.js | 69 + .../TraceTimelineViewer/TraceView.js | 187 + .../TraceTimelineViewer/get-filtered-spans.js | 29 + .../TracePage/TraceTimelineViewer/grid.css | 24 +- .../TracePage/TraceTimelineViewer/index.css | 44 +- .../TracePage/TraceTimelineViewer/index.js | 492 +- .../TraceTimelineViewer}/index.test.js | 34 +- .../TraceTimelineViewer/transforms.js | 28 +- .../TracePage/TraceTimelineViewer/utils.js | 109 +- .../TraceTimelineViewer/utils.test.js | 205 +- src/components/TracePage/index.css | 97 + src/components/TracePage/index.js | 118 +- src/components/TracePage/index.test.js | 116 +- src/index.js | 14 +- src/middlewares/index.js | 12 +- src/model/trace-viewer.js | 24 + src/selectors/trace.js | 86 +- src/selectors/trace.test.js | 15 +- src/utils/TreeNode.js | 13 +- src/utils/configure-store.test.js | 2 +- src/utils/prefix-url.js | 61 + src/utils/prefix-url.test.js | 60 + yarn.lock | 4925 ++++++++++------- 79 files changed, 5832 insertions(+), 4528 deletions(-) create mode 100644 src/components/App/Page.css create mode 100644 src/components/App/TopNav.css rename src/components/{SpanGraph/SpanGraphTickHeaderLabel.test.js => App/index.test.js} (51%) rename src/components/{SpanGraph/SpanGraphTickHeaderLabel.js => DependencyGraph/index.test.js} (68%) create mode 120000 src/components/SearchTracePage/react-vis.css delete mode 100644 src/components/SpanGraph/SpanGraph.css delete mode 100644 src/components/SpanGraph/SpanGraphSpan.js delete mode 100644 src/components/SpanGraph/SpanGraphSpan.test.js delete mode 100644 src/components/SpanGraph/SpanGraphTick.js delete mode 100644 src/components/SpanGraph/SpanGraphTick.test.js delete mode 100644 src/components/SpanGraph/SpanGraphTickHeader.js delete mode 100644 src/components/SpanGraph/SpanGraphTickHeader.test.js delete mode 100644 src/components/SpanGraph/index.js delete mode 100644 src/components/SpanGraph/index.test.js create mode 100644 src/components/TracePage/SpanGraph/SpanGraphTickHeader.js create mode 100644 src/components/TracePage/SpanGraph/SpanGraphTickHeader.test.js create mode 100644 src/components/TracePage/SpanGraph/index.css create mode 100644 src/components/TracePage/SpanGraph/index.js create mode 100644 src/components/TracePage/SpanGraph/index.test.js delete mode 100644 src/components/TracePage/TracePage.css delete mode 100644 src/components/TracePage/TracePageTimeline.test.js rename src/components/TracePage/{TracePageTimeline.js => TraceSpanGraph.js} (78%) create mode 100644 src/components/TracePage/TraceSpanGraph.test.js create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanBar.css create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanBar.js create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanBarRow.css create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanBarRow.js create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanDetailRow.css create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanDetailRow.js create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanTreeOffset.css create mode 100644 src/components/TracePage/TraceTimelineViewer/SpanTreeOffset.js create mode 100644 src/components/TracePage/TraceTimelineViewer/Ticks.css create mode 100644 src/components/TracePage/TraceTimelineViewer/Ticks.js create mode 100644 src/components/TracePage/TraceTimelineViewer/TimelineRow.js create mode 100644 src/components/TracePage/TraceTimelineViewer/TraceView.js create mode 100644 src/components/TracePage/TraceTimelineViewer/get-filtered-spans.js rename src/{ => components/TracePage/TraceTimelineViewer}/index.test.js (59%) create mode 100644 src/components/TracePage/index.css create mode 100644 src/model/trace-viewer.js create mode 100644 src/utils/prefix-url.js create mode 100644 src/utils/prefix-url.test.js diff --git a/.eslintrc b/.eslintrc index 6c744eb461..8c709b599f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ /* jsx */ "jsx-a11y/no-static-element-interactions": 1, "jsx-a11y/href-no-hash": 0, + "jsx-a11y/label-has-for": 0, /* react */ "react/jsx-filename-extension": 0, diff --git a/.flowconfig b/.flowconfig index 54d2741198..cd8775744c 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,6 +1,9 @@ [ignore] +# node_module packages that have a .flowconfig in conflict with this file .*/node_modules/fbjs/.* .*/node_modules/uber-licence/.* +.*/node_modules/redux-form/.* +.*/node_modules/react-motion/.* [include] diff --git a/package.json b/package.json index a8e6e344b1..39c0593056 100644 --- a/package.json +++ b/package.json @@ -4,67 +4,67 @@ "main": "src/index.js", "license": "MIT", "proxy": "http://localhost:16686", + "homepage": null, "devDependencies": { - "babel-eslint": "^7.1.1", - "enzyme": "^2.4.1", - "eslint": "^3.16.1", - "eslint-config-airbnb": "^14.1.0", - "eslint-config-prettier": "^1.5.0", - "eslint-config-react-app": "^0.6.2", - "eslint-plugin-flowtype": "^2.21.0", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-jsx-a11y": "^4.0.0", - "eslint-plugin-react": "7.1.0", - "husky": "^0.13.3", - "lint-staged": "^3.4.0", + "babel-eslint": "^7.2.3", + "enzyme": "^2.9.1", + "eslint": "^4.5.0", + "eslint-config-airbnb": "^15.1.0", + "eslint-config-prettier": "^2.3.0", + "eslint-config-react-app": "^2.0.0", + "eslint-plugin-flowtype": "^2.35.0", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-react": "^7.2.1", + "husky": "^0.14.3", + "lint-staged": "^4.0.3", "prettier": "^1.5.3", - "react-scripts": "^0.9.5", - "sets-equal": "^1.0.0", - "sinon": "^1.17.5", + "react-scripts": "^1.0.11", + "react-test-renderer": "^15.6.1", + "sinon": "^3.2.1", "uber-licence": "^3.1.1" }, "dependencies": { "basscss": "^8.0.3", - "chance": "^1.0.4", - "cytoscape": "^2.7.13", - "cytoscape-dagre": "^1.3.0", - "d3-scale": "^1.0.4", + "chance": "^1.0.10", + "classnames": "^2.2.5", + "cytoscape": "^3.2.1", + "cytoscape-dagre": "^2.0.0", + "d3-scale": "^1.0.6", "dagre": "^0.7.4", - "flow-bin": "^0.36.0", - "fuzzy": "^0.1.1", - "global": "^4.3.0", + "flow-bin": "^0.53.1", + "fuzzy": "^0.1.3", + "global": "^4.3.2", + "history": "^4.6.3", "is-promise": "^2.1.0", "isomorphic-fetch": "^2.2.1", - "json-markup": "^1.0.0", - "lodash": "^4.17.2", - "moment": "^2.14.1", + "json-markup": "^1.1.0", + "lodash": "^4.17.4", + "moment": "^2.18.1", "prop-types": "^15.5.10", - "query-string": "^4.2.3", - "react": "^15.5.0", - "react-addons-perf": "^15.4.1", - "react-addons-shallow-compare": "^15.3.2", - "react-addons-test-utils": "^15.3.1", + "query-string": "^5.0.0", + "react": "^15.6.1", + "react-addons-perf": "^15.4.2", "react-dimensions": "^1.3.0", - "react-dom": "^15.5.0", - "react-ga": "^2.1.2", - "react-helmet": "^3.1.0", - "react-metrics": "^2.2.3", - "react-redux": "^4.4.5", - "react-router": "^2.7.0", - "react-router-redux": "^4.0.5", - "react-sticky": "^5.0.5", - "react-vis": "^0.6.4", - "react-vis-force": "^0.1.3", - "recompose": "^0.20.2", - "redux": "^3.5.2", - "redux-actions": "^0.11.0", - "redux-async-middleware": "0.0.0", - "redux-form": "6.3.2", - "redux-promise-middleware": "^4.0.0", - "reselect": "^2.5.3", - "semantic-ui-css": "^2.2.4", - "semantic-ui-react": "^0.58.1", - "store": "^1.3.20" + "react-dom": "^15.6.1", + "react-ga": "^2.2.0", + "react-helmet": "^5.1.3", + "react-metrics": "^2.3.2", + "react-redux": "^5.0.6", + "react-router-dom": "^4.1.2", + "react-router-redux": "5.0.0-alpha.6", + "react-vis": "^1.7.2", + "react-vis-force": "^0.3.1", + "recompose": "^0.25.0", + "redux": "^3.7.2", + "redux-actions": "^2.2.1", + "redux-async-middleware": "^0.0.0", + "redux-form": "^7.0.3", + "redux-promise-middleware": "^4.3.0", + "reselect": "^3.0.1", + "semantic-ui-css": "^2.2.12", + "semantic-ui-react": "^0.71.4", + "store": "^2.0.12" }, "scripts": { "start": "react-scripts start", diff --git a/src/api/jaeger.js b/src/api/jaeger.js index 63357c9287..266f04ad15 100644 --- a/src/api/jaeger.js +++ b/src/api/jaeger.js @@ -22,6 +22,8 @@ import fetch from 'isomorphic-fetch'; import moment from 'moment'; import queryString from 'query-string'; +import prefixUrl from '../utils/prefix-url'; + function getJSON(url, query) { return fetch(`${url}${query ? `?${queryString.stringify(query)}` : ''}`, { credentials: 'include', @@ -42,12 +44,11 @@ function getJSON(url, query) { } ); } - return response.json(); }); } -export const DEFAULT_API_ROOT = '/api/'; +export const DEFAULT_API_ROOT = prefixUrl('/api/'); export const DEFAULT_DEPENDENCY_LOOKBACK = moment.duration(1, 'weeks').asMilliseconds(); const JaegerAPI = { diff --git a/src/api/jaeger.test.js b/src/api/jaeger.test.js index 5db73918b6..5e7a854a76 100644 --- a/src/api/jaeger.test.js +++ b/src/api/jaeger.test.js @@ -23,14 +23,12 @@ import JaegerAPI from './jaeger'; const generatedTraces = traceGenerator.traces({ traces: 5 }); jest.mock('isomorphic-fetch', () => - jest.fn( - () => - new Promise(resolve => - resolve({ - status: 200, - data: () => Promise.resolve({ data: null }), - }) - ) + jest.fn(() => + Promise.resolve({ + status: 200, + data: () => Promise.resolve({ data: null }), + json: () => Promise.resolve({ data: null }), + }) ) ); diff --git a/src/components/App/App.css b/src/components/App/App.css index f16ed469d7..5981c33b50 100644 --- a/src/components/App/App.css +++ b/src/components/App/App.css @@ -1,21 +1,37 @@ +/* +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#jaeger-ui-root { + height: 100%; +} + a { color: #11939A; } + a:hover { color: #00474E; cursor: pointer; } -.jaeger-ui--content { - padding-left: 1rem; - padding-right: 1rem; - padding-bottom: 1rem; -} - -.ui.menu.jaeger-ui--topnav { - border-radius: 0; - z-index: 1000; - padding: 5px; -} .clearfix:after { content: " "; @@ -53,10 +69,6 @@ a:hover { overflow-x: scroll; } -.overflow-x::-webkit-scrollbar { - display: none; -} - .ui.compact.table td { padding: 0.1em 0.5em; } diff --git a/src/components/App/NotFound.js b/src/components/App/NotFound.js index 70a01208a4..ebd7b64797 100644 --- a/src/components/App/NotFound.js +++ b/src/components/App/NotFound.js @@ -20,7 +20,9 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; + +import prefixUrl from '../../utils/prefix-url'; export default function NotFound({ error }) { return ( @@ -37,11 +39,11 @@ export default function NotFound({ error }) { {error &&

- {error.toString()} + {String(error)}

}
- + {'Back home'}
diff --git a/src/components/App/Page.css b/src/components/App/Page.css new file mode 100644 index 0000000000..6199b9bcbf --- /dev/null +++ b/src/components/App/Page.css @@ -0,0 +1,30 @@ +/* +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +.jaeger-ui-page { + height: 100%; +} + +.jaeger-ui--content { + height: 100%; + padding-top: 50px; +} diff --git a/src/components/App/Page.js b/src/components/App/Page.js index d6dbdce99f..a5ae110838 100644 --- a/src/components/App/Page.js +++ b/src/components/App/Page.js @@ -21,26 +21,23 @@ import PropTypes from 'prop-types'; import React from 'react'; import Helmet from 'react-helmet'; -import { Sticky, StickyContainer } from 'react-sticky'; import TopNav from './TopNav'; +import './Page.css'; + export default function JaegerUIPage({ children }) { return ( -
- - - - - -
- {children} -
-
+
+ + +
+ {children} +
); } JaegerUIPage.propTypes = { - children: PropTypes.any, + children: PropTypes.node, }; diff --git a/src/components/App/TopNav.css b/src/components/App/TopNav.css new file mode 100644 index 0000000000..73315ece98 --- /dev/null +++ b/src/components/App/TopNav.css @@ -0,0 +1,29 @@ +/* +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +.ui.menu.jaeger-ui--topnav { + border-radius: 0; + padding: 5px; + position: fixed; + width: 100%; + z-index: 1000; +} diff --git a/src/components/App/TopNav.js b/src/components/App/TopNav.js index 0bb68015a9..835356661b 100644 --- a/src/components/App/TopNav.js +++ b/src/components/App/TopNav.js @@ -19,18 +19,22 @@ // THE SOFTWARE. import React from 'react'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; + import TraceIDSearchInput from './TraceIDSearchInput'; +import prefixUrl from '../../utils/prefix-url'; + +import './TopNav.css'; const NAV_LINKS = [ { key: 'dependencies', - to: '/dependencies', + to: prefixUrl('/dependencies'), text: 'Dependencies', }, { key: 'search', - to: '/search', + to: prefixUrl('/search'), text: 'Search', }, ]; @@ -38,7 +42,7 @@ const NAV_LINKS = [ export default function TopNav() { return (