Skip to content

Commit

Permalink
feat: Use new predictions V2 channel behind feature flag (#423)
Browse files Browse the repository at this point in the history
* refactor: PredctionsStreamDataResponse => PredictionsWithAssociations

* test(PredictionsRepository): tests for v2 channel

* test(PredictionsByStopJoinResponse): unit tests for static funcs

* feat(StopDetailsPage): Using v2 predictions channel

* fix(PredictionsRepositoryTests): Fix mock

* fix(android tests): Add connectV2 stub

* fix(NearbyTransitPageTest): one more missing connectV2

* fix(AppVariant): restore staging

* fix(PredictionsRepositoryTests): fix test failing on ios

* fix: resolve bad merge resolution in MockPredictionsRepository'

* Update iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift

Co-authored-by: Melody Horn <[email protected]>

* test(StopDetailsPageTest): actually assert that departures are updated

* refactor(PredictionsByStopJoinResponse): move functions out of companion

---------

Co-authored-by: Melody Horn <[email protected]>
  • Loading branch information
KaylaBrady and boringcactus authored Sep 26, 2024
1 parent e792b6e commit 20151ec
Show file tree
Hide file tree
Showing 19 changed files with 1,137 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.mbta.tid.mbta_app.model.SocketError
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.NearbyResponse
import com.mbta.tid.mbta_app.model.response.PredictionsByStopJoinResponse
import com.mbta.tid.mbta_app.model.response.PredictionsByStopMessageResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.repositories.INearbyRepository
import com.mbta.tid.mbta_app.repositories.IPinnedRoutesRepository
Expand Down Expand Up @@ -185,6 +187,15 @@ class NearbyTransitPageTest : KoinTest {
onReceive(Outcome(PredictionsStreamDataResponse(builder), null))
}

override fun connectV2(
stopIds: List<String>,
onJoin: (Outcome<PredictionsByStopJoinResponse?, SocketError>) -> Unit,
onMessage:
(Outcome<PredictionsByStopMessageResponse?, SocketError>) -> Unit
) {
/* no-op */
}

override fun disconnect() {
/* no-op */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.mbta.tid.mbta_app.model.RouteType
import com.mbta.tid.mbta_app.model.SocketError
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.PredictionsByStopJoinResponse
import com.mbta.tid.mbta_app.model.response.PredictionsByStopMessageResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.repositories.INearbyRepository
import com.mbta.tid.mbta_app.repositories.IPinnedRoutesRepository
Expand Down Expand Up @@ -179,6 +181,15 @@ class NearbyTransitViewTest : KoinTest {
onReceive(Outcome(PredictionsStreamDataResponse(builder), null))
}

override fun connectV2(
stopIds: List<String>,
onJoin: (Outcome<PredictionsByStopJoinResponse?, SocketError>) -> Unit,
onMessage:
(Outcome<PredictionsByStopMessageResponse?, SocketError>) -> Unit
) {
/* no-op */
}

override fun disconnect() {
/* no-op */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import com.mbta.tid.mbta_app.model.StopDetailsFilter
import com.mbta.tid.mbta_app.model.UpcomingTrip
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.PredictionsByStopJoinResponse
import com.mbta.tid.mbta_app.model.response.PredictionsByStopMessageResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.repositories.IGlobalRepository
import com.mbta.tid.mbta_app.repositories.INearbyRepository
Expand Down Expand Up @@ -125,6 +127,15 @@ class StopDetailsViewTest {
onReceive(Outcome(PredictionsStreamDataResponse(builder), null))
}

override fun connectV2(
stopIds: List<String>,
onJoin: (Outcome<PredictionsByStopJoinResponse?, SocketError>) -> Unit,
onMessage:
(Outcome<PredictionsByStopMessageResponse?, SocketError>) -> Unit
) {
/* no-op */
}

override fun disconnect() {
/* no-op */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.compose.ui.test.junit4.createComposeRule
import com.mbta.tid.mbta_app.model.ObjectCollectionBuilder
import com.mbta.tid.mbta_app.model.Outcome
import com.mbta.tid.mbta_app.model.SocketError
import com.mbta.tid.mbta_app.model.response.PredictionsByStopJoinResponse
import com.mbta.tid.mbta_app.model.response.PredictionsByStopMessageResponse
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.repositories.IPredictionsRepository
import kotlinx.coroutines.channels.Channel
Expand Down Expand Up @@ -48,6 +50,14 @@ class SubscribeToPredictionsTest {
this.onReceive = onReceive
}

override fun connectV2(
stopIds: List<String>,
onJoin: (Outcome<PredictionsByStopJoinResponse?, SocketError>) -> Unit,
onMessage: (Outcome<PredictionsByStopMessageResponse?, SocketError>) -> Unit
) {
/* no-op */
}

override fun disconnect() {
check(isConnected) { "called disconnect when not connected" }
isConnected = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse
import com.mbta.tid.mbta_app.repositories.IPredictionsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.koin.compose.koinInject

Expand Down
3 changes: 3 additions & 0 deletions iosApp/iosApp/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@
},
"Updated: %@" : {
"comment" : "Interpolated value is a timestamp for when displayed alert details were last changed"
},
"Using Predictions Channel V2" : {

},
"Weather" : {
"comment" : "Possible alert cause"
Expand Down
79 changes: 73 additions & 6 deletions iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct NearbyTransitView: View {
var pinnedRouteRepository = RepositoryDI().pinnedRoutes
@State var predictionsRepository = RepositoryDI().predictions
var schedulesRepository = RepositoryDI().schedules
var settingsRepository = RepositoryDI().settings
var getNearby: (GlobalResponse, CLLocationCoordinate2D) -> Void
@Binding var state: NearbyViewModel.NearbyTransitState
@Binding var location: CLLocationCoordinate2D?
Expand All @@ -30,8 +31,10 @@ struct NearbyTransitView: View {
@State var nearbyWithRealtimeInfo: [StopsAssociated]?
@State var now = Date.now
@State var pinnedRoutes: Set<String> = []
@State var predictionsByStop: PredictionsByStopJoinResponse?
@State var predictions: PredictionsStreamDataResponse?
@State var predictionsError: SocketError?
@State var predictionsV2Enabled = false

let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
let inspection = Inspection<Self>()
Expand All @@ -48,7 +51,10 @@ struct NearbyTransitView: View {
.onAppear {
getGlobal()
getNearby(location: location, globalData: globalData)
joinPredictions(state.nearbyByRouteAndStop?.stopIds())
Task {
await getPredictionsFeatureFlag()
joinPredictions(state.nearbyByRouteAndStop?.stopIds())
}
updateNearbyRoutes()
updatePinnedRoutes()
getSchedule()
Expand All @@ -69,6 +75,14 @@ struct NearbyTransitView: View {
.onChange(of: scheduleResponse) { response in
updateNearbyRoutes(scheduleResponse: response)
}
.onChange(of: predictionsByStop) { newPredictionsByStop in
if let newPredictionsByStop {
let condensedPredictions = newPredictionsByStop.toPredictionsStreamDataResponse()
updateNearbyRoutes(predictions: condensedPredictions)
} else {
updateNearbyRoutes(predictions: nil)
}
}
.onChange(of: predictions) { predictions in
updateNearbyRoutes(predictions: predictions)
}
Expand Down Expand Up @@ -105,6 +119,9 @@ struct NearbyTransitView: View {
} else {
ScrollViewReader { proxy in
ScrollView {
if predictionsV2Enabled {
Text("Using Predictions Channel V2")
}
LazyVStack {
ForEach(transit, id: \.id) { nearbyTransit in
switch onEnum(of: nearbyTransit) {
Expand Down Expand Up @@ -142,7 +159,9 @@ struct NearbyTransitView: View {

private func errorCard(_ errorText: String) -> some View {
IconCard(iconName: "network.slash", details: Text(errorText))
.refreshable(state.loading) { getNearby(location: location, globalData: globalData) }
.refreshable(state.loading) {
getNearby(location: location, globalData: globalData)
}
}

var didAppear: ((Self) -> Void)?
Expand Down Expand Up @@ -171,18 +190,62 @@ struct NearbyTransitView: View {
}
}

func getPredictionsFeatureFlag() async {
do {
let settings = try await settingsRepository.getSettings()
predictionsV2Enabled = settings.first(where: { $0.key == .predictionsV2Channel })?.isOn ?? false
} catch {}
}

func joinPredictions(_ stopIds: Set<String>?) {
guard let stopIds else { return }
predictionsRepository.connect(stopIds: Array(stopIds)) { outcome in
if predictionsV2Enabled {
joinPredictionsV2(stopIds: stopIds)
} else {
predictionsRepository.connect(stopIds: Array(stopIds)) { outcome in
DispatchQueue.main.async {
if let data = outcome.data {
predictions = data
predictionsError = nil
} else if let error = outcome.error {
predictionsError = error.toSwiftEnum()
}
}
}
}
}

func joinPredictionsV2(stopIds: Set<String>) {
predictionsRepository.connectV2(stopIds: Array(stopIds), onJoin: { outcome in
DispatchQueue.main.async {
if let data = outcome.data {
predictions = data
predictionsByStop = data
predictionsError = nil
} else if let error = outcome.error {
predictionsError = error.toSwiftEnum()
}
}
}
}, onMessage: { outcome in
DispatchQueue.main.async {
if let data = outcome.data {
if let existingPredictionsByStop = predictionsByStop {
predictionsByStop = existingPredictionsByStop.mergePredictions(updatedPredictions: data)
predictionsError = nil
} else {
predictionsByStop = PredictionsByStopJoinResponse(
predictionsByStop: [data.stopId: data.predictions],
trips: data.trips,
vehicles: data.vehicles
)
predictionsError = nil
}

} else if let error = outcome.error {
predictionsError = error.toSwiftEnum()
}
}

})
}

func leavePredictions() {
Expand Down Expand Up @@ -223,9 +286,13 @@ struct NearbyTransitView: View {
alerts: AlertsStreamDataResponse? = nil,
pinnedRoutes: Set<String>? = nil
) {
let fallbackPredictions = if let predictionsByStop {
predictionsByStop.toPredictionsStreamDataResponse()
} else { self.predictions }

nearbyWithRealtimeInfo = withRealtimeInfo(
schedules: scheduleResponse ?? self.scheduleResponse,
predictions: predictions ?? self.predictions,
predictions: predictions ?? fallbackPredictions,
alerts: alerts ?? nearbyVM.alerts,
filterAtTime: now.toKotlinInstant(),
pinnedRoutes: pinnedRoutes ?? self.pinnedRoutes
Expand Down
6 changes: 6 additions & 0 deletions iosApp/iosApp/Pages/Settings/Setting+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ extension Setting: Identifiable {
"Search - Route Results"
case .map:
"Map Debug"
case .predictionsV2Channel:
"Predictions V2 Channel"
}
}

Expand All @@ -33,6 +35,8 @@ extension Setting: Identifiable {
"point.topleft.down.to.point.bottomright.curvepath.fill"
case .map:
"location.magnifyingglass"
case .predictionsV2Channel:
"magnifyingglass"
}
}

Expand All @@ -42,6 +46,8 @@ extension Setting: Identifiable {
.featureFlags
case .searchRouteResults:
.featureFlags
case .predictionsV2Channel:
.featureFlags
case .map:
.debug
}
Expand Down
Loading

0 comments on commit 20151ec

Please sign in to comment.