Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 30 additions & 50 deletions __tests__/util/itinerary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,34 @@ describe('util > itinerary', () => {
}
const rentalCarLeg = {
mode: 'CAR_RENT',
rentedCar: true
// Note: OTP2 sets rentedBike to true for all rented vehicles, including rented cars.
rentedBike: true
}
const rentalMicromobilityLeg = {
mode: 'MICROMOBILITY_RENT',
rentedVehicle: true
// Note: OTP2 sets rentedBike to true for all rented vehicles, including rented scooters.
rentedBike: true
}
const rideHailLeg = {
hailedCar: true,
mode: 'CAR_HAIL'
mode: 'CAR_HAIL',
rideHailingEstimate: {
arrival: 'PT4M',
maxPrice: {
amount: 19,
currency: {
code: 'USD'
}
},
minPrice: {
amount: 17,
currency: {
code: 'USD'
}
},
provider: {
id: 'ride-hail-platform'
}
}
}

const testCases = [
Expand Down Expand Up @@ -66,7 +85,7 @@ describe('util > itinerary', () => {
legs: [walkLeg, rentalBikeLeg]
},
title:
'should be true for an itinerary without transit and without rentals.'
'should be false for an itinerary without transit and with a rented bike.'
},
{
expected: false,
Expand Down Expand Up @@ -116,73 +135,34 @@ describe('util > itinerary', () => {
})
})
describe('getItineraryDefaultMonitoredDays', () => {
const transitLegWeekday = {
mode: 'BUS',
serviceDate: '20210609', // Wednesday
transitLeg: true
}
const transitLegSaturday = {
mode: 'BUS',
serviceDate: '20210612', // Saturday
transitLeg: true
}
const transitLegSunday = {
mode: 'BUS',
serviceDate: '20210613', // Sunday
transitLeg: true
}
const THURSDAY_20210610_1218_EDT = 1623341891000
const SATURDAY_20210612_1218_EDT = 1623514691000
const SUNDAY_20210613_1218_EDT = 1623601091000

const testCases = [
{
expected: WEEKDAYS,
itinerary: {
legs: [walkLeg, transitLegWeekday]
startTime: THURSDAY_20210610_1218_EDT
},
title:
"should be ['monday' thru 'friday'] for an itinerary starting on a weekday."
},
{
expected: WEEKEND_DAYS,
itinerary: {
legs: [walkLeg, transitLegSaturday]
startTime: SATURDAY_20210612_1218_EDT
},
title:
"should be ['saturday', 'sunday'] for an itinerary starting on a Saturday."
},
{
expected: WEEKEND_DAYS,
itinerary: {
legs: [walkLeg, transitLegSunday]
startTime: SUNDAY_20210613_1218_EDT
},
title:
"should be ['saturday', 'sunday'] for an itinerary starting on a Sunday."
},
{
expected: WEEKDAYS,
itinerary: {
legs: [walkLeg],
startTime: 1623341891000 // Thursday 2021-06-10 12:18 pm EDT
},
title:
"should be ['monday' thru 'friday'] for an itinerary without transit starting on a weekday (fallback case)."
},
{
expected: WEEKEND_DAYS,
itinerary: {
legs: [walkLeg],
startTime: 1623514691000 // Saturday 2021-06-12 12:18 pm EDT
},
title:
"should be ['saturday', 'sunday'] for an itinerary without transit starting on a Saturday (fallback case)."
},
{
expected: WEEKEND_DAYS,
itinerary: {
legs: [walkLeg],
startTime: 1623601091000 // Sunday 2021-06-13 12:18 pm EDT
},
title:
"should be ['saturday', 'sunday'] for an itinerary without transit starting on a Sunday (fallback case)."
}
]

Expand Down
788 changes: 787 additions & 1 deletion a11y/mocks/plan.json

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions lib/actions/plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
import { toDate, utcToZonedTime } from 'date-fns-tz'

import { getActiveItineraries, getActiveItinerary } from '../util/state'
import { getFirstStopId } from '../util/itinerary'
import { getServiceStart, SERVICE_BREAK } from '../util/api'

import { routingQuery } from './api'
Expand Down Expand Up @@ -41,20 +40,12 @@ function getActiveItineraryOrFirstFound(state) {
* Shifts the planning start/end date/time to the specified values.
*/
function shiftPlan(departArrive, zonedDate, time, itinerary) {
return function (dispatch, getState) {
const state = getState()
const { api } = state.otp.config
return function (dispatch) {
const params = {
date: format(zonedDate, OTP_API_DATE_FORMAT),
departArrive,
time
}
if (!api?.v2) {
if (!itinerary) {
itinerary = getActiveItineraryOrFirstFound(state)
}
params.startTransitStopId = getFirstStopId(itinerary)
}
dispatch(updateParamsAndPlan(params))
}
}
Expand Down
10 changes: 4 additions & 6 deletions lib/components/narrative/save-trip-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Ban } from '@styled-icons/fa-solid/Ban'
import { connect } from 'react-redux'
import { FormattedMessage, useIntl } from 'react-intl'
import { Itinerary } from '@opentripplanner/types'
import { Lock } from '@styled-icons/fa-solid/Lock'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { PlusCircle } from '@styled-icons/fa-solid/PlusCircle'
Expand All @@ -10,16 +11,13 @@ import { AppReduxState } from '../../util/state-types'
import { CREATE_TRIP_PATH } from '../../util/constants'
import { getActiveItinerary } from '../../util/state'
import { IconWithText } from '../util/styledIcon'
import {
itineraryCanBeMonitored,
ItineraryWithOtp1HailedCar
} from '../../util/itinerary'
import { itineraryCanBeMonitored } from '../../util/itinerary'
import { PersistenceConfig } from '../../util/config-types'
import { UnstyledLink } from '../user/styled'
import { User } from '../user/types'

interface Props {
itinerary?: ItineraryWithOtp1HailedCar
itinerary?: Itinerary
loggedInUser?: User
persistence?: PersistenceConfig
}
Expand Down Expand Up @@ -106,7 +104,7 @@ const SaveTripButton = ({
const mapStateToProps = (state: AppReduxState) => {
const { persistence } = state.otp.config
return {
itinerary: getActiveItinerary(state) as ItineraryWithOtp1HailedCar,
itinerary: getActiveItinerary(state) as Itinerary,
loggedInUser: state.user.loggedInUser,
persistence
}
Expand Down
60 changes: 10 additions & 50 deletions lib/util/itinerary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Place
} from '@opentripplanner/types'
import { isTransitLeg } from '@opentripplanner/core-utils/lib/itinerary'
import { toDate, utcToZonedTime } from 'date-fns-tz'
import { utcToZonedTime } from 'date-fns-tz'
import coreUtils from '@opentripplanner/core-utils'
import hash from 'object-hash'
import memoize from 'lodash.memoize'
Expand All @@ -21,15 +21,6 @@ export interface ItineraryStartTime {
realtime: boolean
}

// FIXME: replace with OTP2 logic.
interface LegWithOtp1HailedCar extends Leg {
hailedCar?: boolean
}

export interface ItineraryWithOtp1HailedCar extends Itinerary {
legs: LegWithOtp1HailedCar[]
}

interface OtpResponse {
plan: {
itineraries: Itinerary[]
Expand Down Expand Up @@ -69,14 +60,11 @@ interface RelaxedFareProductSelector {
* @returns true if an itinerary has no rental or ride hail leg (e.g. CAR_RENT, CAR_HAIL, BICYCLE_RENT, etc.).
* (We use the corresponding fields returned by OTP to get transit legs and rental/ride hail legs.)
*/
export function itineraryCanBeMonitored(
itinerary?: ItineraryWithOtp1HailedCar
): boolean {
export function itineraryCanBeMonitored(itinerary?: Itinerary): boolean {
return (
!!itinerary?.legs &&
!itinerary.legs.some(
(leg: LegWithOtp1HailedCar) =>
leg.rentedBike || leg.rentedCar || leg.rentedVehicle || leg.hailedCar
(leg: Leg) => leg.rentedBike || !!leg.rideHailingEstimate
)
)
}
Expand All @@ -85,43 +73,19 @@ export function getMinutesUntilItineraryStart(itinerary: Itinerary): number {
return differenceInMinutes(new Date(itinerary.startTime), new Date())
}

/**
* Gets the first transit leg of the given itinerary, or null if none found.
*/
function getFirstTransitLeg(itinerary: Itinerary) {
return itinerary?.legs?.find(isTransitLeg)
}

/**
* Get the first stop ID from the itinerary in the underscore format required by
* the startTransitStopId query param (e.g., TRIMET_12345 instead of TRIMET:12345).
*/
export function getFirstStopId(itinerary: Itinerary): string | undefined {
return getFirstTransitLeg(itinerary)?.from.stopId?.replace(':', '_')
}

/**
* Returns the set of monitored days that will be initially shown to the user
* for the given itinerary.
* @param itinerary The itinerary from which the default monitored days are extracted.
* @returns ['monday' thru 'friday'] if itinerary happens on a weekday(*),
* ['saturday', 'sunday'] if itinerary happens on a saturday/sunday(*).
* (*) For transit itineraries, the first transit leg is used to make
* the determination. Otherwise, the itinerary startTime is used.
* @returns ['monday' thru 'friday'] if itinerary happens on a weekday,
* ['saturday', 'sunday'] if itinerary happens on a saturday/sunday,
* based on the itinerary startTime.
*/
export function getItineraryDefaultMonitoredDays(
itinerary: Itinerary,
timeZone = coreUtils.time.getUserTimezone()
): string[] {
const firstTransitLeg = getFirstTransitLeg(itinerary)
// firstTransitLeg should be non-null because only transit trips can be monitored at this time.
// - using serviceDate covers legs that start past midnight.
// - The format of serviceDate can either be 'yyyyMMdd' (OTP v1) or 'yyyy-MM-dd' (OTP v2)
// and both formats are correctly handled by toDate from date-fns-tz.
const startDate = firstTransitLeg
? toDate(firstTransitLeg.serviceDate || '', { timeZone })
: utcToZonedTime(new Date(itinerary.startTime), timeZone)

const startDate = utcToZonedTime(new Date(itinerary.startTime), timeZone)
const dayOfWeek = startDate.getDay()
return dayOfWeek === 0 || dayOfWeek === 6 ? WEEKEND_DAYS : WEEKDAYS
}
Expand Down Expand Up @@ -172,14 +136,10 @@ export function sortStartTimes(
}

// Ignore certain keys that could add significant calculation time to hashing.
// The alerts are irrelevant, but the intermediateStops, interStopGeometry and
// The alerts are irrelevant, but the intermediateStops, legGeometry and
// steps could have the legGeometry substitute as an equivalent hash value
const blackListedKeys = [
'alerts',
'intermediateStops',
'interStopGeometry',
'steps'
]
const blackListedKeys = ['alerts', 'intermediateStops', 'legGeometry', 'steps']

// make blackListedKeys into an object due to superior lookup performance
const blackListedKeyLookup: Record<string, boolean> = {}
blackListedKeys.forEach((key) => {
Expand Down
Loading
Loading