Skip to content

Commit

Permalink
Merge pull request #243 from ibi-group/graphql-qbd2-itincheck
Browse files Browse the repository at this point in the history
Migrate Itinerary Checks to GraphQL
  • Loading branch information
binh-dam-ibigroup authored Oct 14, 2024
2 parents e006ca8 + 8b64f92 commit 4e1b6b5
Show file tree
Hide file tree
Showing 41 changed files with 1,305 additions and 3,624 deletions.
1 change: 1 addition & 0 deletions .github/workflows/maven-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
NOTIFICATION_FROM_PHONE: ${{ secrets.NOTIFICATION_FROM_PHONE }}
OTP_ADMIN_DASHBOARD_URL: ${{ secrets.OTP_ADMIN_DASHBOARD_URL }}
OTP_API_ROOT: ${{ secrets.OTP_API_ROOT }}
OTP2_API_ROOT: ${{ secrets.OTP_API_ROOT }}
OTP_PLAN_ENDPOINT: ${{ secrets.OTP_PLAN_ENDPOINT }}
OTP_TIMEZONE: ${{ secrets.OTP_TIMEZONE }}
OTP_UI_URL: ${{ secrets.OTP_UI_URL }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private static void initializeHttpEndpoints() throws IOException, InterruptedExc
new LogController(API_PREFIX),
new ErrorEventsController(API_PREFIX),
new CDPFilesController(API_PREFIX),
new OtpRequestProcessor("/otp", OtpVersion.OTP1),
new OtpRequestProcessor("/otp", OtpVersion.OTP2),
new OtpRequestProcessor("/otp2", OtpVersion.OTP2)
// TODO Add other models.
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import spark.Request;
import spark.Response;

import java.net.URISyntaxException;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -67,23 +66,14 @@ MonitoredTrip preCreateHook(MonitoredTrip monitoredTrip, Request req) {
// check itinerary existence for recurring trips only for now.
// (Existence should ultimately be checked on all trips.)
if (!monitoredTrip.isOneTime()) {
try {
// Check itinerary existence for all days and replace the provided trip's itinerary with a verified,
// non-realtime version of it.
boolean success = monitoredTrip.checkItineraryExistence(true);
if (!success) {
logMessageAndHalt(
req,
HttpStatus.BAD_REQUEST_400,
monitoredTrip.itineraryExistence.message
);
}
} catch (URISyntaxException e) { // triggered by OtpQueryUtils#getQueryParams.
// Check itinerary existence for all days and replace the provided trip's itinerary with a verified,
// non-realtime version of it.
boolean success = monitoredTrip.checkItineraryExistence(true);
if (!success) {
logMessageAndHalt(
req,
HttpStatus.INTERNAL_SERVER_ERROR_500,
"Error parsing the trip query parameters.",
e
HttpStatus.BAD_REQUEST_400,
monitoredTrip.itineraryExistence.message
);
}
}
Expand Down Expand Up @@ -132,7 +122,7 @@ private void preCreateOrUpdateChecks(MonitoredTrip monitoredTrip, Request req) {
*/
private void processTripQueryParams(MonitoredTrip monitoredTrip, Request req) {
try {
monitoredTrip.initializeFromItineraryAndQueryParams();
monitoredTrip.initializeFromItineraryAndQueryParams(req);
} catch (Exception e) {
logMessageAndHalt(
req,
Expand Down Expand Up @@ -197,17 +187,8 @@ private static ItineraryExistence checkItinerary(Request request, Response respo
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Error parsing JSON for MonitoredTrip", e);
return null;
}
try {
trip.initializeFromItineraryAndQueryParams();
trip.checkItineraryExistence(false);
} catch (URISyntaxException e) { // triggered by OtpQueryUtils#getQueryParams.
logMessageAndHalt(
request,
HttpStatus.INTERNAL_SERVER_ERROR_500,
"Error parsing the trip query parameters.",
e
);
}
trip.initializeFromItineraryAndQueryParams(trip.otp2QueryParams);
trip.checkItineraryExistence(false);
return trip.itineraryExistence;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.opentripplanner.middleware.OtpMiddlewareMain;
import org.opentripplanner.middleware.otp.OtpDispatcher;
import org.opentripplanner.middleware.otp.OtpRequest;
import org.opentripplanner.middleware.otp.response.Itinerary;
Expand Down Expand Up @@ -65,7 +66,9 @@ public class ItineraryExistence extends Model {
*/
public Date timestamp = new Date();

private transient Function<OtpRequest, OtpResponse> otpResponseProvider = ItineraryExistence::getOtpResponse;
private transient Function<OtpRequest, OtpResponse> otpResponseProvider = getOtpResponseProvider();

public static Function<OtpRequest, OtpResponse> otpResponseProviderOverride = null;

// Required for persistence.
public ItineraryExistence() {}
Expand All @@ -82,6 +85,12 @@ public ItineraryExistence(
if (otpResponseProvider != null) this.otpResponseProvider = otpResponseProvider;
}

private Function<OtpRequest, OtpResponse> getOtpResponseProvider() {
return OtpMiddlewareMain.inTestEnvironment && otpResponseProviderOverride != null
? otpResponseProviderOverride
: ItineraryExistence::getOtpResponse;
}

/**
* Helper function to extract the existence check for a particular day of the week.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.mongodb.client.FindIterable;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.conversions.Bson;
import org.opentripplanner.middleware.auth.Permission;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.otp.OtpDispatcherResponse;
import org.opentripplanner.middleware.otp.OtpGraphQLVariables;
import org.opentripplanner.middleware.otp.OtpRequest;
import org.opentripplanner.middleware.otp.response.Itinerary;
import org.opentripplanner.middleware.otp.response.OtpResponse;
Expand All @@ -21,24 +19,14 @@
import org.opentripplanner.middleware.tripmonitor.JourneyState;
import org.opentripplanner.middleware.utils.DateTimeUtils;
import org.opentripplanner.middleware.utils.ItineraryUtils;
import spark.Request;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.mongodb.client.model.Filters.eq;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.opentripplanner.middleware.utils.ItineraryUtils.DATE_PARAM;
import static org.opentripplanner.middleware.utils.ItineraryUtils.MODE_PARAM;
import static org.opentripplanner.middleware.utils.ItineraryUtils.TIME_PARAM;

/**
* A monitored trip represents a trip a user would like to receive notification on if affected by a delay and/or route
Expand Down Expand Up @@ -137,8 +125,14 @@ public class MonitoredTrip extends Model {
/**
* Query params. Query parameters influencing trip.
*/
// TODO: Remove
public String queryParams;

/**
* GraphQL query parameters for OTP.
*/
public OtpGraphQLVariables otp2QueryParams = new OtpGraphQLVariables();

/**
* The trip's itinerary
*/
Expand Down Expand Up @@ -185,14 +179,12 @@ public MonitoredTrip() {
/**
* Used only during testing
*/
public MonitoredTrip(OtpDispatcherResponse otpDispatcherResponse) throws URISyntaxException,
JsonProcessingException {
queryParams = otpDispatcherResponse.requestUri.getQuery();
public MonitoredTrip(OtpGraphQLVariables otp2QueryParams, OtpDispatcherResponse otpDispatcherResponse) throws JsonProcessingException {
TripPlan plan = otpDispatcherResponse.getResponse().plan;
itinerary = plan.itineraries.get(0);

// extract trip time from parsed params and itinerary
initializeFromItineraryAndQueryParams();
initializeFromItineraryAndQueryParams(otp2QueryParams);
}

/**
Expand All @@ -202,7 +194,7 @@ public MonitoredTrip(OtpDispatcherResponse otpDispatcherResponse) throws URISynt
public boolean checkItineraryExistence(
boolean replaceItinerary,
Function<OtpRequest, OtpResponse> otpResponseProvider
) throws URISyntaxException {
) {
// Get queries to execute by date.
List<OtpRequest> queriesByDate = getItineraryExistenceQueries();
itineraryExistence = new ItineraryExistence(queriesByDate, itinerary, arriveBy, otpResponseProvider);
Expand All @@ -217,17 +209,16 @@ public boolean checkItineraryExistence(
/**
* Shorthand for above method using the default otpResponseProvider.
*/
public boolean checkItineraryExistence(boolean replaceItinerary) throws URISyntaxException {
public boolean checkItineraryExistence(boolean replaceItinerary) {
return checkItineraryExistence(replaceItinerary, null);
}

/**
* Replace the itinerary provided with the monitored trip
* with a non-real-time, verified itinerary from the responses provided.
*/
private boolean updateTripWithVerifiedItinerary() throws URISyntaxException {
Map<String, String> params = parseQueryParams();
String queryDate = params.get(DATE_PARAM);
private boolean updateTripWithVerifiedItinerary() {
String queryDate = otp2QueryParams.date;
DayOfWeek dayOfWeek = DateTimeUtils.getDateFromQueryDateString(queryDate).getDayOfWeek();

// Find the response corresponding to the day of the query.
Expand All @@ -239,7 +230,7 @@ private boolean updateTripWithVerifiedItinerary() throws URISyntaxException {
if (verifiedItinerary != null) {
// Set itinerary for monitored trip if verified itinerary is available.
this.itinerary = verifiedItinerary;
this.initializeFromItineraryAndQueryParams();
this.initializeFromItineraryAndQueryParams(otp2QueryParams);
return true;
} else {
// Otherwise, set itinerary existence error/message.
Expand All @@ -254,10 +245,9 @@ private boolean updateTripWithVerifiedItinerary() throws URISyntaxException {
*/
@JsonIgnore
@BsonIgnore
public List<OtpRequest> getItineraryExistenceQueries()
throws URISyntaxException {
public List<OtpRequest> getItineraryExistenceQueries() {
return ItineraryUtils.getOtpRequestsForDates(
ItineraryUtils.excludeRealtime(parseQueryParams()),
otp2QueryParams,
ItineraryUtils.getDatesToCheckItineraryExistence(this)
);
}
Expand All @@ -266,24 +256,26 @@ public List<OtpRequest> getItineraryExistenceQueries()
* Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of
* the itinerary is removed.
*/
public void initializeFromItineraryAndQueryParams() throws IllegalArgumentException, URISyntaxException {
public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, JsonProcessingException {
initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromMonitoredTripRequest(req));
}

/**
* Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of
* the itinerary is removed.
*/
public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVariables) throws IllegalArgumentException {
int lastLegIndex = itinerary.legs.size() - 1;
from = itinerary.legs.get(0).from;
to = itinerary.legs.get(lastLegIndex).to;
Map<String, String> parsedQueryParams = parseQueryParams();

// Update modes in query params with set derived from itinerary. This ensures that, when sending requests to OTP
// for trip monitoring, we are querying the modes retained by the user when they saved the itinerary.
Set<String> modes = ItineraryUtils.deriveModesFromItinerary(itinerary);
parsedQueryParams.put(MODE_PARAM, String.join(",", modes));
this.queryParams = ItineraryUtils.toQueryString(parsedQueryParams);
this.arriveBy = parsedQueryParams.getOrDefault("arriveBy", "false").equals("true");
this.otp2QueryParams = graphQLVariables;
this.arriveBy = graphQLVariables.arriveBy;

// Ensure the itinerary we store does not contain any realtime info.
clearRealtimeInfo();

// set the trip time by parsing the query params
tripTime = parsedQueryParams.get(TIME_PARAM);
tripTime = graphQLVariables.time;
if (tripTime == null) {
throw new IllegalArgumentException("A monitored trip must have a time set in the query params!");
}
Expand Down Expand Up @@ -371,10 +363,6 @@ public boolean canBeManagedBy(RequestingUser requestingUser) {
return super.canBeManagedBy(requestingUser);
}

private Bson tripIdFilter() {
return eq("monitoredTripId", this.id);
}

/**
* Clear real-time info from itinerary to store.
* FIXME: Do we need to clear more than the alerts?
Expand All @@ -396,22 +384,6 @@ public boolean delete() {
return Persistence.monitoredTrips.removeById(this.id);
}

/**
* Parse the query params for this trip into a map of the variables.
*/
public Map<String, String> parseQueryParams() throws URISyntaxException {
// If for some reason a leading ? is present in queryParams, skip it.
// (We already include a ? when calling URLEncodedUtils.parse below.)
String queryParamsWithoutQuestion = queryParams.startsWith("?")
? queryParams.substring(1)
: queryParams;

return URLEncodedUtils.parse(
new URI(String.format("http://example.com/plan?%s", queryParamsWithoutQuestion)),
UTF_8
).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue));
}

/**
* Returns the target hour of the day that the trip is either departing at or arriving by
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import org.eclipse.jetty.http.HttpMethod;
import org.opentripplanner.middleware.bugsnag.BugsnagReporter;
import org.opentripplanner.middleware.otp.response.OtpResponse;
import org.opentripplanner.middleware.utils.GraphQLUtils;
import org.opentripplanner.middleware.utils.HttpResponseValues;
import org.opentripplanner.middleware.utils.HttpUtils;
import org.opentripplanner.middleware.utils.ItineraryUtils;
import org.opentripplanner.middleware.utils.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -78,7 +79,23 @@ public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, Strin
* Provides a response from the OTP server target service based on the input {@link OtpRequest}.
*/
public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, OtpRequest otpRequest) {
return sendOtpPlanRequest(version, ItineraryUtils.toQueryString(otpRequest.requestParameters));
return sendOtpPlanRequest(version, otpRequest.requestParameters);
}

/**
* Provides a response from the OTP server target service based on the input {@link OtpRequest}.
*/
public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, OtpGraphQLVariables params) {
OtpGraphQLQuery query = new OtpGraphQLQuery();
query.query = GraphQLUtils.getPlanQueryTemplate();
query.variables = params;
return sendOtpPostRequest(
version,
"",
OTP_GRAPHQL_ENDPOINT,
HttpUtils.HEADERS_JSON,
JsonUtils.toJson(query).replace("\\\\n", "\\n").replace("\\\\\"", "\"")
);
}

/**
Expand Down Expand Up @@ -119,7 +136,7 @@ private static OtpDispatcherResponse sendOtpRequest(
Map<String, String> headers,
String bodyContent
) {
LOG.info("Sending request to OTP: {}", uri.toString());
LOG.info("Sending request to OTP: {}", uri);
HttpResponseValues otpResponse =
HttpUtils.httpRequestRawResponse(
uri,
Expand All @@ -131,11 +148,15 @@ private static OtpDispatcherResponse sendOtpRequest(
}

public static OtpResponse sendOtpRequestWithErrorHandling(String sentParams) {
return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP1, sentParams));
return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, sentParams));
}

public static OtpResponse sendOtpRequestWithErrorHandling(OtpRequest otpRequest) {
return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP1, otpRequest));
return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, otpRequest));
}

public static OtpResponse sendOtpRequestWithErrorHandling(OtpGraphQLVariables params) {
return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, params));
}

private static OtpResponse handleOtpDispatcherResponse(Supplier<OtpDispatcherResponse> otpDispatcherResponseSupplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public OtpDispatcherResponse(String otpResponse, URI requestUri) {
*/
public OtpResponse getResponse() throws JsonProcessingException {
try {
return JsonUtils.getPOJOFromJSON(responseBody, OtpResponse.class);
return JsonUtils.getPOJOFromJSON(responseBody, OtpResponseGraphQLWrapper.class).data;
} catch (JsonProcessingException e) {
BugsnagReporter.reportErrorToBugsnag("Failed to parse OTP response!", responseBody, e);
throw e;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.opentripplanner.middleware.otp;

/** Wraps an OTP GraphQL query data */
public class OtpGraphQLQuery {
public String query;

public OtpGraphQLVariables variables;
}
Loading

0 comments on commit 4e1b6b5

Please sign in to comment.