-
Notifications
You must be signed in to change notification settings - Fork 12
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
feat: Show "Depart at" buttons and show the correct itinerary on click #2238
Conversation
And change its input from `itinerary` to `itineraries` so that it can be responsible for choosing which itinerary to display
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would probably be better as a function component since it has so little state. I'm just nervous about having state in multiple places...sometimes in the live view, sometimes in live components. With things like the map which can potentially have a lot of state, needs a lot of interactivity, and is very self-contained, it makes more sense to make it a live component. It would probably help to have some kind of rule-of-thumb about the number of attributes and callbacks that make a live component make sense. I don't think this component meets a minimal criteria.
type="button" | ||
class={[ | ||
"border border-brand-primary rounded px-2.5 py-1.5 mr-2 text-brand-primary text-lg", | ||
"hover:bg-brand-primary-lightest #{@background_class}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just do:
"hover:bg-brand-primary-lightest",
@background_class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, you might even want to do:
"hover:bg-brand-primary-lightest bg-transparent",
@active && "bg-brand-primary-lightest"
That allows you to remove two lines of code.
update_trip_details(base_itinerary, trip_id: "trip_id_1", start_time: ~T[09:26:00]), | ||
update_trip_details(base_itinerary, trip_id: "trip_id_2", start_time: ~T[10:46:00]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to avoid magic numbers, names, times, etc. in tests. Reading this test, I wonder what is special about these two times. If we're just testing that the text in the button reflects the time we pass it, we can assign a random time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're testing that the later time is the one that appears, we can create a random time for the first then add time to that, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went the way I did here because I prefer not to have logic in tests, since that makes it harder to see exactly what the test is testing. In this case, the logic would be the conversion from ~T[09:26:00]
into the string "9:26AM"
.
I do agree that having ~T[09:26:00]
and "9:26AM"
be closer to each other in the test file would make them feel less magic. Lemme try a quick refactor, and then let's talk if we still don't love that.
def stub_otp_results(itineraries) do | ||
expect(OpenTripPlannerClient.Mock, :plan, fn _ -> | ||
{:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}} | ||
end) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should probably be private functions since they won't be used anywhere outside of this file.
# Uhhh the OTP factory will generate with any route_type value but our | ||
# parsing will break with unexpected route types | ||
itineraries = | ||
OtpFactory.build_list(3, :itinerary) | ||
|> Enum.map(&limit_route_types/1) | ||
|
||
stub_otp_results(itineraries) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we already do this in a factory?
|> limit_route_types() |
If not, we should probably fix the factory rather than add that code here (since it applies to all code that uses the OtpFactory and not just this test).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we do this in a factory (I didn't add this - I just moved it), and I do think we should. I was thinking that fixing that could be a follow-up PR, so that the depart-at buttons can still go-live.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine as long as we do it later. I know the factory does this, but it also parses it. Might be worth trying to use the factory directly:
def itinerary_factory do |
expect(OpenTripPlannerClient.Mock, :plan, fn _ -> | ||
{:ok, %OpenTripPlannerClient.Plan{itineraries: []}} | ||
end) | ||
stub_otp_results([]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're only using this in one place, I would just write this as an expectation in this test. That would allow you to remove one of the two functions above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stub_otp_results
is called in two places - once here and once in stub_populated_otp_results
. I guess I could inline it in both places, but I wanted to avoid duplicating 👇
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
{:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
end)
I'd love to talk about this more, since I actually found this easier to reason about by having the state located closer to where that state is used. I got confused by having the state managed by the live view, but then having the interaction still be facilitated by the buttons within the details component. I could sort of see having the state still live at the live view level, and get managed via callback, but I think that the itinerary_details component would still need to be a live component in order to use callbacks. |
If you don't capture clicks with target = myself then they'll go to the parent. That way the logic for the entire live view is in a single place instead of in multiple places. It gives us a nice pattern of expectation that attributes flow down and events bubble up. From the docs:
|
In metro, the only reason the map and datepicker are live components is because they have to interact with external libraries and manage state in JS. Even the map, when clicked, actually sends the event to the parent live view. So, if you were going to add a pin where the user clicked the live view would add that pin in a callback. |
Talked offline with the following conclusion:
|
{:ok, | ||
socket | ||
|> assign(:expanded_itinerary_index, nil) | ||
|> assign(:selected_itinerary_detail_index, 0)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per this comment, both of these bits of state are going to get moved up to the TripPlanner
level in a follow-up PR.
…e-showing an itinerary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing the feedback!
Adds these buttons, which toggle between different versions of the same itinerary.
Summary of changes
Asana Ticket: Trip Planner Preview | "Depart at" buttons
General checks
New UI, or substantial UI changes
New endpoints, or non-trivial changes to current endpoints