diff --git a/example-config.yml b/example-config.yml index cc270ada0..2bae9c943 100644 --- a/example-config.yml +++ b/example-config.yml @@ -310,6 +310,9 @@ itinerary: # Whether the plan first/previous/next/last buttons should be shown along with # plan trip itineraries. showPlanFirstLastButtons: false + # Filters out trips returned by OTP by default, unless specifically requested. + # e.g. filters out walk-only itineraries if user has not explicitly asked for them. + strictItineraryFiltering: false # Whether to render route names and colors in the blocks inside # the batch ui rows renderRouteNamesInBlocks: true diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 3290f9afd..55bacbcc8 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -46,7 +46,8 @@ import { RoutingQueryCallResult } from './api-constants' import { setItineraryView } from './ui' import { zoomToPlace } from './map' -const { generateCombinations, generateOtp2Query } = coreUtils.queryGen +const { generateCombinations, generateOtp2Query, SIMPLIFICATIONS } = + coreUtils.queryGen const { getTripOptionsFromQuery, getUrlParams } = coreUtils.query const { convertGraphQLResponseToLegacy } = coreUtils.itinerary const { randId } = coreUtils.storage @@ -825,6 +826,8 @@ export function routingQuery(searchId = null, updateSearchInReducer) { config?.modes?.initialState?.enabledModeButtons || {} + const strictModes = config?.itinerary?.strictItineraryFiltering + // Filter mode definitions based on active mode keys const activeModeButtons = config.modes?.modeButtons.filter((mb) => activeModeKeys.includes(mb.key) @@ -919,11 +922,31 @@ export function routingQuery(searchId = null, updateSearchInReducer) { routingError, { rewritePayload: (response, dispatch, getState) => { - const withCollapsedShortNames = - response.data?.plan?.itineraries?.map((itin) => ({ + const itineraries = response.data?.plan?.itineraries + + // Convert user-selected transit modes from mode selector into modes recognized by OTP. + const activeModeStrings = activeModes.map( + (am) => SIMPLIFICATIONS[am.mode] + ) + + let filteredItineraries = itineraries + // If "strictItineraryFiltering" is enabled, only return itineraries that contain at least one explicitly requested mode... + if (strictModes) { + filteredItineraries = itineraries.filter((itin) => + itin.legs.some((leg) => + activeModeStrings.includes(SIMPLIFICATIONS[leg.mode]) + ) + ) + // ... Otherwise return all itineraries. + } + + // Filter itineraries to collapse short names and hide unnecessary errors. + const withCollapsedShortNames = filteredItineraries.map( + (itin) => ({ ...itin, legs: itin.legs?.map(convertGraphQLResponseToLegacy) - })) + }) + ) /* It is possible for a NO_TRANSIT_CONNECTION error to be returned even if trips were returned, since it is on a mode-by-mode basis.