Skip to content

Commit

Permalink
fix: Display GL vehicles on filtered stop page (#460)
Browse files Browse the repository at this point in the history
* feat: Fetch all GL vehicles on the stop page

* test: Add tests for multi route vehicles
  • Loading branch information
EmmaSimon authored Oct 15, 2024
1 parent f3a677a commit 40ff300
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 18 deletions.
2 changes: 0 additions & 2 deletions iosApp/iosApp/Pages/Map/HomeMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ struct HomeMapView: View {
@State var vehiclesRepository: IVehiclesRepository
@State var vehiclesData: [Vehicle]?

@State var upcomingRoutePatterns: Set<String> = .init()

@StateObject var locationDataManager: LocationDataManager
@Binding var sheetHeight: CGFloat

Expand Down
4 changes: 3 additions & 1 deletion iosApp/iosApp/Pages/Map/HomeMapViewHandlerExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ extension HomeMapView {
leaveVehiclesChannel()
vehiclesRepository.connect(routeId: routeId, directionId: directionId) { outcome in
if case let .ok(result) = onEnum(of: outcome) {
vehiclesData = Array(result.data.vehicles.values)
if let departures = nearbyVM.departures {
vehiclesData = Array(departures.filterVehiclesByUpcoming(vehicles: result.data).values)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,7 @@ object RouteFeaturesBuilder {

if (targetRouteData.isNotEmpty()) {
return departures?.let {
val upcomingRoutePatternIds =
departures.routes
.flatMap { it.allUpcomingTrips() }
.mapNotNull { it.trip.routePatternId }
val targetRoutePatternIds = upcomingRoutePatternIds.toSet()
val targetRoutePatternIds = departures.upcomingPatternIds
targetRouteData.map { routeData ->
val filteredShapes =
routeData.segmentedShapes.filter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.mbta.tid.mbta_app.model.response.GlobalResponse
import com.mbta.tid.mbta_app.model.response.NearbyResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.model.response.ScheduleResponse
import com.mbta.tid.mbta_app.model.response.VehiclesStreamDataResponse
import kotlinx.datetime.Instant

data class StopDetailsDepartures(val routes: List<PatternsByStop>) {
Expand Down Expand Up @@ -44,6 +45,15 @@ data class StopDetailsDepartures(val routes: List<PatternsByStop>) {
}
)

val allUpcomingTrips = routes.flatMap { it.allUpcomingTrips() }

val upcomingPatternIds = allUpcomingTrips.mapNotNull { it.trip.routePatternId }.toSet()

fun filterVehiclesByUpcoming(vehicles: VehiclesStreamDataResponse): Map<String, Vehicle> {
val routeIds = allUpcomingTrips.map { it.trip.routeId }.toSet()
return vehicles.vehicles.filter { routeIds.contains(it.value.routeId) }
}

fun autoFilter(): StopDetailsFilter? {
if (routes.size != 1) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ object VehiclesOnRouteChannel {

val newDataEvent = "stream_data"

fun topic(routeIds: List<String>, directionId: Int) =
"vehicles:routes:${routeIds.joinToString(",")}:${directionId}"

fun joinPayload(routeId: String, directionId: Int): Map<String, Any> {
return mapOf("route_id" to routeId, "direction_id" to directionId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mbta.tid.mbta_app.repositories

import com.mbta.tid.mbta_app.model.SocketError
import com.mbta.tid.mbta_app.model.Vehicle
import com.mbta.tid.mbta_app.model.greenRoutes
import com.mbta.tid.mbta_app.model.response.ApiResult
import com.mbta.tid.mbta_app.model.response.VehiclesStreamDataResponse
import com.mbta.tid.mbta_app.network.PhoenixChannel
Expand Down Expand Up @@ -29,8 +30,16 @@ class VehiclesRepository(private val socket: PhoenixSocket) : IVehiclesRepositor
directionId: Int,
onReceive: (ApiResult<VehiclesStreamDataResponse>) -> Unit
) {
val joinPayload = VehiclesOnRouteChannel.joinPayload(routeId, directionId)
channel = socket.getChannel(VehiclesOnRouteChannel.topic, joinPayload)
val topic =
VehiclesOnRouteChannel.topic(
if (routeId == "line-Green") {
greenRoutes.toList()
} else {
listOf(routeId)
},
directionId
)
channel = socket.getChannel(topic, emptyMap())

channel?.onEvent(VehiclesOnRouteChannel.newDataEvent) { message ->
handleNewDataMessage(message, onReceive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.mbta.tid.mbta_app.model.response.AlertsStreamDataResponse
import com.mbta.tid.mbta_app.model.response.GlobalResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.model.response.ScheduleResponse
import com.mbta.tid.mbta_app.model.response.VehiclesStreamDataResponse
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.hours
Expand Down Expand Up @@ -228,6 +229,30 @@ class StopDetailsDeparturesTest {
val directionWest = Direction("West", "Kenmore & West", 0)
val directionEast = Direction("East", "Park St & North", 1)

val departures =
StopDetailsDepartures(
stop,
GlobalResponse(
objects,
mapOf(
stop.id to
listOf(
routePatternB1.id,
routePatternB2.id,
routePatternC1.id,
routePatternC2.id,
routePatternE1.id,
routePatternE2.id
)
)
),
ScheduleResponse(objects),
PredictionsStreamDataResponse(objects),
null,
setOf(),
filterAtTime = time,
)

assertEquals(
StopDetailsDepartures(
listOf(
Expand Down Expand Up @@ -269,19 +294,125 @@ class StopDetailsDeparturesTest {
)
)
),
departures
)

assertEquals(
setOf(
routePatternB1.id,
routePatternC1.id,
routePatternE1.id,
routePatternB2.id,
routePatternC2.id,
routePatternE2.id
),
departures.upcomingPatternIds
)
}

@Test
fun `StopDetailsDepartures filters vehicles by relevant routes`() {
val objects = ObjectCollectionBuilder()

val stop = objects.stop()
objects.line { id = "line-Green" }
val routeB =
objects.route {
id = "B"
sortOrder = 1
lineId = "line-Green"
directionNames = listOf("West", "East")
directionDestinations = listOf("Kenmore & West", "Park St & North")
}
val routePatternB =
objects.routePattern(routeB) {
representativeTrip { headsign = "B" }
directionId = 0
typicality = RoutePattern.Typicality.Typical
}
val tripB = objects.trip(routePatternB)

val routeC =
objects.route {
id = "C"
sortOrder = 2
lineId = "line-Green"
directionNames = listOf("West", "East")
directionDestinations = listOf("Kenmore & West", "Park St & North")
}
val routePatternC =
objects.routePattern(routeC) {
representativeTrip { headsign = "C" }
directionId = 0
typicality = RoutePattern.Typicality.Typical
}
val tripC = objects.trip(routePatternC)

val routeD =
objects.route {
id = "D"
sortOrder = 3
lineId = "line-Green"
directionNames = listOf("West", "East")
directionDestinations = listOf("Riverside", "Park St & North")
}

val routeE =
objects.route {
id = "E"
sortOrder = 3
lineId = "line-Green"
directionNames = listOf("West", "East")
directionDestinations = listOf("Heath Street", "Park St & North")
}
val routePatternE =
objects.routePattern(routeE) {
representativeTrip { headsign = "Heath Street" }
directionId = 0
typicality = RoutePattern.Typicality.Typical
id = "test-hs"
}
val tripE = objects.trip(routePatternE)

val time = Instant.parse("2024-03-18T10:41:13-04:00")

val schedB =
objects.schedule {
trip = tripB
stopId = stop.id
stopSequence = 90
departureTime = time + 1.minutes
}
val schedC =
objects.schedule {
trip = tripC
stopId = stop.id
stopSequence = 90
departureTime = time + 2.minutes
}
val schedE =
objects.schedule {
trip = tripE
stopId = stop.id
stopSequence = 90
departureTime = time + 3.minutes
}

objects.prediction(schedB) { departureTime = time + 1.5.minutes }
objects.prediction(schedC) { departureTime = time + 2.3.minutes }
objects.prediction(schedE) { departureTime = time + 2.3.minutes }

val departures =
StopDetailsDepartures(
stop,
GlobalResponse(
objects,
mapOf(
stop.id to
listOf(
routePatternB1.id,
routePatternB2.id,
routePatternC1.id,
routePatternC2.id,
routePatternE1.id,
routePatternE2.id
routePatternB.id,
routePatternC.id,
routePatternE.id,
)
)
),
Expand All @@ -291,6 +422,42 @@ class StopDetailsDeparturesTest {
setOf(),
filterAtTime = time,
)
val vehicleB =
objects.vehicle {
routeId = routeB.id
currentStatus = Vehicle.CurrentStatus.InTransitTo
}
val vehicleC =
objects.vehicle {
routeId = routeC.id
currentStatus = Vehicle.CurrentStatus.InTransitTo
}
val vehicleD =
objects.vehicle {
routeId = routeD.id
currentStatus = Vehicle.CurrentStatus.InTransitTo
}
val vehicleE =
objects.vehicle {
routeId = routeE.id
currentStatus = Vehicle.CurrentStatus.InTransitTo
}
val vehicleResponse =
VehiclesStreamDataResponse(
mapOf(
vehicleB.id to vehicleB,
vehicleC.id to vehicleC,
vehicleD.id to vehicleD,
vehicleE.id to vehicleE,
)
)
assertEquals(
mapOf(
vehicleB.id to vehicleB,
vehicleC.id to vehicleC,
vehicleE.id to vehicleE,
),
departures.filterVehiclesByUpcoming(vehicleResponse)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,13 @@ class VehiclesOnRouteChannelTest {
parsed
)
}

@Test
fun testTopicInterpolation() {
val topic1 = VehiclesOnRouteChannel.topic(listOf("Red"), 0)
val topic2 = VehiclesOnRouteChannel.topic(listOf("Green-B", "Green-C", "Green-D"), 1)

assertEquals("vehicles:routes:Red:0", topic1)
assertEquals("vehicles:routes:Green-B,Green-C,Green-D:1", topic2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.mbta.tid.mbta_app.network.PhoenixMessage
import com.mbta.tid.mbta_app.network.PhoenixPush
import com.mbta.tid.mbta_app.network.PhoenixPushStatus
import com.mbta.tid.mbta_app.network.PhoenixSocket
import com.mbta.tid.mbta_app.phoenix.PredictionsForStopsChannel
import com.mbta.tid.mbta_app.phoenix.VehiclesOnRouteChannel
import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
Expand Down Expand Up @@ -44,7 +44,7 @@ class VehiclesRepositoryTest : KoinTest {
val vehiclesRepo = VehiclesRepository(socket)
every { socket.getChannel(any(), any()) } returns mock<PhoenixChannel>(MockMode.autofill)
vehiclesRepo.channel =
socket.getChannel(topic = PredictionsForStopsChannel.topic, params = emptyMap())
socket.getChannel(topic = VehiclesOnRouteChannel.topic, params = emptyMap())
assertNotNull(vehiclesRepo.channel)

vehiclesRepo.disconnect()
Expand Down

0 comments on commit 40ff300

Please sign in to comment.