Skip to content

Commit ded87b0

Browse files
Merge pull request #167 from opentripplanner/add-trip-rq-history
Log trip requests
2 parents b27e9a6 + ae3c44d commit ded87b0

23 files changed

+708
-508
lines changed

Diff for: example-config.yml

+20-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,30 @@ api:
1616
# name: Oregon Zoo, Portland, OR
1717

1818
### The persistence setting is used to enable the storage of places (home, work),
19-
### recent searches/places, user overrides, and favorite stops. Currently the
20-
### only strategy is localStorage (which is used by default). It also must be
21-
### enabled to show the stored locations (see above).
22-
### TODO: add another server-based strategy
19+
### recent searches/places, user overrides, and favorite stops.
20+
### Pick the strategy that best suits your needs.
21+
###
22+
### If you do not require remote storage of preferences,
23+
### then use the localStorage strategy outlined below (which is used by default).
24+
### The localStorage strategy will use the browser application storage.
25+
### It must be enabled to show the stored locations (see above).
2326
persistence:
2427
enabled: true
2528
strategy: localStorage
2629

30+
### If using the OTP Middleware to store user profiles
31+
### with Auth0 as the authentication mechanism,
32+
### then use the otp_middleware strategy below instead:
33+
# persistence:
34+
# enabled: true
35+
# strategy: otp_middleware
36+
# auth0:
37+
# domain: your-auth0-domain
38+
# clientId: your-auth0-client-id
39+
# otp_middleware:
40+
# apiBaseUrl: https://otp-middleware.example.com
41+
# apiKey: your-middleware-api-key
42+
2743
map:
2844
initLat: 45.52
2945
initLon: -122.682

Diff for: example.js

+17-57
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,18 @@ import { Provider } from 'react-redux'
1111
import thunk from 'redux-thunk'
1212
import createLogger from 'redux-logger'
1313

14-
// Auth0
15-
import { Auth0Provider } from 'use-auth0-hooks'
16-
import { accountLinks, getAuth0Callbacks, getAuth0Config } from './lib/util/auth'
17-
import { AUTH0_SCOPE, URL_ROOT } from './lib/util/constants'
18-
1914
// import Bootstrap Grid components for layout
20-
import { Nav, Navbar, Grid, Row, Col } from 'react-bootstrap'
15+
import { Grid, Row, Col } from 'react-bootstrap'
2116

2217
// import OTP-RR components
2318
import {
24-
AppMenu,
2519
DefaultMainPanel,
20+
DesktopNav,
2621
Map,
2722
MobileMain,
28-
NavLoginButtonAuth0,
2923
ResponsiveWebapp,
30-
createOtpReducer
24+
createOtpReducer,
25+
createUserReducer
3126
} from './lib'
3227
// load the OTP configuration
3328
import otpConfig from './config.yml'
@@ -74,42 +69,19 @@ if (process.env.NODE_ENV === 'development') {
7469
const store = createStore(
7570
combineReducers({
7671
otp: createOtpReducer(otpConfig),
72+
user: createUserReducer(),
7773
router: connectRouter(history)
7874
}),
7975
compose(applyMiddleware(...middleware))
8076
)
8177

82-
// Auth0 config and callbacks.
83-
const auth0Config = getAuth0Config(otpConfig)
84-
const auth0Callbacks = getAuth0Callbacks(store)
85-
8678
// define a simple responsive UI using Bootstrap and OTP-RR
8779
class OtpRRExample extends Component {
8880
render () {
8981
/** desktop view **/
9082
const desktopView = (
9183
<div className='otp'>
92-
<Navbar fluid inverse>
93-
<Navbar.Header>
94-
<Navbar.Brand>
95-
<div style={{ float: 'left', color: 'white', fontSize: 28 }}>
96-
<AppMenu />
97-
</div>
98-
<div className='navbar-title' style={{ marginLeft: 50 }}>OpenTripPlanner</div>
99-
</Navbar.Brand>
100-
</Navbar.Header>
101-
102-
{auth0Config && (
103-
<Navbar.Collapse>
104-
<Nav pullRight>
105-
<NavLoginButtonAuth0
106-
id='login-control'
107-
links={accountLinks}
108-
/>
109-
</Nav>
110-
</Navbar.Collapse>
111-
)}
112-
</Navbar>
84+
<DesktopNav />
11385
<Grid>
11486
<Row className='main-row'>
11587
<Col sm={6} md={4} className='sidebar'>
@@ -144,32 +116,20 @@ class OtpRRExample extends Component {
144116
}
145117
}
146118

147-
const innerProvider = (
148-
<Provider store={store}>
149-
{ /**
119+
// render the app
120+
render(
121+
(
122+
<Provider store={store}>
123+
{ /**
150124
* If not using router history, simply include OtpRRExample here:
151125
* e.g.
152126
* <OtpRRExample />
153127
*/
154-
}
155-
<OtpRRExample />
156-
</Provider>
157-
)
158-
159-
// render the app
160-
render(auth0Config
161-
? (<Auth0Provider
162-
audience={auth0Config.audience}
163-
scope={AUTH0_SCOPE}
164-
domain={auth0Config.domain}
165-
clientId={auth0Config.clientId}
166-
redirectUri={URL_ROOT}
167-
{...auth0Callbacks}
168-
>
169-
{innerProvider}
170-
</Auth0Provider>)
171-
: innerProvider
172-
,
128+
}
129+
<OtpRRExample />
130+
</Provider>
131+
)
132+
,
173133

174-
document.getElementById('root')
134+
document.getElementById('root')
175135
)

Diff for: lib/actions/api.js

+62-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import qs from 'qs'
1010

1111
import { rememberPlace } from './map'
1212
import { getStopViewerConfig, queryIsValid } from '../util/state'
13+
import { getSecureFetchOptions } from '../util/middleware'
14+
1315
if (typeof (fetch) === 'undefined') require('isomorphic-fetch')
1416

1517
const { hasCar } = coreUtils.itinerary
@@ -81,7 +83,9 @@ function getActiveItinerary (otpState) {
8183
*/
8284
export function routingQuery (searchId = null) {
8385
return async function (dispatch, getState) {
84-
const otpState = getState().otp
86+
const state = getState()
87+
const otpState = state.otp
88+
8589
const isNewSearch = !searchId
8690
if (isNewSearch) searchId = randId()
8791
const routingType = otpState.currentQuery.routingType
@@ -95,7 +99,7 @@ export function routingQuery (searchId = null) {
9599

96100
// fetch a realtime route
97101
const query = constructRoutingQuery(otpState)
98-
fetch(query)
102+
fetch(query, getOtpFetchOptions(state))
99103
.then(getJsonAndCheckResponse)
100104
.then(json => {
101105
dispatch(routingResponse({ response: json, searchId }))
@@ -126,8 +130,29 @@ export function routingQuery (searchId = null) {
126130
if (isNewSearch || params.ui_activeSearch !== searchId) {
127131
dispatch(updateOtpUrlParams(otpState, searchId))
128132
}
129-
// also fetch a non-realtime route
130-
fetch(constructRoutingQuery(otpState, true))
133+
134+
// Also fetch a non-realtime route.
135+
//
136+
// FIXME: The statement below may no longer apply with future work
137+
// involving realtime info embedded in the OTP response.
138+
// (That action records an entry again in the middleware.)
139+
// For users who opted in to store trip request history,
140+
// to avoid recording the same trip request twice in the middleware,
141+
// only add the user Authorization token to the request
142+
// when querying the non-realtime route.
143+
//
144+
// The advantage of using non-realtime route is that the middleware will be able to
145+
// record and provide the theoretical itinerary summary without having to query OTP again.
146+
// FIXME: Interestingly, and this could be from a side effect elsewhere,
147+
// when a logged-in user refreshes the page, the trip request in the URL is not recorded again
148+
// (state.user stays unpopulated until after this function is called).
149+
//
150+
const { user } = state
151+
const storeTripHistory = user &&
152+
user.loggedInUser &&
153+
user.loggedInUser.storeTripHistory
154+
155+
fetch(constructRoutingQuery(otpState, true), getOtpFetchOptions(state, storeTripHistory))
131156
.then(getJsonAndCheckResponse)
132157
.then(json => {
133158
// FIXME: This is only performed when ignoring realtimeupdates currently, just
@@ -150,6 +175,39 @@ function getJsonAndCheckResponse (res) {
150175
return res.json()
151176
}
152177

178+
/**
179+
* This method determines the fetch options (including API key and Authorization headers) for the OTP API.
180+
* - If the OTP server is not the middleware server (standalone OTP server),
181+
* an empty object is returned.
182+
* - If the OTP server is the same as the middleware server,
183+
* then an object is returned with the following:
184+
* - A middleware API key, if it has been set in the configuration (it is most likely required),
185+
* - An Auth0 accessToken, when includeToken is true and a user is logged in (userState.loggedInUser is not null).
186+
* This method assumes JSON request bodies.)
187+
*/
188+
function getOtpFetchOptions (state, includeToken = false) {
189+
let apiBaseUrl, apiKey, token
190+
191+
const { api, persistence } = state.otp.config
192+
if (persistence && persistence.otp_middleware) {
193+
({ apiBaseUrl, apiKey } = persistence.otp_middleware)
194+
}
195+
196+
const isOtpServerSameAsMiddleware = apiBaseUrl === api.host
197+
if (isOtpServerSameAsMiddleware) {
198+
if (includeToken && state.user) {
199+
const { accessToken, loggedInUser } = state.user
200+
if (accessToken && loggedInUser) {
201+
token = accessToken
202+
}
203+
}
204+
205+
return getSecureFetchOptions(token, apiKey)
206+
} else {
207+
return {}
208+
}
209+
}
210+
153211
function constructRoutingQuery (otpState, ignoreRealtimeUpdates) {
154212
const { config, currentQuery } = otpState
155213
const routingType = currentQuery.routingType

Diff for: lib/actions/auth.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { push } from 'connected-react-router'
2+
3+
import { setPathBeforeSignIn } from '../actions/user'
4+
5+
/**
6+
* This function is called by the Auth0Provider component, with the described parameter(s),
7+
* when a new access token could not be retrieved.
8+
* @param {Error} err
9+
* @param {AccessTokenRequestOptions} options
10+
*/
11+
export function showAccessTokenError (err, options) {
12+
return function (dispatch, getState) {
13+
// TODO: improve this.
14+
console.error('Failed to retrieve access token: ', err)
15+
}
16+
}
17+
18+
/**
19+
* This function is called by the Auth0Provider component, with the described parameter(s),
20+
* when signing-in fails for some reason.
21+
* @param {Error} err
22+
*/
23+
export function showLoginError (err) {
24+
return function (dispatch, getState) {
25+
// TODO: improve this.
26+
if (err) dispatch(push('/oops'))
27+
}
28+
}
29+
30+
/**
31+
* This function is called by the Auth0Provider component, with the described parameter(s),
32+
* after the user signs in.
33+
* @param {Object} appState The state that was stored when calling useAuth().login().
34+
*/
35+
export function processSignIn (appState) {
36+
return function (dispatch, getState) {
37+
if (appState && appState.urlHash) {
38+
// At this stage after login, Auth0 has already redirected to /signedin (Auth0-whitelisted)
39+
// which shows the AfterLoginScreen.
40+
//
41+
// Here, we save the URL hash prior to login (contains a combination of itinerary search, stop/trip view, etc.),
42+
// so that the AfterLoginScreen can redirect back there when logged-in user info is fetched.
43+
// (For routing, it is easier to deal with the path without the hash sign.)
44+
const hashIndex = appState.urlHash.indexOf('#')
45+
const urlHashWithoutHash = hashIndex >= 0
46+
? appState.urlHash.substr(hashIndex + 1)
47+
: '/'
48+
dispatch(setPathBeforeSignIn(urlHashWithoutHash))
49+
} else if (appState && appState.returnTo) {
50+
// TODO: Handle other after-login situations.
51+
// Note that when redirecting from a login-protected (e.g. account) page while logged out,
52+
// then returnTo is set by Auth0 to this object format:
53+
// {
54+
// pathname: "/"
55+
// query: { ... }
56+
// }
57+
}
58+
}
59+
}

Diff for: lib/actions/ui.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import { setActiveItinerary } from './narrative'
1111
import { getUiUrlParams } from '../util/state'
1212

1313
/**
14-
* Wrapper function for history#push that preserves the current search or, if
14+
* Wrapper function for history#push (or, if specified, replace, etc.)
15+
* that preserves the current search or, if
1516
* replaceSearch is provided (including an empty string), replaces the search
1617
* when routing to a new URL path.
1718
* @param {[type]} url path to route to
1819
* @param {string} replaceSearch optional search string to replace current one
20+
* @param {func} routingMethod the connected-react-router method to execute (defaults to push).
1921
*/
20-
export function routeTo (url, replaceSearch) {
22+
export function routeTo (url, replaceSearch, routingMethod = push) {
2123
return function (dispatch, getState) {
2224
// Get search to preserve when routing to new path.
2325
const { router } = getState()
@@ -28,7 +30,7 @@ export function routeTo (url, replaceSearch) {
2830
} else {
2931
path = `${path}${search}`
3032
}
31-
dispatch(push(path))
33+
dispatch(routingMethod(path))
3234
}
3335
}
3436

0 commit comments

Comments
 (0)