Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refetch transit leg with node query of GTFS GraphQL API #6045

Open
wants to merge 15 commits into
base: dev-2.x
Choose a base branch
from

Conversation

vesameskanen
Copy link
Contributor

@vesameskanen vesameskanen commented Sep 5, 2024

Summary

GTFS leg object now includes a graphql node id . Transit leg can be refetched using the Node query:

query legQuery { node(id: "foo") { ... on Leg { mode start { estimated { time } } } } }

Such query can be used to update an itinerary and to detect if the itinerary is no longer possible because of delays or canceled trips.

Non-transit legs cannot be refetched - the response is always null.

This PR also adds missing implementation for querying leg's realtimeState via GTFS graphql API.

Documentation

In the graphql schema.

@vesameskanen vesameskanen requested a review from a team as a code owner September 5, 2024 10:42
@leonardehrenfried leonardehrenfried changed the title Add support for refetching transit leg using the node query of GTFS graphql API Refetch transit leg with node query of GTFS Graphql API Sep 5, 2024
@leonardehrenfried leonardehrenfried changed the title Refetch transit leg with node query of GTFS Graphql API Refetch transit leg with node query of GTFS GraphQL API Sep 5, 2024
@leonardehrenfried
Copy link
Member

@binh-dam-ibigroup This might be interesting for you.

@vesameskanen I almost cannot believe that it is so easy!

Copy link

codecov bot commented Sep 5, 2024

Codecov Report

Attention: Patch coverage is 75.00000% with 6 lines in your changes missing coverage. Please review.

Project coverage is 69.79%. Comparing base (d2f4b2d) to head (20f855a).
Report is 91 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...pplanner/apis/gtfs/datafetchers/QueryTypeImpl.java 0.00% 4 Missing ⚠️
...anner/apis/gtfs/datafetchers/NodeTypeResolver.java 0.00% 2 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #6045      +/-   ##
=============================================
+ Coverage      69.74%   69.79%   +0.04%     
- Complexity     17316    17364      +48     
=============================================
  Files           1960     1962       +2     
  Lines          74267    74380     +113     
  Branches        7603     7627      +24     
=============================================
+ Hits           51796    51910     +114     
+ Misses         19829    19827       -2     
- Partials        2642     2643       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -445,6 +447,15 @@ public DataFetcher<Object> node() {
// TODO: Add geometry
return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null);
}
case "Leg":
if (id.equals("null") || id.isBlank()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to treat the string "null" specially? In what case can this happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that Leg base class retuns null in getLegReference() method, and that value gets encoded into relay id. Decoding the relay id produces a string value 'null'.

I added the check to prevent unhandled exception when somebody tries to refetch non-transit legs. Maybe there is a more elegant way to handle such situations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the response for non-transit legs is
"data": { "node": null }

Copy link
Member

@leonardehrenfried leonardehrenfried Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about checking if the reference returns null in LegImpl and never return the string "null" but the null value?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a better solution. We should make the id field nullable in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried making id of Node type nullable and everything seems to work fine.

Another option would be to encode null as an empty string in LegReferenceSerializer, in which case id.isBlank() test would work. This might also fix problems in transmodel api.

@@ -611,7 +611,7 @@ type Itinerary {
walkTime: Long
}

type Leg {
type Leg implements Node {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is backwards-compatible but lets discuss in the dev meeting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm almost 100% sure it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do some research on this, but could not find anything. So, lets change it and see what happens - maybe wait with merging this PR after the next release?

@optionsome
Copy link
Member

@vesameskanen I almost cannot believe that it is so easy!

This is easy because this was already done in the transmodel API. However, the transmodel API has own legs query for this because it lacks the node query/interface.

@optionsome
Copy link
Member

@vesameskanen will still add some integration tests for this.

@vesameskanen
Copy link
Contributor Author

vesameskanen commented Sep 6, 2024

GTFS integration tests currently cannot test leg refetching, because mocked itineraries lack essential data used in encoding the leg information. Integration test now tests only that legs contain the id.

public DataFetcher<Relay.ResolvedGlobalId> id() {
return environment -> {
var ref = getSource(environment).getLegReference();
var id = (ref == null) ? "" : LegReferenceSerializer.encode(ref);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yesterday in the developer meeting we thought it might make sense to use some special string like "NotAvailable" instead of "null" or empty string.

@@ -669,6 +669,8 @@ type Leg {
For non-transit legs, null.
"""
headsign: String
"An identifier for the leg, which can be used to re-fetch transit leg information."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also mention that an invalid reference id is returned for other legs. Also, mention that the id might not be usable forever since the data and ids within data can change over time.

@@ -26,7 +28,7 @@ private LegReferenceSerializer() {}
@Nullable
public static String encode(LegReference legReference) {
if (legReference == null) {
return null;
return notAvailable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since u change this, do you also need to update the transmodel API handling of missing value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transmodel API works as expected. null is encoded as 'NotAvailable' and 'NotAvailable' is decoded as null.

The only change I can think of is maybe removing isBlank() check from TransmodelGraphQLSchema.java.

@vesameskanen vesameskanen marked this pull request as draft September 11, 2024 11:15
@vesameskanen
Copy link
Contributor Author

vesameskanen commented Sep 11, 2024

I tested new legs with relay and got lots of problems. Relay does not accept nodes with same node id but different content. Either all leg types must be serialized to unique id ('NotAvailable' is not OK), or we have to revert node interface for legs and add a dedicated leg query. Any opinions?

@leonardehrenfried
Copy link
Member

leonardehrenfried commented Sep 11, 2024

Maybe I don't get it but don't all legs have the same type in GraphQL?

@optionsome
Copy link
Member

Maybe I don't get it but don't all legs have the same type in GraphQL?

@vesameskanen explained to me that the issue is that the non-transit legs all have the same id. We discussed this today and thought maybe the solution would be to generate unique ids based on actual leg data (such as departure/arrival time) even if we don't support refetching based on those ids.

I tried adding tests for refetching the legs through the node query in the graphql integration tests on Tuesday but creating/maintaining such test would be slightly difficult.

@vesameskanen vesameskanen marked this pull request as ready for review September 13, 2024 05:56
Comment on lines +338 to +344
String.join(
"",
leg.start().time().toString(),
leg.end().time().toString(),
leg.getFrom().toStringShort(),
leg.getTo().toStringShort()
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the transit leg references, this is now not "double base64 encoded" (for transit the original reference is already base64 encoded and then encoded again with the leg: prefix). I wonder if we should encode this as well or not.

I like that this is only on the GTFS API so it doesn't potentially break transmodel clients who might rely on the fact that non-null ids are refetchable-

@@ -244,6 +245,10 @@ default boolean getRealTime() {
return false;
}

default RealTimeState getRealTimeState() {
return RealTimeState.SCHEDULED;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's better to return SCHEDULED or null? SCHEDULED for street legs feels a bit wrong, on the other hand that's how it worked in otp1 seemingly.

@@ -427,6 +433,7 @@ public String toString() {
.addEnum("alightRule", getAlightRule())
.addObj("transferFromPrevLeg", transferFromPrevLeg)
.addObj("transferToNextLeg", transferToNextLeg)
.addEnum("realtimeState", getRealTimeState())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should add this to the toString as this is not Leg's own field.

@t2gran t2gran added this to the 2.7 (next release) milestone Sep 18, 2024
@optionsome
Copy link
Member

We discussed this pr again in the dev meeting today and came to the conclusion that it's better to create own leg(id) query for the refetching purpose because the node query is more fit for limited number of entities stored in a database, for example, and for legs, we don't really have the ability to refetch all types of legs currently. The field on leg can still be called id but it should be nullable and we should return null for non-transit legs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants