Skip to content

Commit

Permalink
Merge branch 'dev' into optional-lateness
Browse files Browse the repository at this point in the history
  • Loading branch information
miles-grant-ibigroup authored Oct 23, 2023
2 parents 445637b + c628631 commit dd60674
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 15 deletions.
19 changes: 18 additions & 1 deletion __tests__/util/state.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
/* globals describe, expect, it */

import '../test-utils/mock-window-url'
import { queryIsValid } from '../../lib/util/state'
import { isValidSubsequence, queryIsValid } from '../../lib/util/state'

describe('util > state', () => {
describe('isValidSubsequence', () => {
it('should handle edge cases correctly', () => {
expect(isValidSubsequence([0], [0])).toBe(true)
expect(isValidSubsequence([0], [1])).toBe(false)
expect(isValidSubsequence([], [])).toBe(true)
expect(isValidSubsequence([], [9])).toBe(false)
expect(isValidSubsequence([9], [])).toBe(true)
expect(isValidSubsequence([9], [9, 9])).toBe(false)
expect(isValidSubsequence([9, 9, 9], [9, 9])).toBe(true)
})
it('should handle normal cases correctly', () => {
expect(isValidSubsequence([1, 2, 3, 4, 5], [5, 6, 3])).toBe(false)
expect(isValidSubsequence([1, 2, 3, 4, 5], [2, 3, 4])).toBe(true)
expect(isValidSubsequence([1, 2, 4, 4, 3], [2, 3, 4])).toBe(false)
expect(isValidSubsequence([1, 2, 3, 4, 5], [1, 3, 4])).toBe(false)
})
})
describe('queryIsValid', () => {
const fakeFromLocation = {
lat: 12,
Expand Down
1 change: 1 addition & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ actions:
emailVerificationResent: The email verification message has been resent.
genericError: "An error was encountered: {err}"
itineraryExistenceCheckFailed: Error checking whether your selected trip is possible.
mustAcceptTermsToSavePlace: Please accept the Terms of Use (under My Account) to save locations.
mustBeLoggedInToSavePlace: Please log in to save locations.
placeRemembered: The settings for this place have been saved.
preferencesSaved: Your preferences have been saved.
Expand Down
3 changes: 3 additions & 0 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ actions:
nouveau.
genericError: "Une erreur s'est produite : {err}"
itineraryExistenceCheckFailed: Erreur lors de la vérification de la validité du trajet choisi.
mustAcceptTermsToSavePlace: >-
Veuillez accepter les conditions d'utilisation (dans Mon compte) pour
enregistrer des lieux.
mustBeLoggedInToSavePlace: Veuillez vous connecter pour enregistrer des lieux.
placeRemembered: Les informations pour ce lieu ont été enregistrées.
preferencesSaved: Vos préférences ont été enregistrées.
Expand Down
29 changes: 28 additions & 1 deletion lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { generateModeSettingValues } from '../util/api'
import {
getActiveItineraries,
getActiveItinerary,
isValidSubsequence,
getRouteOperator,

Check failure on line 17 in lib/actions/apiV2.js

View workflow job for this annotation

GitHub Actions / test-build-release

Member 'getRouteOperator' of the import declaration should be sorted alphabetically
queryIsValid
} from '../util/state'
Expand Down Expand Up @@ -655,7 +656,33 @@ export const findRoute = (params) =>

const newRoute = clone(route)
const routePatterns = {}
newRoute.patterns.forEach((pattern) => {

// Sort patterns by length to make algorithm below more efficient
const patternsSortedByLength = newRoute.patterns.sort(
(a, b) => a.stops.length - b.stops.length
)

// Remove all patterns that are subsets of larger patterns
const filteredPatterns = patternsSortedByLength
// Start with the largest for performance
.reverse()
.filter((pattern) => {
// Compare to all other patterns TODO: make this beat O(n^2)
return !patternsSortedByLength.find((p) => {
// Don't compare against ourself
if (p.id === pattern.id) return false

// If our pattern is longer, it's not a subset
if (p.stops.length < pattern.stops.length) return false

return isValidSubsequence(
p.stops.map((s) => s.id),
pattern.stops.map((s) => s.id)
)
})
})

filteredPatterns.forEach((pattern) => {
const patternStops = pattern.stops.map((stop) => {
const color =
stop.routes?.length > 0 &&
Expand Down
33 changes: 21 additions & 12 deletions lib/actions/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -746,20 +746,29 @@ export function rememberPlace(placeTypeLocation, intl) {

if (persistenceMode.isOtpMiddleware) {
if (loggedInUser) {
// For middleware loggedInUsers, this method should only be triggered by the
// 'Save as home' or 'Save as work' links from OTP UI's EndPointOverlay/EndPoint.
const { location } = placeTypeLocation
if (isHomeOrWork(location)) {
// Find the index of the place in the loggedInUser.savedLocations
const placeIndex = loggedInUser.savedLocations.findIndex(
(loc) => loc.type === location.type
)
if (placeIndex > -1) {
// Convert to loggedInUser saved place
return dispatch(
saveUserPlace(convertToPlace(location), placeIndex, intl)
if (loggedInUser.hasConsentedToTerms) {
// For middleware loggedInUsers who have accepted the terms of use,
// this method should only be triggered by the
// 'Save as home' or 'Save as work' links from OTP UI's EndPointOverlay/EndPoint.
const { location } = placeTypeLocation
if (isHomeOrWork(location)) {
// Find the index of the place in the loggedInUser.savedLocations
const placeIndex = loggedInUser.savedLocations.findIndex(
(loc) => loc.type === location.type
)
if (placeIndex > -1) {
// Convert to loggedInUser saved place
return dispatch(
saveUserPlace(convertToPlace(location), placeIndex, intl)
)
}
}
} else {
alert(
intl.formatMessage({
id: 'actions.user.mustAcceptTermsToSavePlace'
})
)
}
} else {
alert(
Expand Down
8 changes: 7 additions & 1 deletion lib/components/app/batch-routing-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FormattedMessage, injectIntl, IntlShape } from 'react-intl'
import React, { Component, FormEvent } from 'react'

import { getActiveSearch, getShowUserSettings } from '../../util/state'
import { getPersistenceMode } from '../../util/user'
import BatchSettings from '../form/batch-settings'
import InvisibleA11yLabel from '../util/invisible-a11y-label'
import LocationField from '../form/connected-location-field'
Expand Down Expand Up @@ -108,7 +109,12 @@ class BatchRoutingPanel extends Component<Props> {

// connect to the redux store
const mapStateToProps = (state: any) => {
const showUserSettings = getShowUserSettings(state)
// Show the place shortcuts for OTP-middleware users who have accepted the terms of use
// and deployments using persistence to localStorage. Don't show shortcuts otherwise.
const showUserSettings =
getShowUserSettings(state) &&
(state.user.loggedInUser?.hasConsentedToTerms ||
getPersistenceMode(state.otp.config.persistence).isLocalStorage)
return {
activeSearch: getActiveSearch(state),
showUserSettings
Expand Down
21 changes: 21 additions & 0 deletions lib/util/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,24 @@ export function getOperatorAndRoute(routeObject, transitOperators, intl) {
)
)
}

/**
* Helper method returns true if an array is a subsequence of another.
*
* More efficient than comparing strings as we don't need to look at the entire
* array.
*/
export function isValidSubsequence(array, sequence) {
// Find starting point
let i = 0
let j = 0
while (array[i] !== sequence[j] && i < array.length) {
i = i + 1
}
// We've found the starting point, now we test to see if the rest of the sequence is matched
while (array[i] === sequence[j] && i < array.length && j < sequence.length) {
i = i + 1
j = j + 1
}
return j === sequence.length
}

0 comments on commit dd60674

Please sign in to comment.