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

ALTAPPS-544: Shared Sentry transactions convenient API #705

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.hyperskill.app.home.presentation
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
Expand All @@ -25,6 +26,7 @@ import org.hyperskill.app.home.presentation.HomeFeature.Message
import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder
import org.hyperskill.app.sentry.domain.withTransaction
import org.hyperskill.app.step.domain.interactor.StepInteractor
import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow
import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor
Expand Down Expand Up @@ -69,39 +71,7 @@ class HomeActionDispatcher(

override suspend fun doSuspendableAction(action: Action) {
when (action) {
is Action.FetchHomeScreenData -> {
val sentryTransaction = HyperskillSentryTransactionBuilder.buildHomeScreenRemoteDataLoading()
sentryInteractor.startTransaction(sentryTransaction)

val currentProfile = currentProfileStateRepository
.getState(forceUpdate = true) // ALTAPPS-303: Get from remote to get relevant problem of the day
.getOrElse {
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
return onNewMessage(Message.HomeFailure)
}

val problemOfDayStateResult = actionScope.async { getProblemOfDayState(currentProfile.dailyStep) }
val repetitionsStateResult = actionScope.async { getRepetitionsState() }
val isFreemiumEnabledResult = actionScope.async { freemiumInteractor.isFreemiumEnabled() }

val problemOfDayState = problemOfDayStateResult.await().getOrElse {
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
return onNewMessage(Message.HomeFailure)
}
val repetitionsState = repetitionsStateResult.await().getOrElse {
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
return onNewMessage(Message.HomeFailure)
}
val isFreemiumEnabled = isFreemiumEnabledResult.await().getOrElse {
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
return onNewMessage(Message.HomeFailure)
}

sentryInteractor.finishTransaction(sentryTransaction)

onNewMessage(Message.HomeSuccess(problemOfDayState, repetitionsState, isFreemiumEnabled))
onNewMessage(Message.ReadyToLaunchNextProblemInTimer)
}
is Action.FetchHomeScreenData -> handleFetchHomeScreenData(::onNewMessage)
is Action.LaunchTimer -> {
if (isTimerLaunched) {
return
Expand Down Expand Up @@ -137,6 +107,30 @@ class HomeActionDispatcher(
}
}

private suspend fun handleFetchHomeScreenData(onNewMessage: (Message) -> Unit) {
sentryInteractor.withTransaction(
HyperskillSentryTransactionBuilder.buildHomeScreenRemoteDataLoading(),
onError = { setOf(Message.HomeFailure) }
) {
coroutineScope {
val currentProfile = currentProfileStateRepository
.getState(forceUpdate = true) // ALTAPPS-303: Get from remote to get a relevant problem of the day
.getOrThrow()
val problemOfDayStateResult = async { getProblemOfDayState(currentProfile.dailyStep) }
val repetitionsStateResult = async { getRepetitionsState() }
val isFreemiumEnabledResult = async { freemiumInteractor.isFreemiumEnabled() }
setOf(
Message.HomeSuccess(
problemOfDayState = problemOfDayStateResult.await().getOrThrow(),
repetitionsState = repetitionsStateResult.await().getOrThrow(),
isFreemiumEnabled = isFreemiumEnabledResult.await().getOrThrow()
),
Message.ReadyToLaunchNextProblemInTimer
)
}
}.forEach(onNewMessage)
}

private suspend fun getProblemOfDayState(dailyStepId: Long?): Result<HomeFeature.ProblemOfDayState> =
if (dailyStepId == null) {
Result.success(HomeFeature.ProblemOfDayState.Empty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.hyperskill.app.projects.domain.model.ProjectWithProgress
import org.hyperskill.app.projects.domain.repository.ProjectsRepository
import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder
import org.hyperskill.app.sentry.domain.withTransaction
import org.hyperskill.app.study_plan.domain.repository.CurrentStudyPlanStateRepository
import org.hyperskill.app.track.domain.interactor.TrackInteractor
import org.hyperskill.app.track.domain.model.TrackWithProgress
Expand Down Expand Up @@ -50,59 +51,39 @@ internal class ProgressScreenActionDispatcher(
action: InternalAction.FetchTrackWithProgress,
onNewMessage: (Message) -> Unit
) {
coroutineScope {
val transaction = HyperskillSentryTransactionBuilder
.buildProgressScreenRemoteTrackWithProgressLoading()
sentryInteractor.startTransaction(transaction)

val profileDeferred = async {
currentProfileStateRepository
.getState(forceUpdate = action.forceLoadFromNetwork)
}
val studyPlanDeferred = async {
currentStudyPlanStateRepository
.getState(forceUpdate = action.forceLoadFromNetwork)
}

val profile = profileDeferred.await().getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProgressScreenFeature.TrackWithProgressFetchResult.Error)
return@coroutineScope
}
val studyPlan = studyPlanDeferred.await().getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProgressScreenFeature.TrackWithProgressFetchResult.Error)
return@coroutineScope
}
sentryInteractor.withTransaction(
HyperskillSentryTransactionBuilder.buildProgressScreenRemoteTrackWithProgressLoading(),
onError = { ProgressScreenFeature.TrackWithProgressFetchResult.Error }
) {
coroutineScope {
val profileDeferred = async {
currentProfileStateRepository
.getState(forceUpdate = action.forceLoadFromNetwork)
}
val studyPlanDeferred = async {
currentStudyPlanStateRepository
.getState(forceUpdate = action.forceLoadFromNetwork)
}

if (studyPlan.trackId == null) {
sentryInteractor.finishTransaction(transaction)
onNewMessage(ProgressScreenFeature.TrackWithProgressFetchResult.Error)
return@coroutineScope
}
val profile = profileDeferred.await().getOrThrow()
val studyPlan = studyPlanDeferred.await().getOrThrow()

val trackWithProgress = fetchTrackWithProgress(studyPlan.trackId, action.forceLoadFromNetwork)
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProgressScreenFeature.TrackWithProgressFetchResult.Error)
return@coroutineScope
if (studyPlan.trackId == null) {
return@coroutineScope ProgressScreenFeature.TrackWithProgressFetchResult.Error
}

if (trackWithProgress == null) {
sentryInteractor.finishTransaction(transaction)
onNewMessage(ProgressScreenFeature.TrackWithProgressFetchResult.Error)
return@coroutineScope
}
val trackWithProgress =
fetchTrackWithProgress(studyPlan.trackId, action.forceLoadFromNetwork)
.getOrThrow()
?: return@coroutineScope ProgressScreenFeature.TrackWithProgressFetchResult.Error

sentryInteractor.finishTransaction(transaction)
onNewMessage(
ProgressScreenFeature.TrackWithProgressFetchResult.Success(
trackWithProgress = trackWithProgress,
studyPlan = studyPlan,
profile = profile
)
)
}
}
}.let(onNewMessage)
ivan-magda marked this conversation as resolved.
Show resolved Hide resolved
}

private suspend fun handleFetchProjectWithProgressAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.hyperskill.app.projects.domain.model.projectId
import org.hyperskill.app.projects.domain.repository.ProjectsRepository
import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder
import org.hyperskill.app.sentry.domain.withTransaction
import org.hyperskill.app.study_plan.domain.repository.CurrentStudyPlanStateRepository
import org.hyperskill.app.track.domain.model.getAllProjects
import org.hyperskill.app.track.domain.repository.TrackRepository
Expand Down Expand Up @@ -49,64 +50,31 @@ internal class ProjectSelectionListActionDispatcher(
action: InternalAction.FetchContent,
onNewMessage: (Message) -> Unit
) {
coroutineScope {
val transaction = HyperskillSentryTransactionBuilder
.buildProjectSelectionListScreenRemoteDataLoading()
sentryInteractor.startTransaction(transaction)
sentryInteractor.withTransaction(
HyperskillSentryTransactionBuilder.buildProjectSelectionListScreenRemoteDataLoading(),
onError = { ProjectSelectionListFeature.ContentFetchResult.Error }
) {
coroutineScope {
val profile = currentProfileStateRepository.getState(forceUpdate = false).getOrThrow()
val track = trackRepository.getTrack(action.trackId, action.forceLoadFromNetwork).getOrThrow()

val profile = currentProfileStateRepository.getState(forceUpdate = false)
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProjectSelectionListFeature.ContentFetchResult.Error)
return@coroutineScope
}

val track =
trackRepository.getTrack(action.trackId, action.forceLoadFromNetwork)
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProjectSelectionListFeature.ContentFetchResult.Error)
return@coroutineScope
}

val projectsIds = track.getAllProjects(profile.isBeta)

val studyPlanDeferred = async {
currentStudyPlanStateRepository.getState(action.forceLoadFromNetwork)
}

val projectsDeferred = async {
projectsRepository.getProjects(projectsIds, action.forceLoadFromNetwork)
}
val projectsIds = track.getAllProjects(profile.isBeta)

val projectsProgressesDeferred = async {
progressesRepository.getProjectsProgresses(projectsIds, action.forceLoadFromNetwork)
}

val studyPlan = studyPlanDeferred.await()
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProjectSelectionListFeature.ContentFetchResult.Error)
return@coroutineScope
val studyPlanDeferred = async {
currentStudyPlanStateRepository.getState(action.forceLoadFromNetwork)
}

val projects = projectsDeferred.await()
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProjectSelectionListFeature.ContentFetchResult.Error)
return@coroutineScope
val projectsDeferred = async {
projectsRepository.getProjects(projectsIds, action.forceLoadFromNetwork)
}
val projectsProgressesDeferred = async {
progressesRepository.getProjectsProgresses(projectsIds, action.forceLoadFromNetwork)
}

val projectsProgresses: Map<Long, ProjectProgress> =
projectsProgressesDeferred.await().map(::mapProgressesToMap)
.getOrElse {
sentryInteractor.finishTransaction(transaction, throwable = it)
onNewMessage(ProjectSelectionListFeature.ContentFetchResult.Error)
return@coroutineScope
}
val studyPlan = studyPlanDeferred.await().getOrThrow()
val projects = projectsDeferred.await().getOrThrow()
val projectsProgresses: Map<Long, ProjectProgress> =
projectsProgressesDeferred.await().map(::mapProgressesToMap).getOrThrow()

sentryInteractor.finishTransaction(transaction)
onNewMessage(
ProjectSelectionListFeature.ContentFetchResult.Success(
profile = profile,
track = track,
Expand All @@ -119,8 +87,8 @@ internal class ProjectSelectionListActionDispatcher(
},
currentProjectId = studyPlan.projectId
)
)
}
}
}.let(onNewMessage)
}

private fun mapProgressesToMap(progresses: List<ProjectProgress>): Map<Long, ProjectProgress> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.hyperskill.app.sentry.domain

import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransaction

/**
* Execute a block of code within a Sentry transaction.
* Automatically starts and finish the [transaction].
*
* @param transaction The transaction to be started.
* @param onError Callback function to create a result in case of an error.
* @param measureBlock The block of code to be executed within the transaction.
* @return The measureBlock execution result.
*/
suspend inline fun <T> SentryInteractor.withTransaction(
transaction: HyperskillSentryTransaction,
onError: (Throwable) -> T,
measureBlock: () -> T
): T {
startTransaction(transaction)
return try {
val result = measureBlock()
finishTransaction(transaction)
result
} catch (e: Exception) {
finishTransaction(transaction, e)
onError(e)
}
}