From 7ba414dfcf7588f2fdd1d55fbbd3ec0d46531ead Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Tue, 3 Dec 2024 11:54:16 +0000 Subject: [PATCH 01/15] refactor(Initial work to check for major trip changes): --- .../middleware/i18n/Message.java | 3 + .../models/TripMonitorNotification.java | 44 ++++++ .../tripmonitor/jobs/CheckMonitoredTrip.java | 144 ++++++++++++++++++ .../tripmonitor/jobs/NotificationType.java | 5 +- .../triptracker/ManageTripTracking.java | 6 +- .../triptracker/TravelerLocator.java | 11 ++ .../triptracker/TravelerPosition.java | 9 ++ .../middleware/utils/ItineraryUtils.java | 52 +++++-- src/main/resources/Message.properties | 3 + src/main/resources/Message_fr.properties | 3 + 10 files changed, 267 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/i18n/Message.java b/src/main/java/org/opentripplanner/middleware/i18n/Message.java index 1870b5dc1..48b29548b 100644 --- a/src/main/java/org/opentripplanner/middleware/i18n/Message.java +++ b/src/main/java/org/opentripplanner/middleware/i18n/Message.java @@ -19,7 +19,10 @@ public enum Message { ACCEPT_DEPENDENT_EMAIL_SUBJECT, ACCEPT_DEPENDENT_EMAIL_MANAGE, ACCEPT_DEPENDENT_ERROR, + DESTINATION_CHANGE_NOTIFICATION, LABEL_AND_CONTENT, + MODE_CHANGE_NOTIFICATION, + ORIGIN_CHANGE_NOTIFICATION, SMS_STOP_NOTIFICATIONS, TRIP_EMAIL_SUBJECT, TRIP_EMAIL_SUBJECT_FOR_USER, diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index 985970370..17140ec47 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -1,6 +1,7 @@ package org.opentripplanner.middleware.models; import org.opentripplanner.middleware.i18n.Message; +import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.slf4j.Logger; @@ -110,4 +111,47 @@ public static TripMonitorNotification createInitialReminderNotification( ) ); } + + /** + * Creates a notification that the leg has changed mode. + */ + public static TripMonitorNotification createModeChangeNotification(Leg expectedLeg, Leg changedLeg, Locale locale) { + return new TripMonitorNotification( + NotificationType.MODE_CHANGE, + String.format( + Message.MODE_CHANGE_NOTIFICATION.get(locale), + expectedLeg.mode, + changedLeg.mode, + expectedLeg.from.name + ) + ); + } + + /** + * Creates a notification that the origin or destination has changed. + */ + public static TripMonitorNotification createStopChangeNotification( + NotificationType delayType, + Leg expectedLeg, + Leg changedLeg, + Locale locale + ) { + String message = null; + if (delayType == NotificationType.ORIGIN_CHANGE) { + message = String.format( + Message.ORIGIN_CHANGE_NOTIFICATION.get(locale), + expectedLeg.from, + changedLeg.from, + expectedLeg.from.name + ); + } else if (delayType == NotificationType.DESTINATION_CHANGE) { + message = String.format( + Message.DESTINATION_CHANGE_NOTIFICATION.get(locale), + expectedLeg.to, + changedLeg.to, + expectedLeg.to.name + ); + } + return (message != null) ? new TripMonitorNotification(delayType, message) : null; + } } diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index d8c44769f..64d83d38d 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -1,5 +1,6 @@ package org.opentripplanner.middleware.tripmonitor.jobs; +import com.mongodb.lang.Nullable; import org.opentripplanner.middleware.i18n.Message; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; @@ -7,6 +8,7 @@ import org.opentripplanner.middleware.models.TripMonitorAlertNotification; import org.opentripplanner.middleware.models.TripMonitorNotification; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; +import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.tripmonitor.TripStatus; import org.opentripplanner.middleware.otp.OtpDispatcher; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -33,11 +35,13 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.IntStream; import static org.opentripplanner.middleware.utils.I18nUtils.label; @@ -248,6 +252,53 @@ private boolean isTrackingOngoing() { return trip.journeyState.tripStatus == TripStatus.TRIP_ACTIVE && TripTrackingData.getOngoingTrackedJourney(trip.id) != null; } + /** + * Whilst a trip is ongoing check for upcoming major changes and notify the traveler. These checks are specifically + * looking for individual differences between the trip itinerary and the latest OTP response itinerary. + */ + public void checkForMajorTripChanges(Leg nextLeg) { + var otpResponse = getOtpResponse(); + int nextLegIndex = getLegIndex(nextLeg); + if ( + otpResponse == null || + nextLegIndex == -1 || + (!trip.isOneTime() && !isTrackingOngoing() && trip.journeyState.matchingItinerary.hasEnded()) + ) { + // Required criteria to check for major changes not met. + return; + } + + if (!hasMatchingItinerary(otpResponse)) { + // There is a change, workout if it is one of the major changes. + enqueueNotification( + checkTripForMajorChange(NotificationType.MODE_CHANGE, otpResponse, nextLegIndex), + checkTripForMajorChange(NotificationType.ORIGIN_CHANGE, otpResponse, nextLegIndex), + checkTripForMajorChange(NotificationType.DESTINATION_CHANGE, otpResponse, nextLegIndex) + ); + } + sendNotifications(); + } + + /** + * Get the position of the next leg within the itinerary. + */ + private int getLegIndex(Leg nextLeg) { + return IntStream + .range(0, trip.itinerary.legs.size()) + .filter(i -> ItineraryUtils.legsMatch(nextLeg, trip.itinerary.legs.get(i))) + .findFirst() + .orElse(-1); + } + + /** + * Confirm that the OTP response contains an itinerary that matches the trip itinerary. + */ + private boolean hasMatchingItinerary(OtpResponse otpResponse) { + return otpResponse.plan.itineraries + .stream() + .anyMatch(i -> ItineraryUtils.itinerariesMatch(trip.itinerary, i)); + } + /** * Find and set the matching itinerary from the OTP response that matches the monitored trip's stored itinerary if a * match exists. @@ -498,6 +549,99 @@ public TripMonitorNotification checkTripForDelay(NotificationType delayType) { return null; } + /** + * Check for major upcoming change and notify the traveler. + */ + @Nullable + public TripMonitorNotification checkTripForMajorChange( + NotificationType delayType, + OtpResponse otpResponse, + int nextLegIndex + ) { + var itineraryToCheck = getMatchingItinerary(delayType, otpResponse); + if (itineraryToCheck == null) { + // Can not find a match with either narrow or wide exceptions. + return null; + } + + int majorChangeIndex = IntStream + .range(0, itineraryToCheck.legs.size()) + .filter(i -> hasMajorChange(i, delayType, itineraryToCheck)) + .findFirst() + .orElse(-1); + + if (majorChangeIndex == -1 || majorChangeIndex < nextLegIndex) { + // No major changes or the traveler has already passed them. + return null; + } + + // Found a major change. + switch (delayType) { + case MODE_CHANGE: + return TripMonitorNotification.createModeChangeNotification( + trip.itinerary.legs.get(majorChangeIndex), + itineraryToCheck.legs.get(majorChangeIndex), + getOtpUserLocale() + ); + case ORIGIN_CHANGE: + case DESTINATION_CHANGE: + return TripMonitorNotification.createStopChangeNotification( + delayType, + trip.itinerary.legs.get(majorChangeIndex), + itineraryToCheck.legs.get(majorChangeIndex), + getOtpUserLocale() + ); + default: + return null; + } + } + + /** + * Check the appropriate leg for a major change based on the provided exception. + */ + private boolean hasMajorChange(int index, NotificationType exception, Itinerary itineraryToCheck) { + if (exception == NotificationType.MODE_CHANGE) { + return !ItineraryUtils.legsModeMatches(trip.itinerary.legs.get(index), itineraryToCheck.legs.get(index)); + } else if (exception == NotificationType.ORIGIN_CHANGE){ + return !ItineraryUtils.stopsMatch(trip.itinerary.legs.get(index).from, itineraryToCheck.legs.get(index).from); + } else if (exception == NotificationType.DESTINATION_CHANGE){ + return !ItineraryUtils.stopsMatch(trip.itinerary.legs.get(index).to, itineraryToCheck.legs.get(index).to); + } + return false; + } + + /** + * A list of all notification types that are classed as major changes to an itinerary. + */ + private List getAllMajorChangeNotificationTypes() { + return List.of(NotificationType.MODE_CHANGE, NotificationType.ORIGIN_CHANGE, NotificationType.DESTINATION_CHANGE); + } + + /** + * Attempt to match on just a single exception, else use the wider exception criteria. + */ + @Nullable + private Itinerary getMatchingItinerary(NotificationType delayType, OtpResponse otpResponse) { + Itinerary matchingWithModeException = getMatchingItineraryWithExceptions(otpResponse, List.of(delayType)); + + Itinerary matchingWithAllExceptions = getMatchingItineraryWithExceptions( + otpResponse, + getAllMajorChangeNotificationTypes() + ); + return (matchingWithModeException != null) ? matchingWithModeException : matchingWithAllExceptions; + } + + /** + * Get the first matching itinerary ignoring exceptions. + */ + private Itinerary getMatchingItineraryWithExceptions(OtpResponse otpResponse, List exceptions) { + return otpResponse.plan.itineraries + .stream() + .filter(i -> ItineraryUtils.itinerariesMatch(trip.itinerary, i, exceptions)) + .findFirst() + .orElse(null); + } + /** * Compose a message for any enqueued notifications and send to {@link OtpUser} based on their notification * preferences. diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java index c643e0e11..1dddeea6a 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java @@ -11,5 +11,8 @@ public enum NotificationType { ITINERARY_CHANGED, // TODO ALERT_FOUND, ITINERARY_NOT_FOUND, - INITIAL_REMINDER + INITIAL_REMINDER, + MODE_CHANGE, + ORIGIN_CHANGE, + DESTINATION_CHANGE } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java index f5eee316f..58e1b0e02 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java @@ -60,7 +60,8 @@ private static TrackingResponse doUpdateTracking(Request request, TripTrackingDa TravelerPosition travelerPosition = new TravelerPosition( trackedJourney, tripData.trip.journeyState.matchingItinerary, - Persistence.otpUsers.getById(tripData.trip.userId) + Persistence.otpUsers.getById(tripData.trip.userId), + tripData.trip ); TripStatus tripStatus = TripStatus.getTripStatus(travelerPosition); trackedJourney.lastLocation().tripStatus = tripStatus; @@ -148,7 +149,8 @@ private static EndTrackingResponse completeJourney(TripTrackingData tripData, bo TravelerPosition travelerPosition = new TravelerPosition( tripData.journey, tripData.trip.journeyState.matchingItinerary, - Persistence.otpUsers.getById(tripData.trip.userId) + Persistence.otpUsers.getById(tripData.trip.userId), + tripData.trip ); cancelBusNotification(travelerPosition); TrackedJourney trackedJourney = travelerPosition.trackedJourney; diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java index e444fbe62..fb89ce429 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java @@ -4,6 +4,7 @@ import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.response.Step; +import org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip; import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction; import org.opentripplanner.middleware.triptracker.instruction.GetOffHereTransitInstruction; import org.opentripplanner.middleware.triptracker.instruction.GetOffNextStopTransitInstruction; @@ -16,6 +17,8 @@ import org.opentripplanner.middleware.utils.Coordinates; import org.opentripplanner.middleware.utils.ConvertsToCoordinates; import org.opentripplanner.middleware.utils.DateTimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.time.Duration; @@ -41,6 +44,8 @@ */ public class TravelerLocator { + private static final Logger LOG = LoggerFactory.getLogger(TravelerLocator.class); + public static final int ACCEPTABLE_AHEAD_OF_SCHEDULE_IN_MINUTES = 15; private static final int MIN_TRANSIT_VEHICLE_SPEED = 5; // meters per second. 11.1 mph or 18 km/h. @@ -86,6 +91,12 @@ public static String getInstruction( } } } + + try { + new CheckMonitoredTrip(travelerPosition.trip).checkForMajorTripChanges(travelerPosition.nextLeg); + } catch (Exception e) { + LOG.error("Error encountered while checking for major trip changes.", e); + } return NO_INSTRUCTION; } diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java index 2756f7e3e..8272ab399 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java @@ -1,5 +1,6 @@ package org.opentripplanner.middleware.triptracker; +import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.models.OtpUser; import org.opentripplanner.middleware.models.TrackedJourney; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -48,6 +49,14 @@ public class TravelerPosition { /** The first leg of the trip. **/ public Leg firstLegOfTrip; + /** The trip related to the traveler's position */ + public MonitoredTrip trip; + + public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpUser otpUser, MonitoredTrip trip) { + this(trackedJourney, itinerary, otpUser); + this.trip = trip; + } + public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpUser otpUser) { TrackingLocation lastLocation = trackedJourney.locations.get(trackedJourney.locations.size() - 1); currentTime = lastLocation.timestamp.toInstant(); diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index 9011c6ddc..d4419f4ef 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -11,10 +11,12 @@ import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Route; +import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -144,6 +146,25 @@ public static boolean itinerariesMatch(Itinerary referenceItinerary, Itinerary c return true; } + public static boolean itinerariesMatch(Itinerary referenceItinerary, Itinerary candidateItinerary, List exceptions) { + // Make sure both itineraries are monitorable before continuing. + if (!referenceItinerary.canBeMonitored() || !candidateItinerary.canBeMonitored()) return false; + + // make sure itineraries have same amount of legs + if (referenceItinerary.legs.size() != candidateItinerary.legs.size()) return false; + + // make sure each leg matches + for (int i = 0; i < referenceItinerary.legs.size(); i++) { + Leg referenceItineraryLeg = referenceItinerary.legs.get(i); + Leg candidateItineraryLeg = candidateItinerary.legs.get(i); + + if (!legsMatch(referenceItineraryLeg, candidateItineraryLeg, exceptions)) return false; + } + + // if this point is reached, the itineraries are assumed to match + return true; + } + /** * Checks that the specified itinerary is on the same day as the specified date/time. * @param itinerary the itinerary to check. @@ -175,23 +196,27 @@ private static boolean isAfterServiceStart(ZonedDateTime time) { return time.getHour() >= SERVICE_DAY_START_HOUR; } + public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { + return legsMatch(referenceItineraryLeg, candidateItineraryLeg, Collections.emptyList()); + } /** * Check whether a new leg of an itinerary matches the previous itinerary leg for the purposes of trip monitoring. */ - public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { + public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg, List exceptions) { // for now don't analyze non-transit legs - if (!referenceItineraryLeg.transitLeg) return true; + if (Boolean.FALSE.equals(referenceItineraryLeg.transitLeg)) return true; + + boolean destinationMatches = exceptions.contains(NotificationType.DESTINATION_CHANGE) || stopsMatch(referenceItineraryLeg.to, candidateItineraryLeg.to); + boolean legModeMatches = exceptions.contains(NotificationType.MODE_CHANGE) || legsModeMatches(referenceItineraryLeg, candidateItineraryLeg); + boolean originMatches = exceptions.contains(NotificationType.ORIGIN_CHANGE) || stopsMatch(referenceItineraryLeg.from, candidateItineraryLeg.from); // make sure the same from/to stop are being used - if ( - !stopsMatch(referenceItineraryLeg.from, candidateItineraryLeg.from) || - !stopsMatch(referenceItineraryLeg.to, candidateItineraryLeg.to) - ) { + if (!originMatches || !destinationMatches) { return false; } // Make sure the transit service is the same as perceived by the customer. It is assumed that the transit - // service is the same expereince to a customer if the following conditions are met: + // service is the same experience to a customer if the following conditions are met: // - The modes of transportation are the same // - The agency name of the transit service is the same (or the reference leg had an empty agency name) // - The route's long name is the same (or the reference leg had an empty route long name) @@ -199,7 +224,7 @@ public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinerar // - The headsign is the same (or the reference leg had an empty headsign) // - The leg has the same interlining qualities with the previous leg if ( - !equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode) || + !legModeMatches || !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || @@ -228,16 +253,23 @@ public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinerar return true; } + /** + * Check whether the mode of two legs match. + */ + public static boolean legsModeMatches(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { + return equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode); + } + /** * Checks whether two stops (OTP Places) match for the purposes of matching itineraries */ - private static boolean stopsMatch(Place stopA, Place stopB) { + public static boolean stopsMatch(Place stopA, Place stopB) { // Stop names must match. It's possible in OTP to have a null place name, although it probably won't occur with // transit legs. But just in case this method is expanded in scope to check more stuff about a place, if both // are null, then assume a match. if ( (stopA.name != null && !stopA.name.equalsIgnoreCase(stopB.name)) || - (stopA.name == null && stopB.name != null) + (stopA.name == null && stopB.name != null) ) { return false; } diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index d0b090e68..ab921b84f 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,7 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. +DESTINATION_CHANGE_NOTIFICATION = There has been a destination change from %s to %s for the leg starting at %s. LABEL_AND_CONTENT = %s: %s +MODE_CHANGE_NOTIFICATION = There has been a mode change from %s to %s for the leg starting at %s. +ORIGIN_CHANGE_NOTIFICATION = There has been an origin change from %s to %s for the leg previously starting at %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification TRIP_EMAIL_SUBJECT_FOR_USER = Trip for %s Notification diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index cc147ec24..a344c4829 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,7 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. +DESTINATION_CHANGE_NOTIFICATION = TODO %s to %s TODO %s. LABEL_AND_CONTENT = %s\u00A0: %s +MODE_CHANGE_NOTIFICATION = TODO. +ORIGIN_CHANGE_NOTIFICATION = TODO %s to %s TODO %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s TRIP_EMAIL_SUBJECT_FOR_USER = Notification pour le trajet de %s From 7c466fc3b527678c1bdf157f04522036d4469942 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Tue, 3 Dec 2024 12:08:18 +0000 Subject: [PATCH 02/15] refactor(Updated swagger docs and included the current leg on major changes): --- .../tripmonitor/jobs/CheckMonitoredTrip.java | 2 +- .../latest-spark-swagger-output.yaml | 239 +++++++++--------- 2 files changed, 122 insertions(+), 119 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 64d83d38d..9202da0a1 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -570,7 +570,7 @@ public TripMonitorNotification checkTripForMajorChange( .findFirst() .orElse(-1); - if (majorChangeIndex == -1 || majorChangeIndex < nextLegIndex) { + if (majorChangeIndex == -1 || majorChangeIndex <= (nextLegIndex - 1)) { // No major changes or the traveler has already passed them. return null; } diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index bfae6f238..4fa75e235 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -56,10 +56,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -90,10 +90,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -123,10 +123,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/admin/user: get: tags: @@ -155,10 +155,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/admin/user" @@ -178,10 +178,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -220,10 +220,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -269,10 +269,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -309,10 +309,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -356,10 +356,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -402,10 +402,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -447,10 +447,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TokenHolder" schema: $ref: "#/definitions/TokenHolder" + responseSchema: + $ref: "#/definitions/TokenHolder" /api/secure/application/fromtoken: get: tags: @@ -464,10 +464,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -498,10 +498,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -531,10 +531,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/application: get: tags: @@ -563,10 +563,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/application" @@ -586,10 +586,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -628,10 +628,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -677,10 +677,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -717,10 +717,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -754,10 +754,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -788,10 +788,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -821,10 +821,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/cdp: get: tags: @@ -853,10 +853,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/cdp" @@ -876,10 +876,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -918,10 +918,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -967,10 +967,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1007,10 +1007,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1050,10 +1050,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ItineraryExistence" schema: $ref: "#/definitions/ItineraryExistence" + responseSchema: + $ref: "#/definitions/ItineraryExistence" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1102,10 +1102,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredtrip" @@ -1125,10 +1125,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1167,10 +1167,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1216,10 +1216,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1257,10 +1257,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1298,10 +1298,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/updatetracking: post: tags: @@ -1319,10 +1319,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/track: post: tags: @@ -1340,10 +1340,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/endtracking: post: tags: @@ -1361,10 +1361,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/EndTrackingResponse" schema: $ref: "#/definitions/EndTrackingResponse" + responseSchema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/monitoredtrip/forciblyendtracking: post: tags: @@ -1382,10 +1382,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/EndTrackingResponse" schema: $ref: "#/definitions/EndTrackingResponse" + responseSchema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/triprequests: get: tags: @@ -1430,10 +1430,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TripRequest" schema: $ref: "#/definitions/TripRequest" + responseSchema: + $ref: "#/definitions/TripRequest" /api/secure/monitoredcomponent: get: tags: @@ -1462,10 +1462,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredcomponent" @@ -1485,10 +1485,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1527,10 +1527,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1576,10 +1576,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1617,10 +1617,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1651,10 +1651,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1729,10 +1729,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/VerificationResult" schema: $ref: "#/definitions/VerificationResult" + responseSchema: + $ref: "#/definitions/VerificationResult" /api/secure/user/{id}/verify_sms/{code}: post: tags: @@ -1752,10 +1752,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/VerificationResult" schema: $ref: "#/definitions/VerificationResult" + responseSchema: + $ref: "#/definitions/VerificationResult" /api/secure/user/fromtoken: get: tags: @@ -1769,10 +1769,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1803,10 +1803,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1836,10 +1836,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/user: get: tags: @@ -1868,10 +1868,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/user" @@ -1891,10 +1891,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1933,10 +1933,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1982,10 +1982,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2022,10 +2022,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2079,11 +2079,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/ApiUsageResult" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/ApiUsageResult" @@ -2110,11 +2110,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/BugsnagEvent" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/BugsnagEvent" @@ -2141,11 +2141,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/CDPFile" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/CDPFile" @@ -2166,11 +2166,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/URL" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/URL" @@ -2376,6 +2376,9 @@ definitions: - "ALERT_FOUND" - "ITINERARY_NOT_FOUND" - "INITIAL_REMINDER" + - "MODE_CHANGE" + - "ORIGIN_CHANGE" + - "DESTINATION_CHANGE" body: type: "string" EncodedPolyline: From f9126c478c3cab9794f489a8b4c373a1079ad06d Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Wed, 4 Dec 2024 17:25:31 +0000 Subject: [PATCH 03/15] refactor(Added unit tests and made changes to wording.): --- .../models/TripMonitorNotification.java | 11 +-- .../tripmonitor/jobs/CheckMonitoredTrip.java | 92 +++++++++---------- src/main/resources/Message.properties | 4 +- src/main/resources/Message_fr.properties | 6 +- .../jobs/CheckMonitoredTripTest.java | 55 +++++++++++ 5 files changed, 109 insertions(+), 59 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index 17140ec47..3eaf9e4a8 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -140,16 +140,15 @@ public static TripMonitorNotification createStopChangeNotification( if (delayType == NotificationType.ORIGIN_CHANGE) { message = String.format( Message.ORIGIN_CHANGE_NOTIFICATION.get(locale), - expectedLeg.from, - changedLeg.from, - expectedLeg.from.name + expectedLeg.from.name, + changedLeg.from.name ); } else if (delayType == NotificationType.DESTINATION_CHANGE) { message = String.format( Message.DESTINATION_CHANGE_NOTIFICATION.get(locale), - expectedLeg.to, - changedLeg.to, - expectedLeg.to.name + expectedLeg.from.name, + expectedLeg.to.name, + changedLeg.to.name ); } return (message != null) ? new TripMonitorNotification(delayType, message) : null; diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 9202da0a1..605bed71a 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -271,9 +271,9 @@ public void checkForMajorTripChanges(Leg nextLeg) { if (!hasMatchingItinerary(otpResponse)) { // There is a change, workout if it is one of the major changes. enqueueNotification( - checkTripForMajorChange(NotificationType.MODE_CHANGE, otpResponse, nextLegIndex), - checkTripForMajorChange(NotificationType.ORIGIN_CHANGE, otpResponse, nextLegIndex), - checkTripForMajorChange(NotificationType.DESTINATION_CHANGE, otpResponse, nextLegIndex) + checkTripForMajorChange(NotificationType.MODE_CHANGE, otpResponse.plan.itineraries, nextLegIndex), + checkTripForMajorChange(NotificationType.ORIGIN_CHANGE, otpResponse.plan.itineraries, nextLegIndex), + checkTripForMajorChange(NotificationType.DESTINATION_CHANGE, otpResponse.plan.itineraries, nextLegIndex) ); } sendNotifications(); @@ -555,45 +555,43 @@ public TripMonitorNotification checkTripForDelay(NotificationType delayType) { @Nullable public TripMonitorNotification checkTripForMajorChange( NotificationType delayType, - OtpResponse otpResponse, + List itineraries, int nextLegIndex ) { - var itineraryToCheck = getMatchingItinerary(delayType, otpResponse); - if (itineraryToCheck == null) { - // Can not find a match with either narrow or wide exceptions. - return null; - } - - int majorChangeIndex = IntStream - .range(0, itineraryToCheck.legs.size()) - .filter(i -> hasMajorChange(i, delayType, itineraryToCheck)) - .findFirst() - .orElse(-1); - - if (majorChangeIndex == -1 || majorChangeIndex <= (nextLegIndex - 1)) { - // No major changes or the traveler has already passed them. - return null; - } - - // Found a major change. - switch (delayType) { - case MODE_CHANGE: - return TripMonitorNotification.createModeChangeNotification( - trip.itinerary.legs.get(majorChangeIndex), - itineraryToCheck.legs.get(majorChangeIndex), - getOtpUserLocale() - ); - case ORIGIN_CHANGE: - case DESTINATION_CHANGE: - return TripMonitorNotification.createStopChangeNotification( - delayType, - trip.itinerary.legs.get(majorChangeIndex), - itineraryToCheck.legs.get(majorChangeIndex), - getOtpUserLocale() - ); - default: + var itineraryToCheck = getMatchingItinerary(delayType, itineraries); + if (itineraryToCheck != null) { + int majorChangeIndex = IntStream + .range(0, itineraryToCheck.legs.size()) + .filter(i -> hasMajorChange(i, delayType, itineraryToCheck)) + .findFirst() + .orElse(-1); + + if (majorChangeIndex == -1 || majorChangeIndex <= (nextLegIndex - 1)) { + // No major change or the traveler has already passed it. return null; + } + + // Found a major change. + switch (delayType) { + case MODE_CHANGE: + return TripMonitorNotification.createModeChangeNotification( + trip.itinerary.legs.get(majorChangeIndex), + itineraryToCheck.legs.get(majorChangeIndex), + getOtpUserLocale() + ); + case ORIGIN_CHANGE: + case DESTINATION_CHANGE: + return TripMonitorNotification.createStopChangeNotification( + delayType, + trip.itinerary.legs.get(majorChangeIndex), + itineraryToCheck.legs.get(majorChangeIndex), + getOtpUserLocale() + ); + default: + return null; + } } + return null; } /** @@ -621,21 +619,19 @@ private List getAllMajorChangeNotificationTypes() { * Attempt to match on just a single exception, else use the wider exception criteria. */ @Nullable - private Itinerary getMatchingItinerary(NotificationType delayType, OtpResponse otpResponse) { - Itinerary matchingWithModeException = getMatchingItineraryWithExceptions(otpResponse, List.of(delayType)); - - Itinerary matchingWithAllExceptions = getMatchingItineraryWithExceptions( - otpResponse, - getAllMajorChangeNotificationTypes() - ); - return (matchingWithModeException != null) ? matchingWithModeException : matchingWithAllExceptions; + private Itinerary getMatchingItinerary(NotificationType delayType, List itineraries) { + Itinerary matchingWithSingleException = getMatchingItineraryWithExceptions(itineraries, List.of(delayType)); + if (matchingWithSingleException != null) { + return matchingWithSingleException; + } + return getMatchingItineraryWithExceptions(itineraries, getAllMajorChangeNotificationTypes()); } /** * Get the first matching itinerary ignoring exceptions. */ - private Itinerary getMatchingItineraryWithExceptions(OtpResponse otpResponse, List exceptions) { - return otpResponse.plan.itineraries + private Itinerary getMatchingItineraryWithExceptions(List itineraries, List exceptions) { + return itineraries .stream() .filter(i -> ItineraryUtils.itinerariesMatch(trip.itinerary, i, exceptions)) .findFirst() diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index ab921b84f..f7414a17c 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. -DESTINATION_CHANGE_NOTIFICATION = There has been a destination change from %s to %s for the leg starting at %s. +DESTINATION_CHANGE_NOTIFICATION = The destination for the leg starting at %s has changed from %s to %s. LABEL_AND_CONTENT = %s: %s MODE_CHANGE_NOTIFICATION = There has been a mode change from %s to %s for the leg starting at %s. -ORIGIN_CHANGE_NOTIFICATION = There has been an origin change from %s to %s for the leg previously starting at %s. +ORIGIN_CHANGE_NOTIFICATION = The leg previously starting at %s will now start at %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification TRIP_EMAIL_SUBJECT_FOR_USER = Trip for %s Notification diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index a344c4829..b10612eac 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. -DESTINATION_CHANGE_NOTIFICATION = TODO %s to %s TODO %s. +DESTINATION_CHANGE_NOTIFICATION = TODO %s TODO %s TODO %s. LABEL_AND_CONTENT = %s\u00A0: %s -MODE_CHANGE_NOTIFICATION = TODO. -ORIGIN_CHANGE_NOTIFICATION = TODO %s to %s TODO %s. +MODE_CHANGE_NOTIFICATION = TODO %s TODO %s TODO %s. +ORIGIN_CHANGE_NOTIFICATION = TODO %s TODO %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s TRIP_EMAIL_SUBJECT_FOR_USER = Notification pour le trajet de %s diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 9bc9a3eb6..165faef11 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -214,6 +214,61 @@ private static Stream createDelayNotificationTestCases() { ); } + @ParameterizedTest + @MethodSource("createMajorChangeNotificationTestCases") + void testMajorChangeNotifications( + NotificationType exception, + String message, + int nextLegIndex + ) throws Exception { + CheckMonitoredTrip check = createCheckMonitoredTrip(this::mockOtpPlanResponse); + OtpResponse otpResponse = mockOtpPlanResponse(); + Itinerary otpResponseItinerary = getItineraryWithMajorChangeApplied(otpResponse, exception); + TripMonitorNotification notification = check.checkTripForMajorChange(exception, List.of(otpResponseItinerary), nextLegIndex); + if (message != null) { + assertNotNull(notification); + assertEquals(message, notification.body); + } + } + + private static Stream createMajorChangeNotificationTestCases() { + return Stream.of( + Arguments.of( + NotificationType.MODE_CHANGE, + "There has been a mode change from TRAM to BUS for the leg starting at Providence Park MAX Station.", + 1 + ), + Arguments.of(NotificationType.MODE_CHANGE, null, 2 /* Traveler already passed the mode change. */), + Arguments.of( + NotificationType.ORIGIN_CHANGE, + "The leg previously starting at Providence Park MAX Station will now start at Library/SW 9th Ave.", + 1 + ), + Arguments.of( + NotificationType.DESTINATION_CHANGE, + "The destination for the leg starting at Providence Park MAX Station has changed from Pioneer Square South MAX Station to Library/SW 9th Ave.", + 1 + ) + ); + } + + /** + * Alter the itinerary to include a change which is classed as a major change. + */ + private Itinerary getItineraryWithMajorChangeApplied(OtpResponse otpResponse, NotificationType exception) { + Itinerary otpResponseItinerary = otpResponse.plan.itineraries.get(0); + if (exception == NotificationType.MODE_CHANGE) { + otpResponseItinerary.legs.get(1).mode = "BUS"; + } + if (exception == NotificationType.ORIGIN_CHANGE) { + otpResponseItinerary.legs.get(1).from.name = "Library/SW 9th Ave"; + } + if (exception == NotificationType.DESTINATION_CHANGE) { + otpResponseItinerary.legs.get(1).to.name = "Library/SW 9th Ave"; + } + return otpResponseItinerary; + } + /** * Convenience method for creating a CheckMonitoredTrip instance with the default journey state. */ From 75f44c8085224dc1cf9e7850ce996eca97d59d0a Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Mon, 9 Dec 2024 16:39:21 +0000 Subject: [PATCH 04/15] refactor(Various updates to monitor leg transition): --- .../middleware/i18n/Message.java | 4 +- .../models/TripMonitorNotification.java | 67 +++++----- .../tripmonitor/jobs/CheckMonitoredTrip.java | 121 +++--------------- .../tripmonitor/jobs/NotificationType.java | 6 +- .../triptracker/ManageTripTracking.java | 8 +- .../triptracker/TravelerLocator.java | 49 ++++++- .../triptracker/TravelerPosition.java | 16 +-- .../middleware/utils/ItineraryUtils.java | 76 ++++------- src/main/resources/Message.properties | 6 +- src/main/resources/Message_fr.properties | 6 +- .../jobs/CheckMonitoredTripTest.java | 66 ++++------ 11 files changed, 169 insertions(+), 256 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/i18n/Message.java b/src/main/java/org/opentripplanner/middleware/i18n/Message.java index 48b29548b..4c440227f 100644 --- a/src/main/java/org/opentripplanner/middleware/i18n/Message.java +++ b/src/main/java/org/opentripplanner/middleware/i18n/Message.java @@ -19,10 +19,10 @@ public enum Message { ACCEPT_DEPENDENT_EMAIL_SUBJECT, ACCEPT_DEPENDENT_EMAIL_MANAGE, ACCEPT_DEPENDENT_ERROR, - DESTINATION_CHANGE_NOTIFICATION, + ARRIVED_NOTIFICATION, + DEPARTED_NOTIFICATION, LABEL_AND_CONTENT, MODE_CHANGE_NOTIFICATION, - ORIGIN_CHANGE_NOTIFICATION, SMS_STOP_NOTIFICATIONS, TRIP_EMAIL_SUBJECT, TRIP_EMAIL_SUBJECT_FOR_USER, diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index 3eaf9e4a8..faaca35f3 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -1,8 +1,8 @@ package org.opentripplanner.middleware.models; import org.opentripplanner.middleware.i18n.Message; -import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; +import org.opentripplanner.middleware.triptracker.TravelerPosition; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -113,44 +113,51 @@ public static TripMonitorNotification createInitialReminderNotification( } /** - * Creates a notification that the leg has changed mode. + * Creates a departed notification. */ - public static TripMonitorNotification createModeChangeNotification(Leg expectedLeg, Leg changedLeg, Locale locale) { + public static TripMonitorNotification createDepartedNotification( + NotificationType legTransitionType, + TravelerPosition travelerPosition + ) { return new TripMonitorNotification( - NotificationType.MODE_CHANGE, + legTransitionType, String.format( - Message.MODE_CHANGE_NOTIFICATION.get(locale), - expectedLeg.mode, - changedLeg.mode, - expectedLeg.from.name + Message.DEPARTED_NOTIFICATION.get(travelerPosition.locale), + travelerPosition.expectedLeg.from.name ) ); } /** - * Creates a notification that the origin or destination has changed. + * Creates a mode change notification. */ - public static TripMonitorNotification createStopChangeNotification( - NotificationType delayType, - Leg expectedLeg, - Leg changedLeg, - Locale locale + public static TripMonitorNotification createModeChangeNotification( + NotificationType legTransitionType, + TravelerPosition travelerPosition ) { - String message = null; - if (delayType == NotificationType.ORIGIN_CHANGE) { - message = String.format( - Message.ORIGIN_CHANGE_NOTIFICATION.get(locale), - expectedLeg.from.name, - changedLeg.from.name - ); - } else if (delayType == NotificationType.DESTINATION_CHANGE) { - message = String.format( - Message.DESTINATION_CHANGE_NOTIFICATION.get(locale), - expectedLeg.from.name, - expectedLeg.to.name, - changedLeg.to.name - ); - } - return (message != null) ? new TripMonitorNotification(delayType, message) : null; + return new TripMonitorNotification( + legTransitionType, + String.format( + Message.MODE_CHANGE_NOTIFICATION.get(travelerPosition.locale), + travelerPosition.expectedLeg.mode, + travelerPosition.nextLeg.mode + ) + ); + } + + /** + * Creates an arrived notification. + */ + public static TripMonitorNotification createArrivedNotification( + NotificationType legTransitionType, + TravelerPosition travelerPosition + ) { + return new TripMonitorNotification( + legTransitionType, + String.format( + Message.ARRIVED_NOTIFICATION.get(travelerPosition.locale), + travelerPosition.expectedLeg.to.name + ) + ); } } diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index f4e2b979d..889004e48 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -16,6 +16,7 @@ import org.opentripplanner.middleware.otp.response.OtpResponse; import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.tripmonitor.JourneyState; +import org.opentripplanner.middleware.triptracker.TravelerPosition; import org.opentripplanner.middleware.triptracker.TripTrackingData; import org.opentripplanner.middleware.utils.ConfigUtils; import org.opentripplanner.middleware.utils.DateTimeUtils; @@ -253,29 +254,9 @@ private boolean isTrackingOngoing() { return trip.journeyState.tripStatus == TripStatus.TRIP_ACTIVE && TripTrackingData.getOngoingTrackedJourney(trip.id) != null; } - /** - * Whilst a trip is ongoing check for upcoming major changes and notify the traveler. These checks are specifically - * looking for individual differences between the trip itinerary and the latest OTP response itinerary. - */ - public void checkForMajorTripChanges(Leg nextLeg) { - var otpResponse = getOtpResponse(); - int nextLegIndex = getLegIndex(nextLeg); - if ( - otpResponse == null || - nextLegIndex == -1 || - (!trip.isOneTime() && !isTrackingOngoing() && trip.journeyState.matchingItinerary.hasEnded()) - ) { - // Required criteria to check for major changes not met. - return; - } - - if (!hasMatchingItinerary(otpResponse)) { - // There is a change, workout if it is one of the major changes. - enqueueNotification( - checkTripForMajorChange(NotificationType.MODE_CHANGE, otpResponse.plan.itineraries, nextLegIndex), - checkTripForMajorChange(NotificationType.ORIGIN_CHANGE, otpResponse.plan.itineraries, nextLegIndex), - checkTripForMajorChange(NotificationType.DESTINATION_CHANGE, otpResponse.plan.itineraries, nextLegIndex) - ); + public void processLegTransition(List legTransitionTypes , TravelerPosition travelerPosition) { + for (NotificationType legTransitionType : legTransitionTypes) { + enqueueNotification(createLegTransitionNotification(legTransitionType, travelerPosition)); } sendNotifications(); } @@ -550,93 +531,21 @@ public TripMonitorNotification checkTripForDelay(NotificationType delayType) { return null; } - /** - * Check for major upcoming change and notify the traveler. - */ @Nullable - public TripMonitorNotification checkTripForMajorChange( - NotificationType delayType, - List itineraries, - int nextLegIndex + public TripMonitorNotification createLegTransitionNotification( + NotificationType legTransitionType, + TravelerPosition travelerPosition ) { - var itineraryToCheck = getMatchingItinerary(delayType, itineraries); - if (itineraryToCheck != null) { - int majorChangeIndex = IntStream - .range(0, itineraryToCheck.legs.size()) - .filter(i -> hasMajorChange(i, delayType, itineraryToCheck)) - .findFirst() - .orElse(-1); - - if (majorChangeIndex == -1 || majorChangeIndex <= (nextLegIndex - 1)) { - // No major change or the traveler has already passed it. + switch (legTransitionType) { + case MODE_CHANGE_NOTIFICATION: + return TripMonitorNotification.createModeChangeNotification(legTransitionType, travelerPosition); + case DEPARTED_NOTIFICATION: + return TripMonitorNotification.createDepartedNotification(legTransitionType, travelerPosition); + case ARRIVED_NOTIFICATION: + return TripMonitorNotification.createArrivedNotification(legTransitionType, travelerPosition); + default: return null; - } - - // Found a major change. - switch (delayType) { - case MODE_CHANGE: - return TripMonitorNotification.createModeChangeNotification( - trip.itinerary.legs.get(majorChangeIndex), - itineraryToCheck.legs.get(majorChangeIndex), - getOtpUserLocale() - ); - case ORIGIN_CHANGE: - case DESTINATION_CHANGE: - return TripMonitorNotification.createStopChangeNotification( - delayType, - trip.itinerary.legs.get(majorChangeIndex), - itineraryToCheck.legs.get(majorChangeIndex), - getOtpUserLocale() - ); - default: - return null; - } } - return null; - } - - /** - * Check the appropriate leg for a major change based on the provided exception. - */ - private boolean hasMajorChange(int index, NotificationType exception, Itinerary itineraryToCheck) { - if (exception == NotificationType.MODE_CHANGE) { - return !ItineraryUtils.legsModeMatches(trip.itinerary.legs.get(index), itineraryToCheck.legs.get(index)); - } else if (exception == NotificationType.ORIGIN_CHANGE){ - return !ItineraryUtils.stopsMatch(trip.itinerary.legs.get(index).from, itineraryToCheck.legs.get(index).from); - } else if (exception == NotificationType.DESTINATION_CHANGE){ - return !ItineraryUtils.stopsMatch(trip.itinerary.legs.get(index).to, itineraryToCheck.legs.get(index).to); - } - return false; - } - - /** - * A list of all notification types that are classed as major changes to an itinerary. - */ - private List getAllMajorChangeNotificationTypes() { - return List.of(NotificationType.MODE_CHANGE, NotificationType.ORIGIN_CHANGE, NotificationType.DESTINATION_CHANGE); - } - - /** - * Attempt to match on just a single exception, else use the wider exception criteria. - */ - @Nullable - private Itinerary getMatchingItinerary(NotificationType delayType, List itineraries) { - Itinerary matchingWithSingleException = getMatchingItineraryWithExceptions(itineraries, List.of(delayType)); - if (matchingWithSingleException != null) { - return matchingWithSingleException; - } - return getMatchingItineraryWithExceptions(itineraries, getAllMajorChangeNotificationTypes()); - } - - /** - * Get the first matching itinerary ignoring exceptions. - */ - private Itinerary getMatchingItineraryWithExceptions(List itineraries, List exceptions) { - return itineraries - .stream() - .filter(i -> ItineraryUtils.itinerariesMatch(trip.itinerary, i, exceptions)) - .findFirst() - .orElse(null); } /** diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java index 1dddeea6a..62d0fa422 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java @@ -12,7 +12,7 @@ public enum NotificationType { ALERT_FOUND, ITINERARY_NOT_FOUND, INITIAL_REMINDER, - MODE_CHANGE, - ORIGIN_CHANGE, - DESTINATION_CHANGE + MODE_CHANGE_NOTIFICATION, + DEPARTED_NOTIFICATION, + ARRIVED_NOTIFICATION } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java index 58e1b0e02..ebf280b6d 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java @@ -60,8 +60,7 @@ private static TrackingResponse doUpdateTracking(Request request, TripTrackingDa TravelerPosition travelerPosition = new TravelerPosition( trackedJourney, tripData.trip.journeyState.matchingItinerary, - Persistence.otpUsers.getById(tripData.trip.userId), - tripData.trip + Persistence.otpUsers.getById(tripData.trip.userId) ); TripStatus tripStatus = TripStatus.getTripStatus(travelerPosition); trackedJourney.lastLocation().tripStatus = tripStatus; @@ -77,6 +76,8 @@ private static TrackingResponse doUpdateTracking(Request request, TripTrackingDa ); } + TravelerLocator.checkForLegTransition(tripStatus, travelerPosition, tripData.trip); + // Provide response. return new TrackingResponse( TRIP_TRACKING_UPDATE_FREQUENCY_SECONDS, @@ -149,8 +150,7 @@ private static EndTrackingResponse completeJourney(TripTrackingData tripData, bo TravelerPosition travelerPosition = new TravelerPosition( tripData.journey, tripData.trip.journeyState.matchingItinerary, - Persistence.otpUsers.getById(tripData.trip.userId), - tripData.trip + Persistence.otpUsers.getById(tripData.trip.userId) ); cancelBusNotification(travelerPosition); TrackedJourney trackedJourney = travelerPosition.trackedJourney; diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java index fb89ce429..39bd29904 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java @@ -1,10 +1,12 @@ package org.opentripplanner.middleware.triptracker; import io.leonard.PolylineUtils; +import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.response.Step; import org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip; +import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction; import org.opentripplanner.middleware.triptracker.instruction.GetOffHereTransitInstruction; import org.opentripplanner.middleware.triptracker.instruction.GetOffNextStopTransitInstruction; @@ -31,6 +33,9 @@ import java.util.Optional; import java.util.stream.Collectors; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.MODE_CHANGE_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.DEPARTED_NOTIFICATION; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.NO_INSTRUCTION; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_IMMEDIATE_RADIUS; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_UPCOMING_RADIUS; @@ -91,13 +96,47 @@ public static String getInstruction( } } } + return NO_INSTRUCTION; + } - try { - new CheckMonitoredTrip(travelerPosition.trip).checkForMajorTripChanges(travelerPosition.nextLeg); - } catch (Exception e) { - LOG.error("Error encountered while checking for major trip changes.", e); + public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition travelerPosition, MonitoredTrip trip) { + if ( + hasRequiredTripStatus(tripStatus) && + (hasRequiredWalkLeg(travelerPosition) || hasRequiredTransitLeg(travelerPosition)) + ) { + List legTransitionTypes = getLegTransitionTypes(travelerPosition); + if (!legTransitionTypes.isEmpty()) { + try { + new CheckMonitoredTrip(trip).processLegTransition(legTransitionTypes, travelerPosition); + } catch (CloneNotSupportedException e) { + LOG.error("Error encountered while checking leg transition.", e); + } + } } - return NO_INSTRUCTION; + + } + + private static List getLegTransitionTypes(TravelerPosition travelerPosition) { + List notificationTypes = new ArrayList<>(); + if (isAtStartOfLeg(travelerPosition)) { + notificationTypes.add(DEPARTED_NOTIFICATION); + } + if (isAtEndOfLeg(travelerPosition)) { + notificationTypes.add(ARRIVED_NOTIFICATION); + } + if (hasModeChanged(travelerPosition)) { + notificationTypes.add(MODE_CHANGE_NOTIFICATION); + } + return notificationTypes; + } + + /** + * The traveler is at the end of the current leg and the mode has changed between this and the next leg. + */ + private static boolean hasModeChanged(TravelerPosition travelerPosition) { + Leg nextLeg = travelerPosition.nextLeg; + Leg expectedLeg = travelerPosition.expectedLeg; + return isAtEndOfLeg(travelerPosition) && nextLeg != null && !nextLeg.mode.equalsIgnoreCase(expectedLeg.mode); } /** diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java index 8272ab399..a76b80a44 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java @@ -13,6 +13,7 @@ import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getExpectedLeg; import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getNextLeg; +import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getPreviousLeg; import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getSegmentFromPosition; import static org.opentripplanner.middleware.utils.GeometryUtils.getDistanceFromLine; import static org.opentripplanner.middleware.utils.ItineraryUtils.getFirstLeg; @@ -49,14 +50,6 @@ public class TravelerPosition { /** The first leg of the trip. **/ public Leg firstLegOfTrip; - /** The trip related to the traveler's position */ - public MonitoredTrip trip; - - public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpUser otpUser, MonitoredTrip trip) { - this(trackedJourney, itinerary, otpUser); - this.trip = trip; - } - public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpUser otpUser) { TrackingLocation lastLocation = trackedJourney.locations.get(trackedJourney.locations.size() - 1); currentTime = lastLocation.timestamp.toInstant(); @@ -112,6 +105,13 @@ public TravelerPosition(Leg expectedLeg, TrackedJourney trackedJourney, Leg firs this.currentPosition = currentPosition; } + /** Used for unit testing. */ + public TravelerPosition(Leg expectedLeg, Leg nextLeg, Coordinates currentPosition) { + this.expectedLeg = expectedLeg; + this.nextLeg = nextLeg; + this.currentPosition = currentPosition; + } + /** Computes the current deviation in meters from the expected itinerary. */ public double getDeviationMeters() { return getDistanceFromLine(legSegmentFromPosition.start, legSegmentFromPosition.end, currentPosition); diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index d4419f4ef..efd339c3d 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -11,12 +11,10 @@ import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Route; -import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -146,25 +144,6 @@ public static boolean itinerariesMatch(Itinerary referenceItinerary, Itinerary c return true; } - public static boolean itinerariesMatch(Itinerary referenceItinerary, Itinerary candidateItinerary, List exceptions) { - // Make sure both itineraries are monitorable before continuing. - if (!referenceItinerary.canBeMonitored() || !candidateItinerary.canBeMonitored()) return false; - - // make sure itineraries have same amount of legs - if (referenceItinerary.legs.size() != candidateItinerary.legs.size()) return false; - - // make sure each leg matches - for (int i = 0; i < referenceItinerary.legs.size(); i++) { - Leg referenceItineraryLeg = referenceItinerary.legs.get(i); - Leg candidateItineraryLeg = candidateItinerary.legs.get(i); - - if (!legsMatch(referenceItineraryLeg, candidateItineraryLeg, exceptions)) return false; - } - - // if this point is reached, the itineraries are assumed to match - return true; - } - /** * Checks that the specified itinerary is on the same day as the specified date/time. * @param itinerary the itinerary to check. @@ -196,27 +175,23 @@ private static boolean isAfterServiceStart(ZonedDateTime time) { return time.getHour() >= SERVICE_DAY_START_HOUR; } - public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { - return legsMatch(referenceItineraryLeg, candidateItineraryLeg, Collections.emptyList()); - } /** * Check whether a new leg of an itinerary matches the previous itinerary leg for the purposes of trip monitoring. */ - public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg, List exceptions) { + public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { // for now don't analyze non-transit legs - if (Boolean.FALSE.equals(referenceItineraryLeg.transitLeg)) return true; - - boolean destinationMatches = exceptions.contains(NotificationType.DESTINATION_CHANGE) || stopsMatch(referenceItineraryLeg.to, candidateItineraryLeg.to); - boolean legModeMatches = exceptions.contains(NotificationType.MODE_CHANGE) || legsModeMatches(referenceItineraryLeg, candidateItineraryLeg); - boolean originMatches = exceptions.contains(NotificationType.ORIGIN_CHANGE) || stopsMatch(referenceItineraryLeg.from, candidateItineraryLeg.from); + if (!referenceItineraryLeg.transitLeg) return true; // make sure the same from/to stop are being used - if (!originMatches || !destinationMatches) { + if ( + !stopsMatch(referenceItineraryLeg.from, candidateItineraryLeg.from) || + !stopsMatch(referenceItineraryLeg.to, candidateItineraryLeg.to) + ) { return false; } // Make sure the transit service is the same as perceived by the customer. It is assumed that the transit - // service is the same experience to a customer if the following conditions are met: + // service is the same expereince to a customer if the following conditions are met: // - The modes of transportation are the same // - The agency name of the transit service is the same (or the reference leg had an empty agency name) // - The route's long name is the same (or the reference leg had an empty route long name) @@ -224,11 +199,11 @@ public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinerar // - The headsign is the same (or the reference leg had an empty headsign) // - The leg has the same interlining qualities with the previous leg if ( - !legModeMatches || - !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || - !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || - !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || - (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) + !equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode) || + !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || + !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || + !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || + (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) ) { return false; } @@ -253,23 +228,16 @@ public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinerar return true; } - /** - * Check whether the mode of two legs match. - */ - public static boolean legsModeMatches(Leg referenceItineraryLeg, Leg candidateItineraryLeg) { - return equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode); - } - /** * Checks whether two stops (OTP Places) match for the purposes of matching itineraries */ - public static boolean stopsMatch(Place stopA, Place stopB) { + private static boolean stopsMatch(Place stopA, Place stopB) { // Stop names must match. It's possible in OTP to have a null place name, although it probably won't occur with // transit legs. But just in case this method is expanded in scope to check more stuff about a place, if both // are null, then assume a match. if ( (stopA.name != null && !stopA.name.equalsIgnoreCase(stopB.name)) || - (stopA.name == null && stopB.name != null) + (stopA.name == null && stopB.name != null) ) { return false; } @@ -277,8 +245,8 @@ public static boolean stopsMatch(Place stopA, Place stopB) { // stop code must match if ( stopA.stop != null && - stopB.stop != null && - !equalsIgnoreCaseOrReferenceWasEmpty(stopA.stop.code, stopB.stop.code) + stopB.stop != null && + !equalsIgnoreCaseOrReferenceWasEmpty(stopA.stop.code, stopB.stop.code) ) { return false; } @@ -302,8 +270,8 @@ public static boolean stopsMatch(Place stopA, Place stopB) { private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { return ( agencyA != null && - agencyB != null && - equalsIgnoreCaseOrReferenceWasEmpty(agencyA.name, agencyB.name) + agencyB != null && + equalsIgnoreCaseOrReferenceWasEmpty(agencyA.name, agencyB.name) ); } @@ -313,9 +281,9 @@ private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { private static boolean routesMatch(Route routeA, Route routeB) { return ( routeA != null && - routeB != null && - equalsIgnoreCaseOrReferenceWasEmpty(routeA.longName, routeB.longName) && - equalsIgnoreCaseOrReferenceWasEmpty(routeA.shortName, routeB.shortName) + routeB != null && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.longName, routeB.longName) && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.shortName, routeB.shortName) ); } @@ -403,4 +371,4 @@ public static Leg getFirstLeg(Itinerary itinerary) { .map(legs -> legs.get(0)) .orElse(null); } -} +} \ No newline at end of file diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index f7414a17c..0a5f338ce 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. -DESTINATION_CHANGE_NOTIFICATION = The destination for the leg starting at %s has changed from %s to %s. +ARRIVED_NOTIFICATION = The traveler has arrived at %s. LABEL_AND_CONTENT = %s: %s -MODE_CHANGE_NOTIFICATION = There has been a mode change from %s to %s for the leg starting at %s. -ORIGIN_CHANGE_NOTIFICATION = The leg previously starting at %s will now start at %s. +MODE_CHANGE_NOTIFICATION = The traveler has changed mode from %s to %s. +DEPARTED_NOTIFICATION = The traveler has departed %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification TRIP_EMAIL_SUBJECT_FOR_USER = Trip for %s Notification diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index b10612eac..6f513e2b7 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. -DESTINATION_CHANGE_NOTIFICATION = TODO %s TODO %s TODO %s. +ARRIVED_NOTIFICATION = TODO %s. LABEL_AND_CONTENT = %s\u00A0: %s -MODE_CHANGE_NOTIFICATION = TODO %s TODO %s TODO %s. -ORIGIN_CHANGE_NOTIFICATION = TODO %s TODO %s. +MODE_CHANGE_NOTIFICATION = TODO %s TODO %s. +DEPARTED_NOTIFICATION = TODO %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s TRIP_EMAIL_SUBJECT_FOR_USER = Notification pour le trajet de %s diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 15a12e183..6376e34d4 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.TrackedJourney; +import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.testutils.OtpMiddlewareTestEnvironment; import org.opentripplanner.middleware.testutils.OtpTestUtils; import org.opentripplanner.middleware.testutils.PersistenceTestUtils; @@ -23,6 +24,8 @@ import org.opentripplanner.middleware.otp.response.OtpResponse; import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.tripmonitor.TripStatus; +import org.opentripplanner.middleware.triptracker.TravelerPosition; +import org.opentripplanner.middleware.utils.Coordinates; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +50,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.middleware.testutils.OtpTestUtils.createDefaultItinerary; import static org.opentripplanner.middleware.tripmonitor.TripStatus.NEXT_TRIP_NOT_POSSIBLE; import static org.opentripplanner.middleware.tripmonitor.TripStatus.TRIP_ACTIVE; import static org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTripBasicTest.makeMonitoredTripFromNow; @@ -217,58 +221,44 @@ private static Stream createDelayNotificationTestCases() { @ParameterizedTest @MethodSource("createMajorChangeNotificationTestCases") void testMajorChangeNotifications( - NotificationType exception, + NotificationType legTransitionType, String message, - int nextLegIndex + TravelerPosition travelerPosition ) throws Exception { CheckMonitoredTrip check = createCheckMonitoredTrip(this::mockOtpPlanResponse); - OtpResponse otpResponse = mockOtpPlanResponse(); - Itinerary otpResponseItinerary = getItineraryWithMajorChangeApplied(otpResponse, exception); - TripMonitorNotification notification = check.checkTripForMajorChange(exception, List.of(otpResponseItinerary), nextLegIndex); - if (message != null) { - assertNotNull(notification); - assertEquals(message, notification.body); - } + TripMonitorNotification notification = check.createLegTransitionNotification( + legTransitionType, + travelerPosition + ); + assertNotNull(notification); + assertEquals(message, notification.body); } - private static Stream createMajorChangeNotificationTestCases() { + private static Stream createMajorChangeNotificationTestCases() throws Exception { + Itinerary itinerary = createDefaultItinerary(); + Leg expectedLeg = itinerary.legs.get(1); + Coordinates expectedLegDestinationCoords = new Coordinates(expectedLeg.to); + Leg nextLeg = itinerary.legs.get(2); + Coordinates nextLegDepartureCoords = new Coordinates(nextLeg.from); return Stream.of( Arguments.of( - NotificationType.MODE_CHANGE, - "There has been a mode change from TRAM to BUS for the leg starting at Providence Park MAX Station.", - 1 + NotificationType.MODE_CHANGE_NOTIFICATION, + "The traveler has changed mode from TRAM to WALK.", + new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords) ), - Arguments.of(NotificationType.MODE_CHANGE, null, 2 /* Traveler already passed the mode change. */), Arguments.of( - NotificationType.ORIGIN_CHANGE, - "The leg previously starting at Providence Park MAX Station will now start at Library/SW 9th Ave.", - 1 + NotificationType.DEPARTED_NOTIFICATION, + "The traveler has departed Providence Park MAX Station.", + new TravelerPosition(expectedLeg, nextLeg, nextLegDepartureCoords) ), Arguments.of( - NotificationType.DESTINATION_CHANGE, - "The destination for the leg starting at Providence Park MAX Station has changed from Pioneer Square South MAX Station to Library/SW 9th Ave.", - 1 + NotificationType.ARRIVED_NOTIFICATION, + "The traveler has arrived at Pioneer Square South MAX Station.", + new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords) ) ); } - /** - * Alter the itinerary to include a change which is classed as a major change. - */ - private Itinerary getItineraryWithMajorChangeApplied(OtpResponse otpResponse, NotificationType exception) { - Itinerary otpResponseItinerary = otpResponse.plan.itineraries.get(0); - if (exception == NotificationType.MODE_CHANGE) { - otpResponseItinerary.legs.get(1).mode = "BUS"; - } - if (exception == NotificationType.ORIGIN_CHANGE) { - otpResponseItinerary.legs.get(1).from.name = "Library/SW 9th Ave"; - } - if (exception == NotificationType.DESTINATION_CHANGE) { - otpResponseItinerary.legs.get(1).to.name = "Library/SW 9th Ave"; - } - return otpResponseItinerary; - } - /** * Convenience method for creating a CheckMonitoredTrip instance with the default journey state. */ @@ -289,7 +279,7 @@ private static CheckMonitoredTrip createCheckMonitoredTrip(JourneyState journeyS journeyState ); CheckMonitoredTrip checkMonitoredTrip = new CheckMonitoredTrip(monitoredTrip, otpResponseProvider); - checkMonitoredTrip.matchingItinerary = OtpTestUtils.createDefaultItinerary(); + checkMonitoredTrip.matchingItinerary = createDefaultItinerary(); return checkMonitoredTrip; } From 926f994d09f31218b066cbf834148c8c7104e1c7 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Mon, 9 Dec 2024 16:51:26 +0000 Subject: [PATCH 05/15] refactor(Addressed unused params and methods. Updated Swagger docs.): --- .../tripmonitor/jobs/CheckMonitoredTrip.java | 22 +------------------ .../triptracker/TravelerPosition.java | 2 -- .../middleware/utils/ItineraryUtils.java | 22 +++++++++---------- .../latest-spark-swagger-output.yaml | 10 ++++----- 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 889004e48..0bfa7c54a 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -1,6 +1,5 @@ package org.opentripplanner.middleware.tripmonitor.jobs; -import com.mongodb.lang.Nullable; import org.opentripplanner.middleware.i18n.Message; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; @@ -27,6 +26,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import javax.annotation.Nullable; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; @@ -261,26 +261,6 @@ public void processLegTransition(List legTransitionTypes , Tra sendNotifications(); } - /** - * Get the position of the next leg within the itinerary. - */ - private int getLegIndex(Leg nextLeg) { - return IntStream - .range(0, trip.itinerary.legs.size()) - .filter(i -> ItineraryUtils.legsMatch(nextLeg, trip.itinerary.legs.get(i))) - .findFirst() - .orElse(-1); - } - - /** - * Confirm that the OTP response contains an itinerary that matches the trip itinerary. - */ - private boolean hasMatchingItinerary(OtpResponse otpResponse) { - return otpResponse.plan.itineraries - .stream() - .anyMatch(i -> ItineraryUtils.itinerariesMatch(trip.itinerary, i)); - } - /** * Find and set the matching itinerary from the OTP response that matches the monitored trip's stored itinerary if a * match exists. diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java index a76b80a44..fa5c14a87 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java @@ -1,6 +1,5 @@ package org.opentripplanner.middleware.triptracker; -import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.models.OtpUser; import org.opentripplanner.middleware.models.TrackedJourney; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -13,7 +12,6 @@ import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getExpectedLeg; import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getNextLeg; -import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getPreviousLeg; import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getSegmentFromPosition; import static org.opentripplanner.middleware.utils.GeometryUtils.getDistanceFromLine; import static org.opentripplanner.middleware.utils.ItineraryUtils.getFirstLeg; diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index efd339c3d..646c7ec9f 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -200,10 +200,10 @@ public static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinerar // - The leg has the same interlining qualities with the previous leg if ( !equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode) || - !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || - !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || - !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || - (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) + !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || + !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || + !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || + (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) ) { return false; } @@ -245,8 +245,8 @@ private static boolean stopsMatch(Place stopA, Place stopB) { // stop code must match if ( stopA.stop != null && - stopB.stop != null && - !equalsIgnoreCaseOrReferenceWasEmpty(stopA.stop.code, stopB.stop.code) + stopB.stop != null && + !equalsIgnoreCaseOrReferenceWasEmpty(stopA.stop.code, stopB.stop.code) ) { return false; } @@ -270,8 +270,8 @@ private static boolean stopsMatch(Place stopA, Place stopB) { private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { return ( agencyA != null && - agencyB != null && - equalsIgnoreCaseOrReferenceWasEmpty(agencyA.name, agencyB.name) + agencyB != null && + equalsIgnoreCaseOrReferenceWasEmpty(agencyA.name, agencyB.name) ); } @@ -281,9 +281,9 @@ private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { private static boolean routesMatch(Route routeA, Route routeB) { return ( routeA != null && - routeB != null && - equalsIgnoreCaseOrReferenceWasEmpty(routeA.longName, routeB.longName) && - equalsIgnoreCaseOrReferenceWasEmpty(routeA.shortName, routeB.shortName) + routeB != null && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.longName, routeB.longName) && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.shortName, routeB.shortName) ); } diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 7c15a66bd..1f9ad76cd 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -1686,10 +1686,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MobilityProfileLite" schema: $ref: "#/definitions/MobilityProfileLite" + responseSchema: + $ref: "#/definitions/MobilityProfileLite" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2376,9 +2376,9 @@ definitions: - "ALERT_FOUND" - "ITINERARY_NOT_FOUND" - "INITIAL_REMINDER" - - "MODE_CHANGE" - - "ORIGIN_CHANGE" - - "DESTINATION_CHANGE" + - "MODE_CHANGE_NOTIFICATION" + - "DEPARTED_NOTIFICATION" + - "ARRIVED_NOTIFICATION" body: type: "string" EncodedPolyline: From 91d66190cb611283993b8e745148641efd695c5e Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Tue, 10 Dec 2024 16:52:02 +0000 Subject: [PATCH 06/15] refactor(Refactor to create and process leg tranistion based notification types): --- .../middleware/i18n/Message.java | 2 + .../models/LegTransitionNotification.java | 103 ++++++++ .../models/TripMonitorNotification.java | 50 ---- .../tripmonitor/jobs/CheckMonitoredTrip.java | 65 +++-- src/main/resources/Message.properties | 8 +- src/main/resources/Message_fr.properties | 2 + .../latest-spark-swagger-output.yaml | 240 +++++++++--------- .../jobs/CheckMonitoredTripTest.java | 58 +++-- 8 files changed, 308 insertions(+), 220 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java diff --git a/src/main/java/org/opentripplanner/middleware/i18n/Message.java b/src/main/java/org/opentripplanner/middleware/i18n/Message.java index 9f9eb5902..a590b1990 100644 --- a/src/main/java/org/opentripplanner/middleware/i18n/Message.java +++ b/src/main/java/org/opentripplanner/middleware/i18n/Message.java @@ -50,6 +50,8 @@ public enum Message { TRIP_INVITE_COMPANION, TRIP_INVITE_PRIMARY_TRAVELER, TRIP_INVITE_OBSERVER, + TRIP_NAME_UNDEFINED, + TRIP_TRAVELER_GENERIC_NAME, TRIP_NOT_FOUND_NOTIFICATION, TRIP_NO_LONGER_POSSIBLE_NOTIFICATION, TRIP_REMINDER_NOTIFICATION, diff --git a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java new file mode 100644 index 000000000..d3efa746d --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java @@ -0,0 +1,103 @@ +package org.opentripplanner.middleware.models; + +import org.opentripplanner.middleware.i18n.Message; +import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; +import org.opentripplanner.middleware.triptracker.TravelerPosition; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class LegTransitionNotification { + + public String travelerName; + public NotificationType notificationType; + public TravelerPosition travelerPosition; + public Locale observerLocale; + public TripMonitorNotification tripMonitorNotification; + + public LegTransitionNotification( + String travelerName, + NotificationType notificationType, + TravelerPosition travelerPosition, + Locale observerLocale + ) { + this.travelerName = travelerName; + this.notificationType = notificationType; + this.travelerPosition = travelerPosition; + this.observerLocale = observerLocale; + this.tripMonitorNotification = createTripMonitorNotification(notificationType); + } + + /** + * Create {@link TripMonitorNotification} for leg transition based on notification type. + */ + @Nullable + public TripMonitorNotification createTripMonitorNotification(NotificationType notificationType) { + String body; + switch (notificationType) { + case MODE_CHANGE_NOTIFICATION: + body = String.format( + Message.MODE_CHANGE_NOTIFICATION.get(travelerPosition.locale), + getTravelerName(), + travelerPosition.expectedLeg.mode, + travelerPosition.nextLeg.mode + ); + break; + case DEPARTED_NOTIFICATION: + body = String.format( + Message.DEPARTED_NOTIFICATION.get(observerLocale), + getTravelerName(), + travelerPosition.expectedLeg.from.name + ); + break; + case ARRIVED_NOTIFICATION: + body = String.format( + Message.ARRIVED_NOTIFICATION.get(travelerPosition.locale), + getTravelerName(), + travelerPosition.expectedLeg.to.name + ); + break; + default: + body = null; + } + return (body != null) ? new TripMonitorNotification(notificationType, body) : null; + } + + /** + * Get the traveler's name if available, if not provide a generic traveler name. + */ + private String getTravelerName() { + if (travelerName != null) { + return travelerName; + } else { + return Message.TRIP_TRAVELER_GENERIC_NAME.get(observerLocale); + } + } + + /** + * Create locale specific notifications. + */ + public static TripMonitorNotification[] createLegTransitionNotifications( + List legTransitionTypes, + String travelerName, + TravelerPosition travelerPosition, + Locale observerLocale + ) { + List tripMonitorNotifications = new ArrayList<>(); + // Create locale specific notifications. + for (NotificationType legTransitionType : legTransitionTypes) { + LegTransitionNotification legTransitionNotification = new LegTransitionNotification( + travelerName, + legTransitionType, + travelerPosition, + observerLocale + ); + if (legTransitionNotification.tripMonitorNotification != null) { + tripMonitorNotifications.add(legTransitionNotification.tripMonitorNotification); + } + } + return tripMonitorNotifications.toArray(new TripMonitorNotification[0]); + } +} diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index faaca35f3..985970370 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -2,7 +2,6 @@ import org.opentripplanner.middleware.i18n.Message; import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; -import org.opentripplanner.middleware.triptracker.TravelerPosition; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,53 +110,4 @@ public static TripMonitorNotification createInitialReminderNotification( ) ); } - - /** - * Creates a departed notification. - */ - public static TripMonitorNotification createDepartedNotification( - NotificationType legTransitionType, - TravelerPosition travelerPosition - ) { - return new TripMonitorNotification( - legTransitionType, - String.format( - Message.DEPARTED_NOTIFICATION.get(travelerPosition.locale), - travelerPosition.expectedLeg.from.name - ) - ); - } - - /** - * Creates a mode change notification. - */ - public static TripMonitorNotification createModeChangeNotification( - NotificationType legTransitionType, - TravelerPosition travelerPosition - ) { - return new TripMonitorNotification( - legTransitionType, - String.format( - Message.MODE_CHANGE_NOTIFICATION.get(travelerPosition.locale), - travelerPosition.expectedLeg.mode, - travelerPosition.nextLeg.mode - ) - ); - } - - /** - * Creates an arrived notification. - */ - public static TripMonitorNotification createArrivedNotification( - NotificationType legTransitionType, - TravelerPosition travelerPosition - ) { - return new TripMonitorNotification( - legTransitionType, - String.format( - Message.ARRIVED_NOTIFICATION.get(travelerPosition.locale), - travelerPosition.expectedLeg.to.name - ) - ); - } } diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index c7dde0555..3411602bc 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -2,12 +2,12 @@ import org.opentripplanner.middleware.i18n.Message; import org.opentripplanner.middleware.models.ItineraryExistence; +import org.opentripplanner.middleware.models.LegTransitionNotification; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.models.OtpUser; import org.opentripplanner.middleware.models.TripMonitorAlertNotification; import org.opentripplanner.middleware.models.TripMonitorNotification; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; -import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.tripmonitor.TripStatus; import org.opentripplanner.middleware.otp.OtpDispatcher; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import javax.annotation.Nullable; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; @@ -43,9 +42,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import java.util.stream.IntStream; -import static org.opentripplanner.middleware.utils.I18nUtils.label; +import static com.mongodb.client.model.Filters.eq; /** * This job handles the primary functions for checking a {@link MonitoredTrip}, including: @@ -252,11 +250,29 @@ private boolean isTrackingOngoing() { return trip.journeyState.tripStatus == TripStatus.TRIP_ACTIVE && TripTrackingData.getOngoingTrackedJourney(trip.id) != null; } - public void processLegTransition(List legTransitionTypes , TravelerPosition travelerPosition) { - for (NotificationType legTransitionType : legTransitionTypes) { - enqueueNotification(createLegTransitionNotification(legTransitionType, travelerPosition)); + /** + * Process leg transition notifications. + */ + public void processLegTransition(List legTransitionTypes, TravelerPosition travelerPosition) { + OtpUser otpUser = getOtpUser(); + if (otpUser != null) { + otpUser.relatedUsers.forEach(relatedUser -> { + if (relatedUser.isConfirmed()) { + OtpUser observer = Persistence.otpUsers.getOneFiltered(eq("email", relatedUser.email)); + if (observer != null) { + enqueueNotification( + LegTransitionNotification.createLegTransitionNotifications( + legTransitionTypes, + otpUser.name, + travelerPosition, + I18nUtils.getOtpUserLocale(observer) + ) + ); + sendNotifications(observer); + } + } + }); } - sendNotifications(); } /** @@ -509,26 +525,8 @@ public TripMonitorNotification checkTripForDelay(NotificationType delayType) { return null; } - @Nullable - public TripMonitorNotification createLegTransitionNotification( - NotificationType legTransitionType, - TravelerPosition travelerPosition - ) { - switch (legTransitionType) { - case MODE_CHANGE_NOTIFICATION: - return TripMonitorNotification.createModeChangeNotification(legTransitionType, travelerPosition); - case DEPARTED_NOTIFICATION: - return TripMonitorNotification.createDepartedNotification(legTransitionType, travelerPosition); - case ARRIVED_NOTIFICATION: - return TripMonitorNotification.createArrivedNotification(legTransitionType, travelerPosition); - default: - return null; - } - } - /** - * Compose a message for any enqueued notifications and send to {@link OtpUser} based on their notification - * preferences. + * Send notification to user associated with the trip. */ private void sendNotifications() { OtpUser otpUser = getOtpUser(); @@ -537,6 +535,14 @@ private void sendNotifications() { // TODO: Bugsnag / delete monitored trip? return; } + sendNotifications(otpUser); + } + + /** + * Compose a message for any enqueued notifications and send to {@link OtpUser} based on their notification + * preferences. + */ + private void sendNotifications(OtpUser otpUser) { // Update push notification devices count, which may change asynchronously NotificationUtils.updatePushDevices(otpUser); @@ -554,9 +560,12 @@ private void sendNotifications() { return; } + Locale locale = getOtpUserLocale(); String tripNameOrReminder = hasInitialReminder ? initialReminderNotification.body : trip.tripName; + if (tripNameOrReminder == null) { + tripNameOrReminder = Message.TRIP_NAME_UNDEFINED.get(locale); + } - Locale locale = getOtpUserLocale(); // A HashMap is needed instead of a Map for template data to be serialized to the template renderer. Map templateData = new HashMap<>(); templateData.putAll(Map.of( diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index e97d1e9a8..651cbfeb8 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. -ARRIVED_NOTIFICATION = The traveler has arrived at %s. +ARRIVED_NOTIFICATION = %s has arrived at %s. LABEL_AND_CONTENT = %s: %s -MODE_CHANGE_NOTIFICATION = The traveler has changed mode from %s to %s. -DEPARTED_NOTIFICATION = The traveler has departed %s. +MODE_CHANGE_NOTIFICATION = %s has changed transit from %s to %s. +DEPARTED_NOTIFICATION = %s has departed %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification TRIP_EMAIL_SUBJECT_FOR_USER = Trip for %s Notification @@ -35,6 +35,8 @@ TRIP_DELAY_MINUTES = %d minutes TRIP_INVITE_COMPANION = %s added you as a companion for their trip. TRIP_INVITE_PRIMARY_TRAVELER = %s made you the primary traveler on this trip. TRIP_INVITE_OBSERVER = %s added you as an observer for their trip. +TRIP_NAME_UNDEFINED = Trip name undefined +TRIP_TRAVELER_GENERIC_NAME = Traveler TRIP_NOT_FOUND_NOTIFICATION = Your itinerary was not found in today's trip planner results. Please check real-time conditions and plan a new trip. TRIP_NO_LONGER_POSSIBLE_NOTIFICATION = Your itinerary is no longer possible on any monitored day of the week. Please plan and save a new trip. TRIP_REMINDER_NOTIFICATION = Reminder for %s at %s. diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index 701666986..1a0a88c50 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -35,6 +35,8 @@ TRIP_DELAY_MINUTES = %d minutes TRIP_INVITE_COMPANION = %s vous a ajouté comme accompagnateur·trice pour un trajet. TRIP_INVITE_PRIMARY_TRAVELER = %s vous a fait le voyageur principal sur ce trajet. TRIP_INVITE_OBSERVER = %s vous a ajouté comme observateur·trice pour un trajet. +TRIP_NAME_UNDEFINED = TODO +TRIP_TRAVELER_GENERIC_NAME = TODO TRIP_NOT_FOUND_NOTIFICATION = Votre trajet est introuvable aujourd'hui. Veuillez vérifier les conditions en temps-réel et recherchez un nouveau trajet. TRIP_NO_LONGER_POSSIBLE_NOTIFICATION = Votre trajet n'est plus possible dans aucun jour de suivi de la semaine. Veuillez rechercher et enregistrer un nouveau trajet. TRIP_REMINDER_NOTIFICATION = Rappel pour %s à %s. diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 1f9ad76cd..3d4e09dc7 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -56,10 +56,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -90,10 +90,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -123,10 +123,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/Job" responseSchema: $ref: "#/definitions/Job" + schema: + $ref: "#/definitions/Job" /api/admin/user: get: tags: @@ -155,10 +155,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/admin/user" @@ -178,10 +178,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -220,10 +220,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -269,10 +269,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -309,10 +309,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/AdminUser" responseSchema: $ref: "#/definitions/AdminUser" + schema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -356,10 +356,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -402,10 +402,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -447,10 +447,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/TokenHolder" responseSchema: $ref: "#/definitions/TokenHolder" + schema: + $ref: "#/definitions/TokenHolder" /api/secure/application/fromtoken: get: tags: @@ -464,10 +464,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -498,10 +498,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -531,10 +531,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/Job" responseSchema: $ref: "#/definitions/Job" + schema: + $ref: "#/definitions/Job" /api/secure/application: get: tags: @@ -563,10 +563,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/application" @@ -586,10 +586,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -628,10 +628,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -677,10 +677,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -717,10 +717,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ApiUser" responseSchema: $ref: "#/definitions/ApiUser" + schema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -754,10 +754,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -788,10 +788,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -821,10 +821,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/Job" responseSchema: $ref: "#/definitions/Job" + schema: + $ref: "#/definitions/Job" /api/secure/cdp: get: tags: @@ -853,10 +853,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/cdp" @@ -876,10 +876,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -918,10 +918,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -967,10 +967,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1007,10 +1007,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/CDPUser" responseSchema: $ref: "#/definitions/CDPUser" + schema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1050,10 +1050,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/ItineraryExistence" responseSchema: $ref: "#/definitions/ItineraryExistence" + schema: + $ref: "#/definitions/ItineraryExistence" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1102,10 +1102,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredtrip" @@ -1125,10 +1125,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredTrip" responseSchema: $ref: "#/definitions/MonitoredTrip" + schema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1167,10 +1167,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredTrip" responseSchema: $ref: "#/definitions/MonitoredTrip" + schema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1216,10 +1216,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredTrip" responseSchema: $ref: "#/definitions/MonitoredTrip" + schema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1257,10 +1257,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredTrip" responseSchema: $ref: "#/definitions/MonitoredTrip" + schema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1298,10 +1298,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/TrackingResponse" responseSchema: $ref: "#/definitions/TrackingResponse" + schema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/updatetracking: post: tags: @@ -1319,10 +1319,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/TrackingResponse" responseSchema: $ref: "#/definitions/TrackingResponse" + schema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/track: post: tags: @@ -1340,10 +1340,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/TrackingResponse" responseSchema: $ref: "#/definitions/TrackingResponse" + schema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/endtracking: post: tags: @@ -1361,10 +1361,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/EndTrackingResponse" responseSchema: $ref: "#/definitions/EndTrackingResponse" + schema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/monitoredtrip/forciblyendtracking: post: tags: @@ -1382,10 +1382,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/EndTrackingResponse" responseSchema: $ref: "#/definitions/EndTrackingResponse" + schema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/triprequests: get: tags: @@ -1430,10 +1430,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/TripRequest" responseSchema: $ref: "#/definitions/TripRequest" + schema: + $ref: "#/definitions/TripRequest" /api/secure/monitoredcomponent: get: tags: @@ -1462,10 +1462,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredcomponent" @@ -1485,10 +1485,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredComponent" responseSchema: $ref: "#/definitions/MonitoredComponent" + schema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1527,10 +1527,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredComponent" responseSchema: $ref: "#/definitions/MonitoredComponent" + schema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1576,10 +1576,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredComponent" responseSchema: $ref: "#/definitions/MonitoredComponent" + schema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1617,10 +1617,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MonitoredComponent" responseSchema: $ref: "#/definitions/MonitoredComponent" + schema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1651,10 +1651,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1686,10 +1686,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/MobilityProfileLite" responseSchema: $ref: "#/definitions/MobilityProfileLite" + schema: + $ref: "#/definitions/MobilityProfileLite" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1729,10 +1729,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/VerificationResult" responseSchema: $ref: "#/definitions/VerificationResult" + schema: + $ref: "#/definitions/VerificationResult" /api/secure/user/{id}/verify_sms/{code}: post: tags: @@ -1752,10 +1752,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/VerificationResult" responseSchema: $ref: "#/definitions/VerificationResult" + schema: + $ref: "#/definitions/VerificationResult" /api/secure/user/fromtoken: get: tags: @@ -1769,10 +1769,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1803,10 +1803,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1836,10 +1836,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/Job" responseSchema: $ref: "#/definitions/Job" + schema: + $ref: "#/definitions/Job" /api/secure/user: get: tags: @@ -1868,10 +1868,10 @@ paths: responses: "200": description: "successful operation" - schema: - $ref: "#/definitions/ResponseList" responseSchema: $ref: "#/definitions/ResponseList" + schema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/user" @@ -1891,10 +1891,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1933,10 +1933,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1982,10 +1982,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2022,10 +2022,10 @@ paths: "200": description: "Successful operation" examples: {} - schema: - $ref: "#/definitions/OtpUser" responseSchema: $ref: "#/definitions/OtpUser" + schema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2079,11 +2079,11 @@ paths: responses: "200": description: "successful operation" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/ApiUsageResult" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/ApiUsageResult" @@ -2110,11 +2110,11 @@ paths: responses: "200": description: "successful operation" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/BugsnagEvent" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/BugsnagEvent" @@ -2141,11 +2141,11 @@ paths: responses: "200": description: "successful operation" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/CDPFile" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/CDPFile" @@ -2166,11 +2166,11 @@ paths: responses: "200": description: "successful operation" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/URL" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/URL" diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 6376e34d4..f6140a1e0 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.middleware.models.ItineraryExistence; +import org.opentripplanner.middleware.models.LegTransitionNotification; import org.opentripplanner.middleware.models.TrackedJourney; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.testutils.OtpMiddlewareTestEnvironment; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -219,22 +221,27 @@ private static Stream createDelayNotificationTestCases() { } @ParameterizedTest - @MethodSource("createMajorChangeNotificationTestCases") - void testMajorChangeNotifications( + @MethodSource("createLegTransitionNotificationTestCases") + void testLegTransitionNotifications( NotificationType legTransitionType, - String message, - TravelerPosition travelerPosition - ) throws Exception { - CheckMonitoredTrip check = createCheckMonitoredTrip(this::mockOtpPlanResponse); - TripMonitorNotification notification = check.createLegTransitionNotification( - legTransitionType, - travelerPosition - ); - assertNotNull(notification); - assertEquals(message, notification.body); + String travelerName, + TravelerPosition travelerPosition, + Locale locale, + String message + ) { + TripMonitorNotification[] notification = LegTransitionNotification.createLegTransitionNotifications( + List.of(legTransitionType), + travelerName, + travelerPosition, + locale + ); + assertNotNull(notification[0]); + assertEquals(message, notification[0].body); } - private static Stream createMajorChangeNotificationTestCases() throws Exception { + private static Stream createLegTransitionNotificationTestCases() throws Exception { + String travelerName = "Obi-Wan"; + Locale locale = Locale.US; Itinerary itinerary = createDefaultItinerary(); Leg expectedLeg = itinerary.legs.get(1); Coordinates expectedLegDestinationCoords = new Coordinates(expectedLeg.to); @@ -243,18 +250,31 @@ private static Stream createMajorChangeNotificationTestCases() throws return Stream.of( Arguments.of( NotificationType.MODE_CHANGE_NOTIFICATION, - "The traveler has changed mode from TRAM to WALK.", - new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords) + travelerName, + new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), + locale, + "Obi-Wan has changed transit from TRAM to WALK." ), Arguments.of( NotificationType.DEPARTED_NOTIFICATION, - "The traveler has departed Providence Park MAX Station.", - new TravelerPosition(expectedLeg, nextLeg, nextLegDepartureCoords) + travelerName, + new TravelerPosition(expectedLeg, nextLeg, nextLegDepartureCoords), + locale, + "Obi-Wan has departed Providence Park MAX Station." + ), + Arguments.of( + NotificationType.ARRIVED_NOTIFICATION, + travelerName, + new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), + locale, + "Obi-Wan has arrived at Pioneer Square South MAX Station." ), Arguments.of( NotificationType.ARRIVED_NOTIFICATION, - "The traveler has arrived at Pioneer Square South MAX Station.", - new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords) + null, + new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), + locale, + "Traveler has arrived at Pioneer Square South MAX Station." ) ); } From c5582e13756562f643b894937263a95ad179ed9e Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Fri, 13 Dec 2024 10:18:34 +0000 Subject: [PATCH 07/15] refactor(Addressed PR feedback.): --- .../middleware/i18n/Message.java | 3 +- .../models/LegTransitionNotification.java | 69 ++--- .../middleware/models/MonitoredTrip.java | 17 ++ .../middleware/models/OtpUser.java | 8 + .../models/TripMonitorNotification.java | 14 + .../middleware/tripmonitor/JourneyState.java | 1 - .../tripmonitor/TrustedCompanion.java | 3 +- .../tripmonitor/jobs/CheckMonitoredTrip.java | 50 ++-- .../tripmonitor/jobs/NotificationType.java | 2 +- .../triptracker/TravelerLocator.java | 38 +-- .../middleware/utils/ItineraryUtils.java | 2 +- src/main/resources/Message.properties | 3 +- src/main/resources/Message_fr.properties | 3 +- .../latest-spark-swagger-output.yaml | 242 +++++++++--------- .../jobs/CheckMonitoredTripTest.java | 65 +++-- 15 files changed, 289 insertions(+), 231 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/i18n/Message.java b/src/main/java/org/opentripplanner/middleware/i18n/Message.java index a590b1990..260c2fb09 100644 --- a/src/main/java/org/opentripplanner/middleware/i18n/Message.java +++ b/src/main/java/org/opentripplanner/middleware/i18n/Message.java @@ -19,10 +19,10 @@ public enum Message { ACCEPT_DEPENDENT_EMAIL_SUBJECT, ACCEPT_DEPENDENT_EMAIL_MANAGE, ACCEPT_DEPENDENT_ERROR, + ARRIVED_AND_MODE_CHANGE_NOTIFICATION, ARRIVED_NOTIFICATION, DEPARTED_NOTIFICATION, LABEL_AND_CONTENT, - MODE_CHANGE_NOTIFICATION, SMS_STOP_NOTIFICATIONS, TRIP_EMAIL_SUBJECT, TRIP_EMAIL_SUBJECT_FOR_USER, @@ -51,7 +51,6 @@ public enum Message { TRIP_INVITE_PRIMARY_TRAVELER, TRIP_INVITE_OBSERVER, TRIP_NAME_UNDEFINED, - TRIP_TRAVELER_GENERIC_NAME, TRIP_NOT_FOUND_NOTIFICATION, TRIP_NO_LONGER_POSSIBLE_NOTIFICATION, TRIP_REMINDER_NOTIFICATION, diff --git a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java index d3efa746d..3593fa532 100644 --- a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java @@ -1,13 +1,16 @@ package org.opentripplanner.middleware.models; import org.opentripplanner.middleware.i18n.Message; +import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import org.opentripplanner.middleware.triptracker.TravelerPosition; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; + +import static com.mongodb.client.model.Filters.eq; public class LegTransitionNotification { @@ -34,28 +37,27 @@ public LegTransitionNotification( * Create {@link TripMonitorNotification} for leg transition based on notification type. */ @Nullable - public TripMonitorNotification createTripMonitorNotification(NotificationType notificationType) { + private TripMonitorNotification createTripMonitorNotification(NotificationType notificationType) { String body; switch (notificationType) { - case MODE_CHANGE_NOTIFICATION: + case ARRIVED_AND_MODE_CHANGE_NOTIFICATION: body = String.format( - Message.MODE_CHANGE_NOTIFICATION.get(travelerPosition.locale), - getTravelerName(), - travelerPosition.expectedLeg.mode, - travelerPosition.nextLeg.mode + Message.ARRIVED_AND_MODE_CHANGE_NOTIFICATION.get(observerLocale), + travelerName, + travelerPosition.expectedLeg.to.name ); break; case DEPARTED_NOTIFICATION: body = String.format( Message.DEPARTED_NOTIFICATION.get(observerLocale), - getTravelerName(), + travelerName, travelerPosition.expectedLeg.from.name ); break; case ARRIVED_NOTIFICATION: body = String.format( - Message.ARRIVED_NOTIFICATION.get(travelerPosition.locale), - getTravelerName(), + Message.ARRIVED_NOTIFICATION.get(observerLocale), + travelerName, travelerPosition.expectedLeg.to.name ); break; @@ -66,38 +68,23 @@ public TripMonitorNotification createTripMonitorNotification(NotificationType no } /** - * Get the traveler's name if available, if not provide a generic traveler name. + * Get a list of users that should be notified of a traveler's leg transition. */ - private String getTravelerName() { - if (travelerName != null) { - return travelerName; - } else { - return Message.TRIP_TRAVELER_GENERIC_NAME.get(observerLocale); + public static Set getLegTransitionNotifyUsers(MonitoredTrip trip) { + Set notifyUsers = new HashSet<>(); + + if (trip.ownedByPrimary() && trip.companion != null) { + notifyUsers.add(Persistence.otpUsers.getOneFiltered(eq("email", trip.companion.email))); + } else if (trip.ownedByCompanion() && trip.primary != null) { + notifyUsers.add(Persistence.otpUsers.getById(trip.primary.userId)); } - } - /** - * Create locale specific notifications. - */ - public static TripMonitorNotification[] createLegTransitionNotifications( - List legTransitionTypes, - String travelerName, - TravelerPosition travelerPosition, - Locale observerLocale - ) { - List tripMonitorNotifications = new ArrayList<>(); - // Create locale specific notifications. - for (NotificationType legTransitionType : legTransitionTypes) { - LegTransitionNotification legTransitionNotification = new LegTransitionNotification( - travelerName, - legTransitionType, - travelerPosition, - observerLocale - ); - if (legTransitionNotification.tripMonitorNotification != null) { - tripMonitorNotifications.add(legTransitionNotification.tripMonitorNotification); + trip.observers.forEach(observer -> { + if (observer.isConfirmed()) { + notifyUsers.add(Persistence.otpUsers.getOneFiltered(eq("email", observer.email))); } - } - return tripMonitorNotifications.toArray(new TripMonitorNotification[0]); + }); + + return notifyUsers; } -} +} \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 4a49703a5..4d8ee80fa 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -32,6 +32,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static com.mongodb.client.model.Filters.eq; + /** * A monitored trip represents a trip a user would like to receive notification on if affected by a delay and/or route * change. @@ -500,4 +502,19 @@ public TripUsers(MobilityProfileLite primary, RelatedUser companion, List findLastTripSurveyNotificationSent() { if (tripSurveyNotifications == null) return Optional.empty(); return tripSurveyNotifications.stream().max(Comparator.comparingLong(n -> n.timeSent.getTime())); } + + /** + * Use name if available, if not fallback on their email (which is a required field). + */ + public String getAddressee() { + return Strings.isBlank(name) ? email : name; + } } diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index 985970370..cf5fa174b 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.Locale; +import java.util.Objects; /** * Contains information about the type and details of messages to be sent to users about their {@link MonitoredTrip}s. @@ -110,4 +111,17 @@ public static TripMonitorNotification createInitialReminderNotification( ) ); } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + TripMonitorNotification that = (TripMonitorNotification) o; + return type == that.type && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type, body); + } } diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/JourneyState.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/JourneyState.java index e1b54b0ce..86ccaea48 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/JourneyState.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/JourneyState.java @@ -40,7 +40,6 @@ public class JourneyState implements Cloneable { /** * The notifications already sent. - * FIXME this is never set, so it has no effect. */ public Set lastNotifications = new HashSet<>(); diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java index cad8bb17b..3b3bea599 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java @@ -184,7 +184,6 @@ private static boolean sendAcceptDependentEmail(OtpUser dependentUser, OtpUser r String acceptDependentLinkLabel = Message.ACCEPT_DEPENDENT_EMAIL_LINK_TEXT.get(locale); String acceptDependentUrl = getAcceptDependentUrl(acceptKey, locale); - String addressee = (Strings.isBlank(dependentUser.name)) ? dependentUser.email : dependentUser.name; // A HashMap is needed instead of a Map for template data to be serialized to the template renderer. Map templateData = new HashMap<>( @@ -193,7 +192,7 @@ private static boolean sendAcceptDependentEmail(OtpUser dependentUser, OtpUser r "acceptDependentLinkLabelAndUrl", label(acceptDependentLinkLabel, acceptDependentUrl, locale), "acceptDependentUrl", acceptDependentUrl, "emailFooter", Message.ACCEPT_DEPENDENT_EMAIL_FOOTER.get(locale), - "emailGreeting", String.format(Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale), addressee), + "emailGreeting", String.format(Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale), dependentUser.getAddressee()), "manageLinkUrl", String.format("%s%s", OTP_UI_URL, SETTINGS_PATH), "manageLinkText", Message.ACCEPT_DEPENDENT_EMAIL_MANAGE.get(locale) ) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 3411602bc..9b93d962b 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -36,7 +36,6 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -44,6 +43,7 @@ import java.util.function.Supplier; import static com.mongodb.client.model.Filters.eq; +import static org.opentripplanner.middleware.models.LegTransitionNotification.getLegTransitionNotifyUsers; /** * This job handles the primary functions for checking a {@link MonitoredTrip}, including: @@ -251,28 +251,24 @@ private boolean isTrackingOngoing() { } /** - * Process leg transition notifications. + * Process leg transition notification. */ - public void processLegTransition(List legTransitionTypes, TravelerPosition travelerPosition) { - OtpUser otpUser = getOtpUser(); - if (otpUser != null) { - otpUser.relatedUsers.forEach(relatedUser -> { - if (relatedUser.isConfirmed()) { - OtpUser observer = Persistence.otpUsers.getOneFiltered(eq("email", relatedUser.email)); - if (observer != null) { - enqueueNotification( - LegTransitionNotification.createLegTransitionNotifications( - legTransitionTypes, - otpUser.name, - travelerPosition, - I18nUtils.getOtpUserLocale(observer) - ) - ); - sendNotifications(observer); - } - } - }); - } + public void processLegTransition(NotificationType notificationType, TravelerPosition travelerPosition) { + OtpUser tripOwner = getOtpUser(); + Set notifyUsers = getLegTransitionNotifyUsers(trip); + notifyUsers.forEach(observer -> { + if (observer != null) { + enqueueNotification( + new LegTransitionNotification( + tripOwner.getAddressee(), + notificationType, + travelerPosition, + I18nUtils.getOtpUserLocale(observer) + ).tripMonitorNotification + ); + sendNotifications(observer); + } + }); } /** @@ -560,7 +556,7 @@ private void sendNotifications(OtpUser otpUser) { return; } - Locale locale = getOtpUserLocale(); + Locale locale = I18nUtils.getOtpUserLocale(otpUser); String tripNameOrReminder = hasInitialReminder ? initialReminderNotification.body : trip.tripName; if (tripNameOrReminder == null) { tripNameOrReminder = Message.TRIP_NAME_UNDEFINED.get(locale); @@ -585,7 +581,7 @@ private void sendNotifications(OtpUser otpUser) { boolean successSms = false; if (otpUser.notificationChannel.contains(OtpUser.Notification.EMAIL)) { - successEmail = sendEmail(otpUser, templateData); + successEmail = sendEmail(otpUser, templateData, locale); } if (otpUser.notificationChannel.contains(OtpUser.Notification.PUSH)) { successPush = sendPush(otpUser, templateData); @@ -597,6 +593,9 @@ private void sendNotifications(OtpUser otpUser) { // TODO: better handle below when one of the following fails if (successEmail || successPush || successSms) { notificationTimestampMillis = DateTimeUtils.currentTimeMillis(); + // Prevent repeated notifications by saving successfully sent notifications. + trip.journeyState.lastNotifications.addAll(notifications); + Persistence.monitoredTrips.replace(trip.id, trip); } } @@ -617,8 +616,7 @@ private boolean sendPush(OtpUser otpUser, Map data) { /** * Send notification email in MonitoredTrip template. */ - private boolean sendEmail(OtpUser otpUser, Map data) { - Locale locale = getOtpUserLocale(); + private boolean sendEmail(OtpUser otpUser, Map data, Locale locale) { String subject = NotificationUtils.getTripEmailSubject(otpUser, locale, trip); return NotificationUtils.sendEmail( otpUser, diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java index 62d0fa422..4a40911cc 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java @@ -12,7 +12,7 @@ public enum NotificationType { ALERT_FOUND, ITINERARY_NOT_FOUND, INITIAL_REMINDER, - MODE_CHANGE_NOTIFICATION, + ARRIVED_AND_MODE_CHANGE_NOTIFICATION, DEPARTED_NOTIFICATION, ARRIVED_NOTIFICATION } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java index e478ed6dc..0d2afce90 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java @@ -36,7 +36,7 @@ import java.util.stream.IntStream; import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_NOTIFICATION; -import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.MODE_CHANGE_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION; import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.DEPARTED_NOTIFICATION; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.NO_INSTRUCTION; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_IMMEDIATE_RADIUS; @@ -101,35 +101,38 @@ public static String getInstruction( return NO_INSTRUCTION; } + /** + * If a traveler is on schedule and on either a walk or transit leg check for possible leg transition notification. + */ public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition travelerPosition, MonitoredTrip trip) { if ( hasRequiredTripStatus(tripStatus) && (hasRequiredWalkLeg(travelerPosition) || hasRequiredTransitLeg(travelerPosition)) ) { - List legTransitionTypes = getLegTransitionTypes(travelerPosition); - if (!legTransitionTypes.isEmpty()) { + NotificationType notificationType = getLegTransitionNotificationType(travelerPosition); + if (notificationType != null) { try { - new CheckMonitoredTrip(trip).processLegTransition(legTransitionTypes, travelerPosition); + new CheckMonitoredTrip(trip).processLegTransition(notificationType, travelerPosition); } catch (CloneNotSupportedException e) { LOG.error("Error encountered while checking leg transition.", e); } } } - } - private static List getLegTransitionTypes(TravelerPosition travelerPosition) { - List notificationTypes = new ArrayList<>(); + /** + * Depending on the traveler's proximity to the start/end of a leg return the appropriate notification type. + */ + private static NotificationType getLegTransitionNotificationType(TravelerPosition travelerPosition) { if (isAtStartOfLeg(travelerPosition)) { - notificationTypes.add(DEPARTED_NOTIFICATION); - } - if (isAtEndOfLeg(travelerPosition)) { - notificationTypes.add(ARRIVED_NOTIFICATION); - } - if (hasModeChanged(travelerPosition)) { - notificationTypes.add(MODE_CHANGE_NOTIFICATION); + return DEPARTED_NOTIFICATION; + } else if (isApproachingEndOfLeg(travelerPosition)) { + if (hasModeChanged(travelerPosition)) { + return ARRIVED_AND_MODE_CHANGE_NOTIFICATION; + } + return ARRIVED_NOTIFICATION; } - return notificationTypes; + return null; } /** @@ -138,7 +141,10 @@ private static List getLegTransitionTypes(TravelerPosition tra private static boolean hasModeChanged(TravelerPosition travelerPosition) { Leg nextLeg = travelerPosition.nextLeg; Leg expectedLeg = travelerPosition.expectedLeg; - return isAtEndOfLeg(travelerPosition) && nextLeg != null && !nextLeg.mode.equalsIgnoreCase(expectedLeg.mode); + return + isApproachingEndOfLeg(travelerPosition) && + nextLeg != null && + !nextLeg.mode.equalsIgnoreCase(expectedLeg.mode); } /** diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index 646c7ec9f..9011c6ddc 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -371,4 +371,4 @@ public static Leg getFirstLeg(Itinerary itinerary) { .map(legs -> legs.get(0)) .orElse(null); } -} \ No newline at end of file +} diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index 651cbfeb8..df70ee760 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,9 +4,9 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. +ARRIVED_AND_MODE_CHANGE_NOTIFICATION = %s has arrived at transit stop %s. ARRIVED_NOTIFICATION = %s has arrived at %s. LABEL_AND_CONTENT = %s: %s -MODE_CHANGE_NOTIFICATION = %s has changed transit from %s to %s. DEPARTED_NOTIFICATION = %s has departed %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification @@ -36,7 +36,6 @@ TRIP_INVITE_COMPANION = %s added you as a companion for their trip. TRIP_INVITE_PRIMARY_TRAVELER = %s made you the primary traveler on this trip. TRIP_INVITE_OBSERVER = %s added you as an observer for their trip. TRIP_NAME_UNDEFINED = Trip name undefined -TRIP_TRAVELER_GENERIC_NAME = Traveler TRIP_NOT_FOUND_NOTIFICATION = Your itinerary was not found in today's trip planner results. Please check real-time conditions and plan a new trip. TRIP_NO_LONGER_POSSIBLE_NOTIFICATION = Your itinerary is no longer possible on any monitored day of the week. Please plan and save a new trip. TRIP_REMINDER_NOTIFICATION = Reminder for %s at %s. diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index 1a0a88c50..ac826b816 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,9 +4,9 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. +ARRIVED_AND_MODE_CHANGE_NOTIFICATION = %s TODO %s. ARRIVED_NOTIFICATION = TODO %s. LABEL_AND_CONTENT = %s\u00A0: %s -MODE_CHANGE_NOTIFICATION = TODO %s TODO %s. DEPARTED_NOTIFICATION = TODO %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s @@ -36,7 +36,6 @@ TRIP_INVITE_COMPANION = %s vous a ajout TRIP_INVITE_PRIMARY_TRAVELER = %s vous a fait le voyageur principal sur ce trajet. TRIP_INVITE_OBSERVER = %s vous a ajouté comme observateur·trice pour un trajet. TRIP_NAME_UNDEFINED = TODO -TRIP_TRAVELER_GENERIC_NAME = TODO TRIP_NOT_FOUND_NOTIFICATION = Votre trajet est introuvable aujourd'hui. Veuillez vérifier les conditions en temps-réel et recherchez un nouveau trajet. TRIP_NO_LONGER_POSSIBLE_NOTIFICATION = Votre trajet n'est plus possible dans aucun jour de suivi de la semaine. Veuillez rechercher et enregistrer un nouveau trajet. TRIP_REMINDER_NOTIFICATION = Rappel pour %s à %s. diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 3d4e09dc7..28389f6a1 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -56,10 +56,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -90,10 +90,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -123,10 +123,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/admin/user: get: tags: @@ -155,10 +155,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/admin/user" @@ -178,10 +178,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -220,10 +220,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -269,10 +269,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -309,10 +309,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/AdminUser" schema: $ref: "#/definitions/AdminUser" + responseSchema: + $ref: "#/definitions/AdminUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -356,10 +356,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -402,10 +402,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -447,10 +447,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TokenHolder" schema: $ref: "#/definitions/TokenHolder" + responseSchema: + $ref: "#/definitions/TokenHolder" /api/secure/application/fromtoken: get: tags: @@ -464,10 +464,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -498,10 +498,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -531,10 +531,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/application: get: tags: @@ -563,10 +563,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/application" @@ -586,10 +586,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -628,10 +628,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -677,10 +677,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -717,10 +717,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ApiUser" schema: $ref: "#/definitions/ApiUser" + responseSchema: + $ref: "#/definitions/ApiUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -754,10 +754,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -788,10 +788,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -821,10 +821,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/cdp: get: tags: @@ -853,10 +853,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/cdp" @@ -876,10 +876,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -918,10 +918,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -967,10 +967,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1007,10 +1007,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/CDPUser" schema: $ref: "#/definitions/CDPUser" + responseSchema: + $ref: "#/definitions/CDPUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1050,10 +1050,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/ItineraryExistence" schema: $ref: "#/definitions/ItineraryExistence" + responseSchema: + $ref: "#/definitions/ItineraryExistence" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1102,10 +1102,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredtrip" @@ -1125,10 +1125,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1167,10 +1167,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1216,10 +1216,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1257,10 +1257,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredTrip" schema: $ref: "#/definitions/MonitoredTrip" + responseSchema: + $ref: "#/definitions/MonitoredTrip" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1298,10 +1298,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/updatetracking: post: tags: @@ -1319,10 +1319,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/track: post: tags: @@ -1340,10 +1340,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TrackingResponse" schema: $ref: "#/definitions/TrackingResponse" + responseSchema: + $ref: "#/definitions/TrackingResponse" /api/secure/monitoredtrip/endtracking: post: tags: @@ -1361,10 +1361,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/EndTrackingResponse" schema: $ref: "#/definitions/EndTrackingResponse" + responseSchema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/monitoredtrip/forciblyendtracking: post: tags: @@ -1382,10 +1382,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/EndTrackingResponse" schema: $ref: "#/definitions/EndTrackingResponse" + responseSchema: + $ref: "#/definitions/EndTrackingResponse" /api/secure/triprequests: get: tags: @@ -1430,10 +1430,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/TripRequest" schema: $ref: "#/definitions/TripRequest" + responseSchema: + $ref: "#/definitions/TripRequest" /api/secure/monitoredcomponent: get: tags: @@ -1462,10 +1462,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/monitoredcomponent" @@ -1485,10 +1485,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1527,10 +1527,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1576,10 +1576,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1617,10 +1617,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MonitoredComponent" schema: $ref: "#/definitions/MonitoredComponent" + responseSchema: + $ref: "#/definitions/MonitoredComponent" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1651,10 +1651,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1686,10 +1686,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/MobilityProfileLite" schema: $ref: "#/definitions/MobilityProfileLite" + responseSchema: + $ref: "#/definitions/MobilityProfileLite" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1729,10 +1729,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/VerificationResult" schema: $ref: "#/definitions/VerificationResult" + responseSchema: + $ref: "#/definitions/VerificationResult" /api/secure/user/{id}/verify_sms/{code}: post: tags: @@ -1752,10 +1752,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/VerificationResult" schema: $ref: "#/definitions/VerificationResult" + responseSchema: + $ref: "#/definitions/VerificationResult" /api/secure/user/fromtoken: get: tags: @@ -1769,10 +1769,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1803,10 +1803,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1836,10 +1836,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/Job" schema: $ref: "#/definitions/Job" + responseSchema: + $ref: "#/definitions/Job" /api/secure/user: get: tags: @@ -1868,10 +1868,10 @@ paths: responses: "200": description: "successful operation" - responseSchema: - $ref: "#/definitions/ResponseList" schema: $ref: "#/definitions/ResponseList" + responseSchema: + $ref: "#/definitions/ResponseList" post: tags: - "api/secure/user" @@ -1891,10 +1891,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1933,10 +1933,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -1982,10 +1982,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2022,10 +2022,10 @@ paths: "200": description: "Successful operation" examples: {} - responseSchema: - $ref: "#/definitions/OtpUser" schema: $ref: "#/definitions/OtpUser" + responseSchema: + $ref: "#/definitions/OtpUser" "400": description: "The request was not formed properly (e.g., some required parameters\ \ may be missing). See the details of the returned response to determine\ @@ -2079,11 +2079,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/ApiUsageResult" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/ApiUsageResult" @@ -2110,11 +2110,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/BugsnagEvent" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/BugsnagEvent" @@ -2141,11 +2141,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/CDPFile" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/CDPFile" @@ -2166,11 +2166,11 @@ paths: responses: "200": description: "successful operation" - responseSchema: + schema: type: "array" items: $ref: "#/definitions/URL" - schema: + responseSchema: type: "array" items: $ref: "#/definitions/URL" @@ -2376,7 +2376,7 @@ definitions: - "ALERT_FOUND" - "ITINERARY_NOT_FOUND" - "INITIAL_REMINDER" - - "MODE_CHANGE_NOTIFICATION" + - "ARRIVED_AND_MODE_CHANGE_NOTIFICATION" - "DEPARTED_NOTIFICATION" - "ARRIVED_NOTIFICATION" body: diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index f6140a1e0..28c8e2cc4 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -11,8 +11,11 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.LegTransitionNotification; +import org.opentripplanner.middleware.models.MobilityProfileLite; +import org.opentripplanner.middleware.models.RelatedUser; import org.opentripplanner.middleware.models.TrackedJourney; import org.opentripplanner.middleware.otp.response.Leg; +import org.opentripplanner.middleware.testutils.ApiTestUtils; import org.opentripplanner.middleware.testutils.OtpMiddlewareTestEnvironment; import org.opentripplanner.middleware.testutils.OtpTestUtils; import org.opentripplanner.middleware.testutils.PersistenceTestUtils; @@ -39,6 +42,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -64,6 +68,9 @@ public class CheckMonitoredTripTest extends OtpMiddlewareTestEnvironment { private static final Logger LOG = LoggerFactory.getLogger(CheckMonitoredTripTest.class); private static OtpUser user; + private static OtpUser primary; + private static OtpUser companion; + private static OtpUser observer; // this is initialized in the setup method after the OTP_TIMEZONE config value is known. private static final ZonedDateTime noonMonday8June2020 = DateTimeUtils.makeOtpZonedDateTime(new Date()) @@ -76,11 +83,14 @@ public class CheckMonitoredTripTest extends OtpMiddlewareTestEnvironment { @BeforeAll public static void setup() { user = PersistenceTestUtils.createUser("user@example.com"); + primary = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-primary-user")); + companion = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-companion-user")); + observer = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-observer-user")); } @AfterAll public static void tearDown() { - Persistence.otpUsers.removeById(user.id); + PersistenceTestUtils.deleteOtpUser(false, user, primary, companion, observer); for (MonitoredTrip trip : Persistence.monitoredTrips.getFiltered(eq("userId", user.id))) { PersistenceTestUtils.deleteMonitoredTrip(trip); } @@ -223,20 +233,20 @@ private static Stream createDelayNotificationTestCases() { @ParameterizedTest @MethodSource("createLegTransitionNotificationTestCases") void testLegTransitionNotifications( - NotificationType legTransitionType, + NotificationType notificationType, String travelerName, TravelerPosition travelerPosition, Locale locale, String message ) { - TripMonitorNotification[] notification = LegTransitionNotification.createLegTransitionNotifications( - List.of(legTransitionType), + TripMonitorNotification notification = new LegTransitionNotification( travelerName, + notificationType, travelerPosition, locale - ); - assertNotNull(notification[0]); - assertEquals(message, notification[0].body); + ).tripMonitorNotification; + assertNotNull(notification); + assertEquals(message, notification.body); } private static Stream createLegTransitionNotificationTestCases() throws Exception { @@ -249,11 +259,11 @@ private static Stream createLegTransitionNotificationTestCases() thro Coordinates nextLegDepartureCoords = new Coordinates(nextLeg.from); return Stream.of( Arguments.of( - NotificationType.MODE_CHANGE_NOTIFICATION, + NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION, travelerName, new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), locale, - "Obi-Wan has changed transit from TRAM to WALK." + "Obi-Wan has arrived at transit stop Pioneer Square South MAX Station." ), Arguments.of( NotificationType.DEPARTED_NOTIFICATION, @@ -268,17 +278,40 @@ private static Stream createLegTransitionNotificationTestCases() thro new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), locale, "Obi-Wan has arrived at Pioneer Square South MAX Station." - ), - Arguments.of( - NotificationType.ARRIVED_NOTIFICATION, - null, - new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), - locale, - "Traveler has arrived at Pioneer Square South MAX Station." ) ); } + @ParameterizedTest + @MethodSource("createLegTransitionNotifyUsersTestCases") + void testLegTransitionNotifyUsers( + String tripOwnerUserId, + Set expectedUsers + ) { + + RelatedUser relatedUser = new RelatedUser(); + relatedUser.email = observer.email; + relatedUser.status = RelatedUser.RelatedUserStatus.CONFIRMED; + + MonitoredTrip trip = new MonitoredTrip(); + trip.userId = tripOwnerUserId; + trip.primary = new MobilityProfileLite(primary); + trip.companion = new RelatedUser(companion.email, RelatedUser.RelatedUserStatus.CONFIRMED); + trip.observers.add(relatedUser); + + Set users = LegTransitionNotification.getLegTransitionNotifyUsers(trip); + assertNotNull(users); + assertTrue(users.containsAll(expectedUsers)); + assertEquals(users.size(), expectedUsers.size()); + } + + private static Stream createLegTransitionNotifyUsersTestCases() { + return Stream.of( + Arguments.of(primary.id, Set.of(companion, observer)), + Arguments.of(companion.id, Set.of(primary, observer)) + ); + } + /** * Convenience method for creating a CheckMonitoredTrip instance with the default journey state. */ From 17363ac3b484da890fbdbf4e7e0f56ad7143d694 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Fri, 13 Dec 2024 11:01:26 +0000 Subject: [PATCH 08/15] refactor(Refactored otp user to provide display name): --- .../org/opentripplanner/middleware/models/OtpUser.java | 6 +++--- .../middleware/models/TripMonitorNotification.java | 10 ++++++++-- .../middleware/tripmonitor/TrustedCompanion.java | 2 +- .../tripmonitor/jobs/CheckMonitoredTrip.java | 2 +- .../middleware/utils/NotificationUtils.java | 3 +-- .../tripmonitor/jobs/CheckMonitoredTripTest.java | 2 +- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java index 852a0c5ec..2992378e5 100644 --- a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java +++ b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java @@ -213,9 +213,9 @@ public Optional findLastTripSurveyNotificationSent() { } /** - * Use name if available, if not fallback on their email (which is a required field). + * Use name if available, if not fallback on email (which is a required field). */ - public String getAddressee() { - return Strings.isBlank(name) ? email : name; + public String getDisplayedName() { + return Strings.isBlank(name) ? email.replace("@", " at ") : name; } } diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index cf5fa174b..5a6d8f256 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -17,8 +17,8 @@ public class TripMonitorNotification extends Model { private static final Logger LOG = LoggerFactory.getLogger(TripMonitorNotification.class); public static final String STOPWATCH_ICON = "â±"; - public final NotificationType type; - public final String body; + public NotificationType type; + public String body; /** Getter functions are used by HTML template renderer */ public String getBody() { @@ -29,6 +29,12 @@ public NotificationType getType() { return type; } + /** + * This no-arg constructor exists to make MongoDB happy. + */ + public TripMonitorNotification() { + } + public TripMonitorNotification(NotificationType type, String body) { this.type = type; this.body = body; diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java index 3b3bea599..6e796ab6c 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/TrustedCompanion.java @@ -192,7 +192,7 @@ private static boolean sendAcceptDependentEmail(OtpUser dependentUser, OtpUser r "acceptDependentLinkLabelAndUrl", label(acceptDependentLinkLabel, acceptDependentUrl, locale), "acceptDependentUrl", acceptDependentUrl, "emailFooter", Message.ACCEPT_DEPENDENT_EMAIL_FOOTER.get(locale), - "emailGreeting", String.format(Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale), dependentUser.getAddressee()), + "emailGreeting", String.format(Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale), dependentUser.getDisplayedName()), "manageLinkUrl", String.format("%s%s", OTP_UI_URL, SETTINGS_PATH), "manageLinkText", Message.ACCEPT_DEPENDENT_EMAIL_MANAGE.get(locale) ) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index b8178b226..0a70fedff 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -259,7 +259,7 @@ public void processLegTransition(NotificationType notificationType, TravelerPosi if (observer != null) { enqueueNotification( new LegTransitionNotification( - tripOwner.getAddressee(), + tripOwner.getDisplayedName(), notificationType, travelerPosition, I18nUtils.getOtpUserLocale(observer) diff --git a/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java b/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java index 251f39240..e94233f08 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java @@ -498,8 +498,7 @@ public static String replaceUserNameInFromEmail(String fromEmail, OtpUser otpUse int lastBracketIndex = fromEmail.indexOf('>'); // HACK: If falling back on email, replace the "@" sign so that the user's email does not override the // application email in brackets. - String displayedName = Strings.isBlank(otpUser.name) ? otpUser.email.replace("@", " at ") : otpUser.name; - return String.format("%s %s", displayedName, fromEmail.substring(firstBracketIndex, lastBracketIndex + 1)); + return String.format("%s %s", otpUser.getDisplayedName(), fromEmail.substring(firstBracketIndex, lastBracketIndex + 1)); } /** diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 720f1f476..80d90ff00 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -807,4 +807,4 @@ private static Stream createCanUnsnoozeTripCases() { Arguments.of(noonMonday8June2020, tuesday, true) ); } -} \ No newline at end of file +} From bc20b847667682a9c1c1e368afaf80660c73d2f1 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Fri, 13 Dec 2024 11:16:35 +0000 Subject: [PATCH 09/15] refactor(Moved various methods into the LegTransitionNotification class): --- .../models/LegTransitionNotification.java | 60 +++++++++++++++++ .../triptracker/ManageTripTracking.java | 3 +- .../triptracker/TravelerLocator.java | 64 ++----------------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java index 3593fa532..731aac664 100644 --- a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java @@ -1,9 +1,14 @@ package org.opentripplanner.middleware.models; import org.opentripplanner.middleware.i18n.Message; +import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.persistence.Persistence; +import org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip; import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import org.opentripplanner.middleware.triptracker.TravelerPosition; +import org.opentripplanner.middleware.triptracker.TripStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.HashSet; @@ -11,8 +16,17 @@ import java.util.Set; import static com.mongodb.client.model.Filters.eq; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.DEPARTED_NOTIFICATION; +import static org.opentripplanner.middleware.triptracker.TravelerLocator.hasRequiredTransitLeg; +import static org.opentripplanner.middleware.triptracker.TravelerLocator.hasRequiredTripStatus; +import static org.opentripplanner.middleware.triptracker.TravelerLocator.hasRequiredWalkLeg; +import static org.opentripplanner.middleware.triptracker.TravelerLocator.isApproachingEndOfLeg; +import static org.opentripplanner.middleware.triptracker.TravelerLocator.isAtStartOfLeg; public class LegTransitionNotification { + private static final Logger LOG = LoggerFactory.getLogger(LegTransitionNotification.class); public String travelerName; public NotificationType notificationType; @@ -87,4 +101,50 @@ public static Set getLegTransitionNotifyUsers(MonitoredTrip trip) { return notifyUsers; } + + /** + * If a traveler is on schedule and on either a walk or transit leg check for possible leg transition notification. + */ + public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition travelerPosition, MonitoredTrip trip) { + if ( + hasRequiredTripStatus(tripStatus) && + (hasRequiredWalkLeg(travelerPosition) || hasRequiredTransitLeg(travelerPosition)) + ) { + NotificationType notificationType = getLegTransitionNotificationType(travelerPosition); + if (notificationType != null) { + try { + new CheckMonitoredTrip(trip).processLegTransition(notificationType, travelerPosition); + } catch (CloneNotSupportedException e) { + LOG.error("Error encountered while checking leg transition.", e); + } + } + } + } + + /** + * Depending on the traveler's proximity to the start/end of a leg return the appropriate notification type. + */ + private static NotificationType getLegTransitionNotificationType(TravelerPosition travelerPosition) { + if (isAtStartOfLeg(travelerPosition)) { + return DEPARTED_NOTIFICATION; + } else if (isApproachingEndOfLeg(travelerPosition)) { + if (hasModeChanged(travelerPosition)) { + return ARRIVED_AND_MODE_CHANGE_NOTIFICATION; + } + return ARRIVED_NOTIFICATION; + } + return null; + } + + /** + * The traveler is at the end of the current leg and the mode has changed between this and the next leg. + */ + private static boolean hasModeChanged(TravelerPosition travelerPosition) { + Leg nextLeg = travelerPosition.nextLeg; + Leg expectedLeg = travelerPosition.expectedLeg; + return + isApproachingEndOfLeg(travelerPosition) && + nextLeg != null && + !nextLeg.mode.equalsIgnoreCase(expectedLeg.mode); + } } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java index 83e74c19b..64c090a6b 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java @@ -1,6 +1,7 @@ package org.opentripplanner.middleware.triptracker; import org.eclipse.jetty.http.HttpStatus; +import org.opentripplanner.middleware.models.LegTransitionNotification; import org.opentripplanner.middleware.models.TrackedJourney; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.persistence.Persistence; @@ -80,7 +81,7 @@ private static TrackingResponse doUpdateTracking(Request request, TripTrackingDa ); } - TravelerLocator.checkForLegTransition(tripStatus, travelerPosition, tripData.trip); + LegTransitionNotification.checkForLegTransition(tripStatus, travelerPosition, tripData.trip); // Provide response. TripInstruction instruction = TravelerLocator.getInstruction(tripStatus, travelerPosition, create); diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java index 6fd076809..a6e32d926 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerLocator.java @@ -1,12 +1,9 @@ package org.opentripplanner.middleware.triptracker; import io.leonard.PolylineUtils; -import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.response.Step; -import org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip; -import org.opentripplanner.middleware.tripmonitor.jobs.NotificationType; import org.opentripplanner.middleware.triptracker.instruction.ContinueInstruction; import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction; import org.opentripplanner.middleware.triptracker.instruction.GetOffHereTransitInstruction; @@ -20,8 +17,6 @@ import org.opentripplanner.middleware.utils.Coordinates; import org.opentripplanner.middleware.utils.ConvertsToCoordinates; import org.opentripplanner.middleware.utils.DateTimeUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.time.Duration; @@ -35,9 +30,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_NOTIFICATION; -import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION; -import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.DEPARTED_NOTIFICATION; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_IMMEDIATE_RADIUS; import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_UPCOMING_RADIUS; import static org.opentripplanner.middleware.utils.GeometryUtils.getDistance; @@ -50,8 +42,6 @@ */ public class TravelerLocator { - private static final Logger LOG = LoggerFactory.getLogger(TravelerLocator.class); - public static final int ACCEPTABLE_AHEAD_OF_SCHEDULE_IN_MINUTES = 15; private static final int MIN_TRANSIT_VEHICLE_SPEED = 5; // meters per second. 11.1 mph or 18 km/h. @@ -92,56 +82,10 @@ public static TripInstruction getInstruction( return null; } - /** - * If a traveler is on schedule and on either a walk or transit leg check for possible leg transition notification. - */ - public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition travelerPosition, MonitoredTrip trip) { - if ( - hasRequiredTripStatus(tripStatus) && - (hasRequiredWalkLeg(travelerPosition) || hasRequiredTransitLeg(travelerPosition)) - ) { - NotificationType notificationType = getLegTransitionNotificationType(travelerPosition); - if (notificationType != null) { - try { - new CheckMonitoredTrip(trip).processLegTransition(notificationType, travelerPosition); - } catch (CloneNotSupportedException e) { - LOG.error("Error encountered while checking leg transition.", e); - } - } - } - } - - /** - * Depending on the traveler's proximity to the start/end of a leg return the appropriate notification type. - */ - private static NotificationType getLegTransitionNotificationType(TravelerPosition travelerPosition) { - if (isAtStartOfLeg(travelerPosition)) { - return DEPARTED_NOTIFICATION; - } else if (isApproachingEndOfLeg(travelerPosition)) { - if (hasModeChanged(travelerPosition)) { - return ARRIVED_AND_MODE_CHANGE_NOTIFICATION; - } - return ARRIVED_NOTIFICATION; - } - return null; - } - - /** - * The traveler is at the end of the current leg and the mode has changed between this and the next leg. - */ - private static boolean hasModeChanged(TravelerPosition travelerPosition) { - Leg nextLeg = travelerPosition.nextLeg; - Leg expectedLeg = travelerPosition.expectedLeg; - return - isApproachingEndOfLeg(travelerPosition) && - nextLeg != null && - !nextLeg.mode.equalsIgnoreCase(expectedLeg.mode); - } - /** * Has required walk leg. */ - private static boolean hasRequiredWalkLeg(TravelerPosition travelerPosition) { + public static boolean hasRequiredWalkLeg(TravelerPosition travelerPosition) { return travelerPosition.expectedLeg != null && travelerPosition.expectedLeg.mode.equalsIgnoreCase("walk"); @@ -150,7 +94,7 @@ private static boolean hasRequiredWalkLeg(TravelerPosition travelerPosition) { /** * Has required transit leg. */ - private static boolean hasRequiredTransitLeg(TravelerPosition travelerPosition) { + public static boolean hasRequiredTransitLeg(TravelerPosition travelerPosition) { return travelerPosition.expectedLeg != null && travelerPosition.expectedLeg.transitLeg; @@ -159,7 +103,7 @@ private static boolean hasRequiredTransitLeg(TravelerPosition travelerPosition) /** * The trip instruction can only be provided if the traveler is close to the indicated route. */ - private static boolean hasRequiredTripStatus(TripStatus tripStatus) { + public static boolean hasRequiredTripStatus(TripStatus tripStatus) { return !tripStatus.equals(TripStatus.DEVIATED) && !tripStatus.equals(TripStatus.ENDED); } @@ -378,7 +322,7 @@ private static boolean isPositionPastStep(TravelerPosition travelerPosition, Con /** * Is the traveler approaching the leg destination. */ - private static boolean isApproachingEndOfLeg(TravelerPosition travelerPosition) { + public static boolean isApproachingEndOfLeg(TravelerPosition travelerPosition) { return getDistanceToEndOfLeg(travelerPosition) <= TRIP_INSTRUCTION_UPCOMING_RADIUS; } From 4e788c57d79f78aba92fabe91b50e524a4e14872 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Fri, 13 Dec 2024 12:48:59 +0000 Subject: [PATCH 10/15] refactor(Created builder to handle basic traveler position needs): --- .../triptracker/TravelerPosition.java | 101 +++++++++++------- .../models/LegTransitionNotificationTest.java | 19 +++- .../triptracker/ManageLegTraversalTest.java | 13 ++- .../triptracker/NotifyBusOperatorTest.java | 39 +++++-- 4 files changed, 118 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java index fa5c14a87..53b3328e7 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/TravelerPosition.java @@ -48,6 +48,20 @@ public class TravelerPosition { /** The first leg of the trip. **/ public Leg firstLegOfTrip; + public TravelerPosition(Builder builder) { + this.expectedLeg = builder.expectedLeg; + this.currentPosition = builder.currentPosition; + this.speed = builder.speed; + this.firstLegOfTrip = builder.firstLegOfTrip; + if (expectedLeg != null && currentPosition != null) { + this.legSegmentFromPosition = getSegmentFromPosition(expectedLeg, currentPosition); + } + this.nextLeg = builder.nextLeg; + this.currentTime = builder.currentTime; + this.trackedJourney = builder.trackedJourney; + + } + public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpUser otpUser) { TrackingLocation lastLocation = trackedJourney.locations.get(trackedJourney.locations.size() - 1); currentTime = lastLocation.timestamp.toInstant(); @@ -68,50 +82,61 @@ public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpU firstLegOfTrip = getFirstLeg(itinerary); } - /** Used for unit testing. */ - public TravelerPosition(Leg expectedLeg, Coordinates currentPosition, int speed) { - this.expectedLeg = expectedLeg; - this.currentPosition = currentPosition; - this.speed = speed; - legSegmentFromPosition = getSegmentFromPosition(expectedLeg, currentPosition); + /** Computes the current deviation in meters from the expected itinerary. */ + public double getDeviationMeters() { + return getDistanceFromLine(legSegmentFromPosition.start, legSegmentFromPosition.end, currentPosition); } - /** Used for unit testing. */ - public TravelerPosition(Leg expectedLeg, Coordinates currentPosition) { - // Anywhere the speed is zero means that speed is not considered for a specific logic. - this(expectedLeg, currentPosition, 0); - } + /** + * Builder to handle basic unit test requirements. + */ + public static final class Builder { + + private Leg expectedLeg; + private Coordinates currentPosition; + private int speed; + private Leg firstLegOfTrip; + private Leg nextLeg; + private Instant currentTime; + private TrackedJourney trackedJourney; + + public Builder setExpectedLeg(Leg expectedLeg) { + this.expectedLeg = expectedLeg; + return this; + } - /** Used for unit testing. */ - public TravelerPosition(Leg expectedLeg, Coordinates currentPosition, Leg firstLegOfTrip) { - // Anywhere the speed is zero means that speed is not considered for a specific logic. - this(expectedLeg, currentPosition, 0); - this.firstLegOfTrip = firstLegOfTrip; - } + public Builder setCurrentPosition(Coordinates currentPosition) { + this.currentPosition = currentPosition; + return this; + } - /** Used for unit testing. */ - public TravelerPosition(Leg nextLeg, Instant currentTime) { - this.nextLeg = nextLeg; - this.currentTime = currentTime; - } + public Builder setSpeed(int speed) { + this.speed = speed; + return this; + } - /** Used for unit testing. */ - public TravelerPosition(Leg expectedLeg, TrackedJourney trackedJourney, Leg first, Coordinates currentPosition) { - this.expectedLeg = expectedLeg; - this.trackedJourney = trackedJourney; - this.firstLegOfTrip = first; - this.currentPosition = currentPosition; - } + public Builder setFirstLegOfTrip(Leg firstLegOfTrip) { + this.firstLegOfTrip = firstLegOfTrip; + return this; + } - /** Used for unit testing. */ - public TravelerPosition(Leg expectedLeg, Leg nextLeg, Coordinates currentPosition) { - this.expectedLeg = expectedLeg; - this.nextLeg = nextLeg; - this.currentPosition = currentPosition; - } + public Builder setNextLeg(Leg nextLeg) { + this.nextLeg = nextLeg; + return this; + } - /** Computes the current deviation in meters from the expected itinerary. */ - public double getDeviationMeters() { - return getDistanceFromLine(legSegmentFromPosition.start, legSegmentFromPosition.end, currentPosition); + public Builder setCurrentTime(Instant currentTime) { + this.currentTime = currentTime; + return this; + } + + public Builder setTrackedJourney(TrackedJourney trackedJourney) { + this.trackedJourney = trackedJourney; + return this; + } + + public TravelerPosition build() { + return new TravelerPosition(this); + } } } diff --git a/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java b/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java index 26791c6d8..eb2191f43 100644 --- a/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java +++ b/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java @@ -40,7 +40,6 @@ public static void tearDown() { PersistenceTestUtils.deleteOtpUser(false, primary, companion, observer); } - @ParameterizedTest @MethodSource("createLegTransitionNotificationTestCases") void testLegTransitionNotifications( @@ -72,21 +71,33 @@ private static Stream createLegTransitionNotificationTestCases() thro Arguments.of( NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION, travelerName, - new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), + new TravelerPosition.Builder() + .setExpectedLeg(expectedLeg) + .setNextLeg(nextLeg) + .setCurrentPosition(expectedLegDestinationCoords) + .build(), locale, "Obi-Wan has arrived at transit stop Pioneer Square South MAX Station." ), Arguments.of( NotificationType.DEPARTED_NOTIFICATION, travelerName, - new TravelerPosition(expectedLeg, nextLeg, nextLegDepartureCoords), + new TravelerPosition.Builder() + .setExpectedLeg(expectedLeg) + .setNextLeg(nextLeg) + .setCurrentPosition(nextLegDepartureCoords) + .build(), locale, "Obi-Wan has departed Providence Park MAX Station." ), Arguments.of( NotificationType.ARRIVED_NOTIFICATION, travelerName, - new TravelerPosition(expectedLeg, nextLeg, expectedLegDestinationCoords), + new TravelerPosition.Builder() + .setExpectedLeg(expectedLeg) + .setNextLeg(nextLeg) + .setCurrentPosition(expectedLegDestinationCoords) + .build(), locale, "Obi-Wan has arrived at Pioneer Square South MAX Station." ) diff --git a/src/test/java/org/opentripplanner/middleware/triptracker/ManageLegTraversalTest.java b/src/test/java/org/opentripplanner/middleware/triptracker/ManageLegTraversalTest.java index 2095867d7..ae472138d 100644 --- a/src/test/java/org/opentripplanner/middleware/triptracker/ManageLegTraversalTest.java +++ b/src/test/java/org/opentripplanner/middleware/triptracker/ManageLegTraversalTest.java @@ -178,7 +178,12 @@ private static Stream createTrace() { @ParameterizedTest @MethodSource("createTurnByTurnTrace") void canTrackTurnByTurn(Leg firstLeg, TraceData traceData) { - TravelerPosition travelerPosition = new TravelerPosition(firstLeg, traceData.position, firstLeg); + TravelerPosition travelerPosition = new TravelerPosition.Builder() + .setExpectedLeg(firstLeg) + .setCurrentPosition(traceData.position) + .setFirstLegOfTrip(firstLeg) + .setSpeed(0) + .build(); TripInstruction tripInstruction = TravelerLocator.getInstruction(traceData.tripStatus, travelerPosition, traceData.isStartOfTrip); assertEquals(traceData.expectedInstruction, tripInstruction != null ? tripInstruction.build() : NO_INSTRUCTION, traceData.message); } @@ -411,7 +416,11 @@ void canTrackTransitRide(TraceData traceData) { transitLeg.intermediateStops = null; } - TravelerPosition travelerPosition = new TravelerPosition(transitLeg, traceData.position, traceData.speed); + TravelerPosition travelerPosition = new TravelerPosition.Builder() + .setExpectedLeg(transitLeg) + .setCurrentPosition(traceData.position) + .setSpeed(traceData.speed) + .build(); TripInstruction tripInstruction = TravelerLocator.getInstruction(traceData.tripStatus, travelerPosition, false); assertEquals(traceData.expectedInstruction, tripInstruction != null ? tripInstruction.build() : NO_INSTRUCTION, traceData.message); } diff --git a/src/test/java/org/opentripplanner/middleware/triptracker/NotifyBusOperatorTest.java b/src/test/java/org/opentripplanner/middleware/triptracker/NotifyBusOperatorTest.java index bf40edb05..f9e2c4239 100644 --- a/src/test/java/org/opentripplanner/middleware/triptracker/NotifyBusOperatorTest.java +++ b/src/test/java/org/opentripplanner/middleware/triptracker/NotifyBusOperatorTest.java @@ -116,7 +116,11 @@ void shouldCancelBusNotificationForStartOfTrip(boolean expected, Leg expectedLeg Leg first = firstLegBusTransit.legs.get(0); TrackedJourney journey = new TrackedJourney(); journey.busNotificationMessages.put(ROUTE_ID, "{\"msg_type\": 1}"); - TravelerPosition travelerPosition = new TravelerPosition(expectedLeg, journey, first, currentPosition); + TravelerPosition travelerPosition = new TravelerPosition.Builder() + .setExpectedLeg(expectedLeg) + .setTrackedJourney(journey) + .setFirstLegOfTrip(first) + .setCurrentPosition(currentPosition).build(); assertEquals(expected, ManageTripTracking.shouldCancelBusNotificationForStartOfTrip(travelerPosition), message); } @@ -249,24 +253,33 @@ private static Stream createWithinOperationalNotifyWindowTrace() { return Stream.of( Arguments.of( true, - new TravelerPosition(busLeg, busDepartureTime), + new TravelerPosition.Builder() + .setNextLeg(busLeg) + .setCurrentTime(busDepartureTime) + .build(), "Traveler is on schedule, notification can be sent." ), Arguments.of( false, - new TravelerPosition(busLeg, busDepartureTime.plusSeconds(60)), + new TravelerPosition.Builder() + .setNextLeg(busLeg) + .setCurrentTime(busDepartureTime.plusSeconds(60)) + .build(), "Traveler is behind schedule, notification can not be sent." ), Arguments.of( true, - new TravelerPosition(busLeg, busDepartureTime.minusSeconds(60)), + new TravelerPosition.Builder() + .setNextLeg(busLeg) + .setCurrentTime(busDepartureTime.minusSeconds(60)) + .build(), "Traveler is ahead of schedule, but within the notify window." ), Arguments.of(false, - new TravelerPosition( - busLeg, - busDepartureTime.plusSeconds((ACCEPTABLE_AHEAD_OF_SCHEDULE_IN_MINUTES + 1) * 60) - ), + new TravelerPosition.Builder() + .setNextLeg(busLeg) + .setCurrentTime(busDepartureTime.plusSeconds((ACCEPTABLE_AHEAD_OF_SCHEDULE_IN_MINUTES + 1) * 60)) + .build(), "Too far ahead of schedule to notify bus operator.") ); } @@ -284,12 +297,18 @@ private static Stream shouldSendBusNotificationAtStartOfTripTrace() { return Stream.of( Arguments.of( true, - new TravelerPosition(busLeg, getBusDepartureTime(busLeg)), + new TravelerPosition.Builder() + .setNextLeg(busLeg) + .setCurrentTime(getBusDepartureTime(busLeg)) + .build(), "Traveler at the start of a trip which starts with a bus leg, should notify." ), Arguments.of( false, - new TravelerPosition(walkLeg, getBusDepartureTime(walkLeg)), + new TravelerPosition.Builder() + .setNextLeg(walkLeg) + .setCurrentTime(getBusDepartureTime(walkLeg)) + .build(), "Traveler at the start of a trip which starts with a walk leg, should not notify." ) ); From 22ed611372dc6d6d0b83b9bc427dd7d414fac4b6 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Mon, 16 Dec 2024 17:44:52 +0000 Subject: [PATCH 11/15] refactor(Addressed PR feedback): --- .../middleware/i18n/Message.java | 2 +- .../models/LegTransitionNotification.java | 18 ++++--- .../models/TripMonitorNotification.java | 11 ++-- .../tripmonitor/jobs/CheckMonitoredTrip.java | 13 +++-- .../tripmonitor/jobs/NotificationType.java | 2 +- src/main/resources/Message.properties | 2 +- src/main/resources/Message_fr.properties | 2 +- .../latest-spark-swagger-output.yaml | 2 +- .../models/LegTransitionNotificationTest.java | 18 ++----- .../jobs/CheckMonitoredTripTest.java | 53 ++++++++++++++++++- 10 files changed, 86 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/i18n/Message.java b/src/main/java/org/opentripplanner/middleware/i18n/Message.java index 260c2fb09..fa43c1532 100644 --- a/src/main/java/org/opentripplanner/middleware/i18n/Message.java +++ b/src/main/java/org/opentripplanner/middleware/i18n/Message.java @@ -19,10 +19,10 @@ public enum Message { ACCEPT_DEPENDENT_EMAIL_SUBJECT, ACCEPT_DEPENDENT_EMAIL_MANAGE, ACCEPT_DEPENDENT_ERROR, - ARRIVED_AND_MODE_CHANGE_NOTIFICATION, ARRIVED_NOTIFICATION, DEPARTED_NOTIFICATION, LABEL_AND_CONTENT, + MODE_CHANGE_NOTIFICATION, SMS_STOP_NOTIFICATIONS, TRIP_EMAIL_SUBJECT, TRIP_EMAIL_SUBJECT_FOR_USER, diff --git a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java index 731aac664..a3ce72350 100644 --- a/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/LegTransitionNotification.java @@ -16,7 +16,7 @@ import java.util.Set; import static com.mongodb.client.model.Filters.eq; -import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION; +import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.MODE_CHANGE_NOTIFICATION; import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.ARRIVED_NOTIFICATION; import static org.opentripplanner.middleware.tripmonitor.jobs.NotificationType.DEPARTED_NOTIFICATION; import static org.opentripplanner.middleware.triptracker.TravelerLocator.hasRequiredTransitLeg; @@ -54,9 +54,9 @@ public LegTransitionNotification( private TripMonitorNotification createTripMonitorNotification(NotificationType notificationType) { String body; switch (notificationType) { - case ARRIVED_AND_MODE_CHANGE_NOTIFICATION: + case MODE_CHANGE_NOTIFICATION: body = String.format( - Message.ARRIVED_AND_MODE_CHANGE_NOTIFICATION.get(observerLocale), + Message.MODE_CHANGE_NOTIFICATION.get(observerLocale), travelerName, travelerPosition.expectedLeg.to.name ); @@ -103,9 +103,13 @@ public static Set getLegTransitionNotifyUsers(MonitoredTrip trip) { } /** - * If a traveler is on schedule and on either a walk or transit leg check for possible leg transition notification. + * If a traveler is on the route (not deviated), check for possible leg transition notification. */ - public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition travelerPosition, MonitoredTrip trip) { + public static void checkForLegTransition( + TripStatus tripStatus, + TravelerPosition travelerPosition, + MonitoredTrip trip + ) { if ( hasRequiredTripStatus(tripStatus) && (hasRequiredWalkLeg(travelerPosition) || hasRequiredTransitLeg(travelerPosition)) @@ -122,14 +126,14 @@ public static void checkForLegTransition(TripStatus tripStatus, TravelerPosition } /** - * Depending on the traveler's proximity to the start/end of a leg return the appropriate notification type. + * Depending on the traveler's proximity to the start/end of a leg, return the appropriate notification type. */ private static NotificationType getLegTransitionNotificationType(TravelerPosition travelerPosition) { if (isAtStartOfLeg(travelerPosition)) { return DEPARTED_NOTIFICATION; } else if (isApproachingEndOfLeg(travelerPosition)) { if (hasModeChanged(travelerPosition)) { - return ARRIVED_AND_MODE_CHANGE_NOTIFICATION; + return MODE_CHANGE_NOTIFICATION; } return ARRIVED_NOTIFICATION; } diff --git a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java index 5a6d8f256..d854d9246 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripMonitorNotification.java @@ -118,16 +118,21 @@ public static TripMonitorNotification createInitialReminderNotification( ); } + /** + * Checks for equality excluding the parent {@link Model} class. + */ @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; TripMonitorNotification that = (TripMonitorNotification) o; return type == that.type && Objects.equals(body, that.body); } + /** + * Creates a hash code from fields in this class only excluding fields within the parent {@link Model} class. + */ @Override public int hashCode() { - return Objects.hash(super.hashCode(), type, body); + return Objects.hash(type, body); } -} +} \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 0a70fedff..7f2c56dd3 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -53,9 +53,7 @@ public class CheckMonitoredTrip implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(CheckMonitoredTrip.class); - private final String OTP_UI_URL = ConfigUtils.getConfigPropertyAsText("OTP_UI_URL"); - - private final String OTP_UI_NAME = ConfigUtils.getConfigPropertyAsText("OTP_UI_NAME"); + public boolean IS_TEST = false; public static final int MAXIMUM_MONITORED_TRIP_ITINERARY_CHECKS = ConfigUtils.getConfigPropertyAsInt("MAXIMUM_MONITORED_TRIP_ITINERARY_CHECKS", 3); @@ -268,6 +266,8 @@ public void processLegTransition(NotificationType notificationType, TravelerPosi sendNotifications(observer); } }); + + updateMonitoredTrip(); } /** @@ -590,11 +590,8 @@ private void sendNotifications(OtpUser otpUser) { } // TODO: better handle below when one of the following fails - if (successEmail || successPush || successSms) { + if (successEmail || successPush || successSms || IS_TEST) { notificationTimestampMillis = DateTimeUtils.currentTimeMillis(); - // Prevent repeated notifications by saving successfully sent notifications. - trip.journeyState.lastNotifications.addAll(notifications); - Persistence.monitoredTrips.replace(trip.id, trip); } } @@ -934,6 +931,8 @@ private boolean updateMonitoredTrip() { // Update notification time if notification successfully sent. if (notificationTimestampMillis != -1) { journeyState.lastNotificationTimeMillis = notificationTimestampMillis; + // Prevent repeated notifications by saving successfully sent notifications. + journeyState.lastNotifications.addAll(notifications); } trip.journeyState = journeyState; Persistence.monitoredTrips.replace(trip.id, trip); diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java index 4a40911cc..62d0fa422 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/NotificationType.java @@ -12,7 +12,7 @@ public enum NotificationType { ALERT_FOUND, ITINERARY_NOT_FOUND, INITIAL_REMINDER, - ARRIVED_AND_MODE_CHANGE_NOTIFICATION, + MODE_CHANGE_NOTIFICATION, DEPARTED_NOTIFICATION, ARRIVED_NOTIFICATION } \ No newline at end of file diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index df70ee760..c44410aec 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -4,9 +4,9 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings ACCEPT_DEPENDENT_ERROR = Unable to accept trusted companion. -ARRIVED_AND_MODE_CHANGE_NOTIFICATION = %s has arrived at transit stop %s. ARRIVED_NOTIFICATION = %s has arrived at %s. LABEL_AND_CONTENT = %s: %s +MODE_CHANGE_NOTIFICATION = %s has arrived at transit stop %s. DEPARTED_NOTIFICATION = %s has departed %s. SMS_STOP_NOTIFICATIONS = To stop receiving notifications, reply STOP. TRIP_EMAIL_SUBJECT = %s Notification diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index ac826b816..312b3d596 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,9 +4,9 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. -ARRIVED_AND_MODE_CHANGE_NOTIFICATION = %s TODO %s. ARRIVED_NOTIFICATION = TODO %s. LABEL_AND_CONTENT = %s\u00A0: %s +MODE_CHANGE_NOTIFICATION = %s TODO %s. DEPARTED_NOTIFICATION = TODO %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 28389f6a1..1f9ad76cd 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -2376,7 +2376,7 @@ definitions: - "ALERT_FOUND" - "ITINERARY_NOT_FOUND" - "INITIAL_REMINDER" - - "ARRIVED_AND_MODE_CHANGE_NOTIFICATION" + - "MODE_CHANGE_NOTIFICATION" - "DEPARTED_NOTIFICATION" - "ARRIVED_NOTIFICATION" body: diff --git a/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java b/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java index eb2191f43..8d96ee713 100644 --- a/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java +++ b/src/test/java/org/opentripplanner/middleware/models/LegTransitionNotificationTest.java @@ -29,7 +29,7 @@ class LegTransitionNotificationTest extends OtpMiddlewareTestEnvironment { private static OtpUser observer; @BeforeAll - public static void setup() { + public static void setup() throws Exception { primary = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-primary-user")); companion = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-companion-user")); observer = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-observer-user")); @@ -46,14 +46,13 @@ void testLegTransitionNotifications( NotificationType notificationType, String travelerName, TravelerPosition travelerPosition, - Locale locale, String message ) { TripMonitorNotification notification = new LegTransitionNotification( travelerName, notificationType, travelerPosition, - locale + Locale.US ).tripMonitorNotification; assertNotNull(notification); assertEquals(message, notification.body); @@ -61,7 +60,6 @@ void testLegTransitionNotifications( private static Stream createLegTransitionNotificationTestCases() throws Exception { String travelerName = "Obi-Wan"; - Locale locale = Locale.US; Itinerary itinerary = createDefaultItinerary(); Leg expectedLeg = itinerary.legs.get(1); Coordinates expectedLegDestinationCoords = new Coordinates(expectedLeg.to); @@ -69,14 +67,13 @@ private static Stream createLegTransitionNotificationTestCases() thro Coordinates nextLegDepartureCoords = new Coordinates(nextLeg.from); return Stream.of( Arguments.of( - NotificationType.ARRIVED_AND_MODE_CHANGE_NOTIFICATION, + NotificationType.MODE_CHANGE_NOTIFICATION, travelerName, new TravelerPosition.Builder() .setExpectedLeg(expectedLeg) .setNextLeg(nextLeg) .setCurrentPosition(expectedLegDestinationCoords) .build(), - locale, "Obi-Wan has arrived at transit stop Pioneer Square South MAX Station." ), Arguments.of( @@ -87,7 +84,6 @@ private static Stream createLegTransitionNotificationTestCases() thro .setNextLeg(nextLeg) .setCurrentPosition(nextLegDepartureCoords) .build(), - locale, "Obi-Wan has departed Providence Park MAX Station." ), Arguments.of( @@ -98,7 +94,6 @@ private static Stream createLegTransitionNotificationTestCases() thro .setNextLeg(nextLeg) .setCurrentPosition(expectedLegDestinationCoords) .build(), - locale, "Obi-Wan has arrived at Pioneer Square South MAX Station." ) ); @@ -110,16 +105,11 @@ void testLegTransitionNotifyUsers( String tripOwnerUserId, Set expectedUsers ) { - - RelatedUser relatedUser = new RelatedUser(); - relatedUser.email = observer.email; - relatedUser.status = RelatedUser.RelatedUserStatus.CONFIRMED; - MonitoredTrip trip = new MonitoredTrip(); trip.userId = tripOwnerUserId; trip.primary = new MobilityProfileLite(primary); trip.companion = new RelatedUser(companion.email, RelatedUser.RelatedUserStatus.CONFIRMED); - trip.observers.add(relatedUser); + trip.observers.add(new RelatedUser(observer.email, RelatedUser.RelatedUserStatus.CONFIRMED)); Set users = LegTransitionNotification.getLegTransitionNotifyUsers(trip); assertNotNull(users); diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 80d90ff00..99b1e02ad 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -10,7 +10,11 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.middleware.models.ItineraryExistence; +import org.opentripplanner.middleware.models.MobilityProfileLite; +import org.opentripplanner.middleware.models.RelatedUser; import org.opentripplanner.middleware.models.TrackedJourney; +import org.opentripplanner.middleware.otp.response.Leg; +import org.opentripplanner.middleware.testutils.ApiTestUtils; import org.opentripplanner.middleware.testutils.OtpMiddlewareTestEnvironment; import org.opentripplanner.middleware.testutils.OtpTestUtils; import org.opentripplanner.middleware.testutils.PersistenceTestUtils; @@ -23,6 +27,8 @@ import org.opentripplanner.middleware.otp.response.OtpResponse; import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.tripmonitor.TripStatus; +import org.opentripplanner.middleware.triptracker.TravelerPosition; +import org.opentripplanner.middleware.utils.Coordinates; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +50,7 @@ import static org.hamcrest.text.MatchesPattern.matchesPattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -807,4 +814,48 @@ private static Stream createCanUnsnoozeTripCases() { Arguments.of(noonMonday8June2020, tuesday, true) ); } -} + + @Test + void testDuplicateNotifications() throws Exception { + OtpUser observer = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-observer-user")); + + MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( + user.id, + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), + true, + OtpTestUtils.createDefaultJourneyState() + ); + monitoredTrip.primary = new MobilityProfileLite(user); + monitoredTrip.observers.add(new RelatedUser(observer.email, RelatedUser.RelatedUserStatus.CONFIRMED)); + Persistence.monitoredTrips.replace(monitoredTrip.id, monitoredTrip); + + Leg expectedLeg = monitoredTrip.itinerary.legs.get(1); + Coordinates expectedLegDestinationCoords = new Coordinates(expectedLeg.to); + Leg nextLeg = monitoredTrip.itinerary.legs.get(2); + + TravelerPosition travelerPosition = new TravelerPosition.Builder() + .setExpectedLeg(expectedLeg) + .setNextLeg(nextLeg) + .setCurrentPosition(expectedLegDestinationCoords) + .build(); + + triggerCheckMonitoredTrip(monitoredTrip, travelerPosition); + MonitoredTrip updated = Persistence.monitoredTrips.getById(monitoredTrip.id); + assertNotEquals(-1, updated.journeyState.lastNotificationTimeMillis); + long previousLastNotificationTimeMillis = updated.journeyState.lastNotificationTimeMillis; + + triggerCheckMonitoredTrip(monitoredTrip, travelerPosition); + updated = Persistence.monitoredTrips.getById(monitoredTrip.id); + assertEquals(previousLastNotificationTimeMillis, updated.journeyState.lastNotificationTimeMillis); + + Persistence.monitoredTrips.removeById(monitoredTrip.id); + Persistence.otpUsers.removeById(observer.id); + } + + private void triggerCheckMonitoredTrip(MonitoredTrip monitoredTrip, TravelerPosition travelerPosition) throws CloneNotSupportedException { + CheckMonitoredTrip checkMonitoredTrip = new CheckMonitoredTrip(monitoredTrip, this::mockOtpPlanResponse); + checkMonitoredTrip.IS_TEST = true; + checkMonitoredTrip.targetZonedDateTime = monitoredTrip.tripZonedDateTime(DateTimeUtils.nowAsLocalDate()); + checkMonitoredTrip.processLegTransition(NotificationType.MODE_CHANGE_NOTIFICATION, travelerPosition); + } +} \ No newline at end of file From 51598047c681e8e15652e096ab6e86b14c26aa27 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Mon, 16 Dec 2024 18:29:42 +0000 Subject: [PATCH 12/15] refactor(Update to guard against missing target zoned date time when updating monitored trip): --- .../middleware/tripmonitor/jobs/CheckMonitoredTrip.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 7f2c56dd3..80a3fbc59 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -926,7 +926,9 @@ private boolean updateMonitoredTrip() { return false; } journeyState.matchingItinerary = matchingItinerary; - journeyState.targetDate = targetZonedDateTime.format(DateTimeUtils.DEFAULT_DATE_FORMATTER); + if (targetZonedDateTime != null) { + journeyState.targetDate = targetZonedDateTime.format(DateTimeUtils.DEFAULT_DATE_FORMATTER); + } journeyState.lastCheckedEpochMillis = DateTimeUtils.currentTimeMillis(); // Update notification time if notification successfully sent. if (notificationTimestampMillis != -1) { From c05e011e7ef7a6a8ea06df0fdc23fc923317b7f3 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Mon, 16 Dec 2024 19:07:47 +0000 Subject: [PATCH 13/15] refactor(CheckMonitoredTrip): Update to preserve matching itinerary --- .../middleware/tripmonitor/jobs/CheckMonitoredTrip.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 80a3fbc59..9bff9e545 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -248,9 +248,12 @@ private boolean isTrackingOngoing() { } /** - * Process leg transition notification. + * Process leg transition notifications by getting all qualifying users and enqueuing relevant notifications. The + * matching itinerary is required when updating the monitored trip. There is no requirement to match the itinerary + * to that returned from OTP, so the existing trip itinerary is used and therefore preserved. */ - public void processLegTransition(NotificationType notificationType, TravelerPosition travelerPosition) { + public void processLegTransition(NotificationType notificationType, TravelerPosition travelerPosition) throws CloneNotSupportedException { + matchingItinerary = trip.itinerary.clone(); OtpUser tripOwner = getOtpUser(); Set notifyUsers = getLegTransitionNotifyUsers(trip); notifyUsers.forEach(observer -> { From 07ad8d80704e654615cbc3974686547703729180 Mon Sep 17 00:00:00 2001 From: Robin Beer Date: Wed, 18 Dec 2024 08:14:17 +0000 Subject: [PATCH 14/15] improvement(hidden getDisplayName from Mongo and JSON serialisation): --- .../java/org/opentripplanner/middleware/models/OtpUser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java index b93ad31ad..78d0345fc 100644 --- a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java +++ b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSetter; import org.apache.logging.log4j.util.Strings; +import org.bson.codecs.pojo.annotations.BsonIgnore; import org.opentripplanner.middleware.auth.Auth0Users; import org.opentripplanner.middleware.auth.RequestingUser; import org.opentripplanner.middleware.persistence.Persistence; @@ -215,6 +216,8 @@ public Optional findLastTripSurveyNotificationSent() { /** * Use name if available, if not fallback on email (which is a required field). */ + @JsonIgnore + @BsonIgnore public String getDisplayedName() { return Strings.isBlank(name) ? email.replace("@", " at ") : name; } From 36b26afa7d532112d72346b040408576db40a705 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:11:11 -0500 Subject: [PATCH 15/15] chore(i18n): Add French text for new strings. --- src/main/resources/Message_fr.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index 312b3d596..ae87d00d3 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -4,10 +4,10 @@ ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accepter la demande ACCEPT_DEPENDENT_EMAIL_SUBJECT = Demande d'accompagnateur ACCEPT_DEPENDENT_EMAIL_MANAGE = Gérez vos préférences ACCEPT_DEPENDENT_ERROR = La demande d'accompagnateur n'a pas été reçue. -ARRIVED_NOTIFICATION = TODO %s. +ARRIVED_NOTIFICATION = %s est arrivé·e à %s. LABEL_AND_CONTENT = %s\u00A0: %s -MODE_CHANGE_NOTIFICATION = %s TODO %s. -DEPARTED_NOTIFICATION = TODO %s. +MODE_CHANGE_NOTIFICATION = %s est arrivé·e à l'arrêt %s. +DEPARTED_NOTIFICATION = %s vient de partir de %s. SMS_STOP_NOTIFICATIONS = Pour arrêter ces notifications, envoyez STOP. TRIP_EMAIL_SUBJECT = Notification pour %s TRIP_EMAIL_SUBJECT_FOR_USER = Notification pour le trajet de %s @@ -35,7 +35,7 @@ TRIP_DELAY_MINUTES = %d minutes TRIP_INVITE_COMPANION = %s vous a ajouté comme accompagnateur·trice pour un trajet. TRIP_INVITE_PRIMARY_TRAVELER = %s vous a fait le voyageur principal sur ce trajet. TRIP_INVITE_OBSERVER = %s vous a ajouté comme observateur·trice pour un trajet. -TRIP_NAME_UNDEFINED = TODO +TRIP_NAME_UNDEFINED = Trajet sans nom TRIP_NOT_FOUND_NOTIFICATION = Votre trajet est introuvable aujourd'hui. Veuillez vérifier les conditions en temps-réel et recherchez un nouveau trajet. TRIP_NO_LONGER_POSSIBLE_NOTIFICATION = Votre trajet n'est plus possible dans aucun jour de suivi de la semaine. Veuillez rechercher et enregistrer un nouveau trajet. TRIP_REMINDER_NOTIFICATION = Rappel pour %s à %s.