Skip to content

Commit

Permalink
Support URL prefix via homepage in package.json
Browse files Browse the repository at this point in the history
  • Loading branch information
tiffon committed Aug 20, 2017
1 parent c657cf1 commit 2226744
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 52 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"main": "src/index.js",
"license": "MIT",
"proxy": "http://localhost:16686",
"homepage": null,
"devDependencies": {
"babel-eslint": "^7.2.3",
"enzyme": "^2.9.1",
Expand Down
5 changes: 3 additions & 2 deletions src/api/jaeger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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 = {
Expand Down
14 changes: 6 additions & 8 deletions src/api/jaeger.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
})
)
);

Expand Down
4 changes: 3 additions & 1 deletion src/components/App/NotFound.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router-dom';

import prefixUrl from '../../utils/prefix-url';

export default function NotFound({ error }) {
return (
<section className="ui container">
Expand All @@ -41,7 +43,7 @@ export default function NotFound({ error }) {
</p>
</div>}
<div className="ui center aligned basic segment">
<Link to="/">
<Link to={prefixUrl('/')}>
{'Back home'}
</Link>
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/components/App/TopNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,27 @@ import React from 'react';
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',
},
];

export default function TopNav() {
return (
<nav className="ui top inverted menu jaeger-ui--topnav">
<Link to="/" className="header item">
<Link to={prefixUrl('/')} className="header item">
{'Jaeger UI'}
</Link>

Expand Down
29 changes: 15 additions & 14 deletions src/components/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import createHistory from 'history/createBrowserHistory';
import PropTypes from 'prop-types';
import { metrics } from 'react-metrics';
import { Provider } from 'react-redux';
import { Route, Redirect, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter } from 'react-router-redux';
import { metrics } from 'react-metrics';

import 'semantic-ui-css/semantic.min.css';

import configureStore from '../../utils/configure-store';
import JaegerAPI, { DEFAULT_API_ROOT } from '../../api/jaeger';

import Page from './Page';
import NotFound from './NotFound';
import { ConnectedTracePage } from '../TracePage';
import { ConnectedSearchTracePage } from '../SearchTracePage';
import { ConnectedDependencyGraphPage } from '../DependencyGraph';
import { ConnectedSearchTracePage } from '../SearchTracePage';
import { ConnectedTracePage } from '../TracePage';
import JaegerAPI, { DEFAULT_API_ROOT } from '../../api/jaeger';
import configureStore from '../../utils/configure-store';
import metricConfig from '../../utils/metrics';
import prefixUrl from '../../utils/prefix-url';

import './App.css';

Expand Down Expand Up @@ -67,17 +67,18 @@ export default class JaegerUIApp extends Component {
render() {
const { history } = this.props;
const store = configureStore(history);

return (
<Provider store={store}>
<ConnectedRouter history={history}>
<PageWithMetrics>
<Switch>
<Route path="/search" component={ConnectedSearchTracePage} />
<Route path="/trace/:id" component={ConnectedTracePage} />
<Route path="/dependencies" component={ConnectedDependencyGraphPage} />
<Redirect exact path="/" to="/search" />
<Route path="*" component={NotFound} />
<Route path={prefixUrl('/search')} component={ConnectedSearchTracePage} />
<Route path={prefixUrl('/trace/:id')} component={ConnectedTracePage} />
<Route path={prefixUrl('/dependencies')} component={ConnectedDependencyGraphPage} />
<Redirect exact path="/" to={prefixUrl('/search')} />
<Redirect exact path={prefixUrl()} to={prefixUrl('/search')} />
<Redirect exact path={prefixUrl('/')} to={prefixUrl('/search')} />
<Route component={NotFound} />
</Switch>
</PageWithMetrics>
</ConnectedRouter>
Expand Down
3 changes: 2 additions & 1 deletion src/components/SearchTracePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as orderBy from '../../model/order-by';
import { sortTraces, getTraceSummaries } from '../../model/search';
import { getPercentageOfDuration } from '../../utils/date';
import getLastXformCacher from '../../utils/get-last-xform-cacher';
import prefixUrl from '../../utils/prefix-url';

/**
* Contains the dropdown to sort and filter trace search results
Expand Down Expand Up @@ -147,7 +148,7 @@ export default class SearchTracePage extends Component {
<ul className="list-reset">
{traceResults.map(trace =>
<li key={trace.traceID} className="my1">
<Link to={`/trace/${trace.traceID}`}>
<Link to={prefixUrl(`/trace/${trace.traceID}`)}>
<TraceSearchResult
trace={trace}
durationPercent={getPercentageOfDuration(trace.duration, maxTraceDuration)}
Expand Down
29 changes: 29 additions & 0 deletions src/components/TracePage/TraceTimelineViewer/get-filtered-spans.js
Original file line number Diff line number Diff line change
@@ -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.

import { filterSpansForText } from '../../../selectors/span';

export default function getFilteredSpans(trace, text) {
const matches = filterSpansForText({
text,
spans: trace.spans,
});
return new Set(matches.map(span => span.spanID));
}
19 changes: 4 additions & 15 deletions src/components/TracePage/TraceTimelineViewer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';

import { filterSpansForText } from '../../../selectors/span';
import getFilteredSpans from './get-filtered-spans';
import TraceView from './TraceView';
import { getPositionInRange } from './utils';

import './grid.css';
import './index.css';

// TODO: Move some styles to css
// TODO: Clean up component names and move to seperate files.
// TODO: Add unit tests
// TODO: unify transforms and utils

function getFilteredSpans(trace, text) {
const matches = filterSpansForText({
text,
spans: trace.spans,
});
return new Set(matches.map(span => span.spanID));
}

export default class TraceTimelineViewer extends Component {
constructor(props) {
Expand All @@ -60,12 +49,12 @@ export default class TraceTimelineViewer extends Component {
}

componentWillReceiveProps(nextProps) {
const { textFilter } = nextProps;
if (textFilter === this.props.textFilter) {
const { xformedTrace, textFilter } = nextProps;
if (textFilter === this.props.textFilter && xformedTrace === this.props.xformedTrace) {
return;
}
const filteredSpans = textFilter ? getFilteredSpans(nextProps.xformedTrace, textFilter) : null;
this.setState({ filteredSpans });
this.setState({ filteredSpans, trace: xformedTrace });
}

toggleSpanCollapse(spanID) {
Expand Down
45 changes: 45 additions & 0 deletions src/components/TracePage/TraceTimelineViewer/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.

import React from 'react';
import { shallow } from 'enzyme';

import TraceTimelineViewer from './index';
import { transformTrace } from './transforms';
import traceGenerator from '../../../demo/trace-generators';

describe('<TraceTimelineViewer>', () => {
const trace = traceGenerator.trace({});
const props = {
textFilter: null,
timeRangeFilter: [0, 1],
xformedTrace: transformTrace(trace),
};

let wrapper;

beforeEach(() => {
wrapper = shallow(<TraceTimelineViewer {...props} />);
});

it('it does not explode', () => {
expect(wrapper).toBeDefined();
});
});
12 changes: 4 additions & 8 deletions src/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { change } from 'redux-form';
import { replace } from 'react-router-redux';

import { searchTraces, fetchServiceOperations } from '../actions/jaeger-api';
import prefixUrl from '../utils/prefix-url';

/**
* Middleware to load "operations" for a particular service.
Expand All @@ -42,15 +43,10 @@ export const loadOperationsForServiceMiddleware = store => next => action => {
};

export const historyUpdateMiddleware = store => next => action => {
switch (action.type) {
case `${searchTraces}`: {
store.dispatch(replace(`/search?${queryString.stringify(action.meta.query)}`));
break;
}
default:
break;
if (action.type === String(searchTraces)) {
const url = prefixUrl(`/search?${queryString.stringify(action.meta.query)}`);
store.dispatch(replace(url));
}

next(action);
};

Expand Down
61 changes: 61 additions & 0 deletions src/utils/prefix-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.

import { homepage } from '../../package.json';

// strip the domain, first slash and trailing slash (if present)
const rx = new RegExp('^(?:https?://[^/]+)?/?(.*?)/?$', 'i');
let prefix = '';

/**
* Generate the URL prefix from `value` and use it for all subsequent calls to
* `prefixUrl()`. All of the following `value` parameters result in the prefix
* `"/some/prefix"` (although, the first is the preferred form).
*
* - `"/some/prefix"`
* - `"some/prefix"`
* - `"/some/prefix/"`
* - `"http://my.example.com/some/prefix"`
* - `"https://my.example.com/some/prefix/"`
*
* Note: This function has a side effect of setting the default URL prefix
* applied via the file's default explort.
*
* @param {?string} value The value to derive the URL prefix from.
* @return {string} The resultant prefix.
*/
export function deriveAndSetPrefix(value) {
prefix = value ? value.replace(rx, '/$1') : '';
return prefix;
}

deriveAndSetPrefix(homepage);

/**
* Add the URL prefix, derived from `homepage` in `package.json`, to the URL
* argument. The domain is stripped from `homepage` before being added.
*
* @param {string} value The URL to have the prefix added to.
* @return {string} The resultant URL.
*/
export default function prefixUrl(value) {
const s = value == null ? '' : String(value);
return prefix + s;
}
Loading

0 comments on commit 2226744

Please sign in to comment.