diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 16d6c5e1d..d1e1b82e4 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -1148,7 +1148,11 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } } }) - ?.map(convertGraphQLResponseToLegacy) + ?.map((leg) => ({ + ...convertGraphQLResponseToLegacy(leg), + route: leg.transitLeg ? leg.route : undefined + })), + otp2QueryParams: query.variables }) ) diff --git a/lib/actions/user.js b/lib/actions/user.js index 887fe4e44..1a1e9e993 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -6,6 +6,7 @@ import isEqual from 'lodash.isequal' import qs from 'qs' import toast from 'react-hot-toast' +import { applyRouteModeOverrides } from '../util/itinerary' import { convertToPlace, getPersistenceMode, @@ -129,6 +130,11 @@ export function fetchMonitoredTrips() { 'GET' ) if (status === 'success') { + const { routeModeOverrides } = getState().otp.config + trips.data.forEach((trip) => { + applyRouteModeOverrides(trip.itinerary, routeModeOverrides) + }) + dispatch(setCurrentUserMonitoredTrips(trips.data)) } } @@ -169,6 +175,20 @@ function convertRequestToSearch(config) { } } +/** + * Determines whether two GraphQL sets of variables are for the same trip request/search. + * + * Modes are excluded from the comparison because the UI triggers multiple queries + * with the same GraphQL variables but with different combinations of modes. + * Modes exclusion also means that if someone makes the same search with different mode settings + * (e.g. excludes/adds transit modes), that search will also be combined with the previous ones. + */ +function areRequestsSameExceptModes(qp1, qp2) { + const { modes: modes1, ...otherParams1 } = qp1 + const { modes: modes2, ...otherParams2 } = qp2 + return isEqual(otherParams1, otherParams2) +} + /** * Removes duplicate requests so that only one request is displayed per "batch". */ @@ -176,7 +196,12 @@ function removeDuplicateRequests(filtered, tripRequest) { // Compare one trip request to the next one. if (filtered.length === 0) { filtered.push(tripRequest) - } else if (!isEqual(filtered[filtered.length - 1].query, tripRequest.query)) { + } else if ( + !areRequestsSameExceptModes( + filtered[filtered.length - 1].query, + tripRequest.query + ) + ) { filtered.push(tripRequest) } else { filtered[filtered.length - 1].query.modes.push(...tripRequest.query.modes) diff --git a/lib/components/narrative/metro/default-route-renderer.tsx b/lib/components/narrative/metro/default-route-renderer.tsx index c16d100ad..728ae9036 100644 --- a/lib/components/narrative/metro/default-route-renderer.tsx +++ b/lib/components/narrative/metro/default-route-renderer.tsx @@ -37,10 +37,16 @@ const DefaultRouteRenderer = ({ leg, style }: RouteRendererProps): JSX.Element => { - const routeTitle = leg.routeShortName || leg.routeLongName + const routeTitle = + typeof leg.route === 'object' + ? leg.route.shortName || leg.route.longName + : leg.routeShortName || leg.routeLongName return ( { const { createOrUpdateUserMonitoredTrip, intl, isCreating } = this.props + const tripToSave = { + ...monitoredTrip, + itinerary: copyAndRemoveRouteModeOverrides(monitoredTrip.itinerary) + } + createOrUpdateUserMonitoredTrip( - monitoredTrip, + tripToSave, isCreating, undefined, undefined, @@ -269,7 +279,6 @@ const mapStateToProps = (state, ownProps) => { const tripId = ownProps.match.params.id const { disableSingleItineraryDays } = state.otp.config return { - activeSearchId: state.otp.activeSearchId, disableSingleItineraryDays, homeTimezone: state.otp.config.homeTimezone, isCreating: tripId === 'new', diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index f0617e5d2..be04015dc 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -66,7 +66,8 @@ export type MonitoredTrip = Record & { itinerary: Itinerary itineraryExistence?: ItineraryExistence leadTimeInMinutes: number - queryParams: string + otp2QueryParams: Record + queryParams: Record tripName: string userId: string } diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0bc899936..fa0f082ef 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -6,7 +6,7 @@ import deepmerge from 'deepmerge' import objectPath from 'object-path' import update from 'immutability-helper' -import { checkForRouteModeOverride } from '../util/config' +import { applyRouteModeOverrides } from '../util/itinerary' import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE, @@ -311,18 +311,7 @@ function createOtpReducer(config) { response.requestId = requestId response.plan.itineraries = response.plan?.itineraries?.map( (itinerary) => { - itinerary.legs = itinerary.legs.map((leg) => { - if (leg.routeId) { - leg.mode = checkForRouteModeOverride( - { - id: leg.routeId, - mode: leg.mode - }, - state.config?.routeModeOverrides - ) - } - return leg - }) + applyRouteModeOverrides(itinerary, state.config.routeModeOverrides) return itinerary } ) diff --git a/lib/util/itinerary.tsx b/lib/util/itinerary.tsx index a8f2d1a2b..76f96ec0e 100644 --- a/lib/util/itinerary.tsx +++ b/lib/util/itinerary.tsx @@ -7,6 +7,7 @@ import hash from 'object-hash' import memoize from 'lodash.memoize' import { AppConfig, CO2Config } from './config-types' +import { checkForRouteModeOverride } from './config' import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' export interface ItineraryStartTime { @@ -450,3 +451,41 @@ export function addSortingCosts( totalFare } } + +interface LegWithOriginalMode extends Leg { + originalMode?: string +} + +/** Applies route mode overrides to an itinerary. */ +export function applyRouteModeOverrides( + itinerary: Itinerary, + routeModeOverrides: Record +): void { + itinerary.legs.forEach((leg: LegWithOriginalMode) => { + // Use OTP2 leg route first, fallback on legacy leg routeId. + const routeId = typeof leg.route === 'object' ? leg.route.id : leg.routeId + if (routeId) { + leg.originalMode = leg.mode + leg.mode = checkForRouteModeOverride( + { + id: routeId, + mode: leg.mode + }, + routeModeOverrides + ) + } + }) +} + +/** Remove mode overrides from an itinerary */ +export function copyAndRemoveRouteModeOverrides( + itinerary: Itinerary +): Itinerary { + return { + ...itinerary, + legs: itinerary.legs.map((leg: LegWithOriginalMode) => ({ + ...leg, + mode: leg.originalMode || leg.mode + })) + } +}