Skip to content

Commit 7644b13

Browse files
authored
shared: Fix JSON deserialization error when fetching comments (#1240)
^ALTAPPS-1367
1 parent 699f9a4 commit 7644b13

File tree

11 files changed

+97
-100
lines changed

11 files changed

+97
-100
lines changed

shared/src/commonMain/kotlin/org/hyperskill/app/comments/domain/model/Comment.kt

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ data class Comment(
1515
val targetType: ContentType = ContentType.UNKNOWN,
1616
@SerialName("text")
1717
val text: String,
18-
@SerialName("localized_text")
19-
val localizedText: String,
2018
@SerialName("user")
2119
val user: CommentAuthor,
2220
@SerialName("time")

shared/src/commonMain/kotlin/org/hyperskill/app/comments/remote/CommentsRemoteDataSourceImpl.kt

+14-18
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,44 @@ import io.ktor.client.call.body
55
import io.ktor.client.request.get
66
import io.ktor.http.ContentType
77
import io.ktor.http.contentType
8-
import kotlinx.coroutines.Deferred
98
import kotlinx.coroutines.async
109
import kotlinx.coroutines.awaitAll
10+
import kotlinx.coroutines.coroutineScope
1111
import org.hyperskill.app.comments.data.source.CommentsRemoteDataSource
1212
import org.hyperskill.app.comments.domain.model.Comment
1313
import org.hyperskill.app.comments.remote.model.CommentsResponse
1414
import org.hyperskill.app.network.remote.parameterIds
1515

16-
class CommentsRemoteDataSourceImpl(
16+
internal class CommentsRemoteDataSourceImpl(
1717
private val httpClient: HttpClient
1818
) : CommentsRemoteDataSource {
1919
override suspend fun getComment(commentId: Long): Result<Comment> =
20-
kotlin.runCatching {
20+
runCatching {
2121
getComments(listOf(commentId))
2222
.map { it.first() }
2323
.getOrThrow()
2424
}
2525

2626
// TODO: ALTAPPS-399/Shared-Implement-generic-GET-request-for-objects-by-ids-with-chunks
2727
override suspend fun getComments(commentsIds: List<Long>): Result<List<Comment>> =
28-
kotlin.runCatching {
28+
runCatching {
2929
if (commentsIds.isEmpty()) {
30-
return Result.success(emptyList())
30+
return@runCatching emptyList()
3131
}
3232

3333
val chunkSize = 100
3434
val chunks = commentsIds.chunked(chunkSize)
3535

36-
val requests: MutableList<Deferred<List<Comment>>> = mutableListOf()
37-
38-
for (chunkIds in chunks) {
39-
val futureResult = httpClient.async {
40-
httpClient.get("/api/comments") {
41-
contentType(ContentType.Application.Json)
42-
parameterIds(chunkIds)
43-
}.body<CommentsResponse>().comments
36+
coroutineScope {
37+
val requests = chunks.map { chunkIds ->
38+
async {
39+
httpClient.get("/api/comments") {
40+
contentType(ContentType.Application.Json)
41+
parameterIds(chunkIds)
42+
}.body<CommentsResponse>().comments
43+
}
4444
}
45-
requests.add(futureResult)
45+
requests.awaitAll().flatten()
4646
}
47-
48-
val responses = requests.awaitAll()
49-
50-
return Result.success(responses.flatten())
5147
}
5248
}

shared/src/commonMain/kotlin/org/hyperskill/app/comments/screen/view/mapper/CommentsScreenViewStateMapper.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal class CommentsScreenViewStateMapper(
7474
authorAvatar = comment.user.avatar,
7575
authorFullName = comment.user.fullName,
7676
formattedTime = comment.time?.let { dateFormatter.formatTimeDistance(it) },
77-
text = comment.localizedText,
77+
text = comment.text,
7878
reactions = comment.reactions.filter { it.value > 0 && it.reactionType in ReactionType.commentReactions }
7979
)
8080

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/MainStepQuizHintsActionDispatcher.kt

+39-38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.hyperskill.app.sentry.domain.withTransaction
1111
import org.hyperskill.app.step_quiz_hints.domain.interactor.StepQuizHintsInteractor
1212
import org.hyperskill.app.step_quiz_hints.domain.model.HintState
1313
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Action
14+
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.InternalAction
1415
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Message
1516
import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository
1617
import org.hyperskill.app.user_storage.domain.interactor.UserStorageInteractor
@@ -29,7 +30,7 @@ internal class MainStepQuizHintsActionDispatcher(
2930
) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {
3031
override suspend fun doSuspendableAction(action: Action) {
3132
when (action) {
32-
is Action.FetchHintsIds -> {
33+
is InternalAction.FetchHintsIds -> {
3334
val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizHintsScreenRemoteDataLoading()
3435
sentryInteractor.startTransaction(sentryTransaction)
3536

@@ -66,7 +67,7 @@ internal class MainStepQuizHintsActionDispatcher(
6667
)
6768
)
6869
}
69-
is Action.ReportHint -> {
70+
is InternalAction.ReportHint -> {
7071
val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizHintsReportHint()
7172
sentryInteractor.startTransaction(sentryTransaction)
7273

@@ -88,47 +89,14 @@ internal class MainStepQuizHintsActionDispatcher(
8889
}
8990
)
9091
}
91-
is Action.ReactHint -> handleReactHintAction(action, ::onNewMessage)
92-
is Action.FetchNextHint -> {
93-
val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizHintsFetchNextHint()
94-
sentryInteractor.startTransaction(sentryTransaction)
95-
96-
commentsRepository
97-
.getComment(action.nextHintId)
98-
.onSuccess {
99-
onNewMessage(
100-
Message.NextHintLoaded(
101-
it,
102-
action.remainingHintsIds,
103-
action.areHintsLimited,
104-
action.stepId
105-
)
106-
)
107-
108-
userStorageInteractor.updateUserStorage(
109-
UserStoragePathBuilder.buildSeenHint(it.targetId, it.id),
110-
HintState.SEEN.userStorageValue
111-
)
112-
sentryInteractor.finishTransaction(sentryTransaction)
113-
}
114-
.onFailure {
115-
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
116-
onNewMessage(
117-
Message.NextHintLoadingError(
118-
action.nextHintId,
119-
action.remainingHintsIds,
120-
action.areHintsLimited,
121-
action.stepId
122-
)
123-
)
124-
}
125-
}
92+
is InternalAction.ReactHint -> handleReactHintAction(action, ::onNewMessage)
93+
is InternalAction.FetchNextHint -> handleFetchNextHintAction(action, ::onNewMessage)
12694
else -> {}
12795
}
12896
}
12997

13098
private suspend fun handleReactHintAction(
131-
action: Action.ReactHint,
99+
action: InternalAction.ReactHint,
132100
onNewMessage: (Message) -> Unit
133101
) {
134102
sentryInteractor.withTransaction(
@@ -153,4 +121,37 @@ internal class MainStepQuizHintsActionDispatcher(
153121
Message.ReactHintSuccess
154122
}.let(onNewMessage)
155123
}
124+
125+
private suspend fun handleFetchNextHintAction(
126+
action: InternalAction.FetchNextHint,
127+
onNewMessage: (Message) -> Unit
128+
) {
129+
sentryInteractor.withTransaction(
130+
HyperskillSentryTransactionBuilder.buildStepQuizHintsFetchNextHint(),
131+
onError = {
132+
Message.NextHintLoadingError(
133+
action.nextHintId,
134+
action.remainingHintsIds,
135+
action.areHintsLimited,
136+
action.stepId
137+
)
138+
}
139+
) {
140+
val comment = commentsRepository
141+
.getComment(action.nextHintId)
142+
.getOrThrow()
143+
144+
userStorageInteractor.updateUserStorage(
145+
UserStoragePathBuilder.buildSeenHint(stepId = comment.targetId, hintId = comment.id),
146+
HintState.SEEN.userStorageValue
147+
)
148+
149+
Message.NextHintLoaded(
150+
comment,
151+
action.remainingHintsIds,
152+
action.areHintsLimited,
153+
action.stepId
154+
)
155+
}.let(onNewMessage)
156+
}
156157
}

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
44
import org.hyperskill.app.analytic.presentation.SingleAnalyticEventActionDispatcher
55
import org.hyperskill.app.core.presentation.CompositeActionDispatcher
66
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Action
7+
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.InternalAction
78
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Message
89

910
class StepQuizHintsActionDispatcher internal constructor(
@@ -13,7 +14,7 @@ class StepQuizHintsActionDispatcher internal constructor(
1314
listOf(
1415
mainStepQuizHintsActionDispatcher,
1516
SingleAnalyticEventActionDispatcher(analyticInteractor) {
16-
(it as? Action.LogAnalyticEvent)?.analyticEvent
17+
(it as? InternalAction.LogAnalyticEvent)?.analyticEvent
1718
}
1819
)
1920
)

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt

+27-25
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object StepQuizHintsFeature {
1111
step.commentsStatistics.any { it.thread == CommentThread.HINT && it.totalCount > 0 }
1212

1313
sealed interface State {
14-
object Idle : State
14+
data object Idle : State
1515

1616
/**
1717
* @property isInitialLoading true in case of initial data loading, false in case of certain hint loading
@@ -52,11 +52,11 @@ object StepQuizHintsFeature {
5252
}
5353

5454
sealed interface ViewState {
55-
object Idle : ViewState
55+
data object Idle : ViewState
5656

57-
object InitialLoading : ViewState
57+
data object InitialLoading : ViewState
5858

59-
object HintLoading : ViewState
59+
data object HintLoading : ViewState
6060

6161
sealed interface Content : ViewState {
6262
object SeeHintButton : Content
@@ -68,7 +68,7 @@ object StepQuizHintsFeature {
6868
) : Content
6969
}
7070

71-
object Error : ViewState
71+
data object Error : ViewState
7272

7373
enum class HintState {
7474
REACT_TO_HINT,
@@ -110,32 +110,32 @@ object StepQuizHintsFeature {
110110
/**
111111
* Indicates that react hint succeed
112112
*/
113-
object ReactHintSuccess : Message
113+
data object ReactHintSuccess : Message
114114

115115
/**
116116
* Indicates that react hint failed
117117
*/
118-
object ReactHintFailure : Message
118+
data object ReactHintFailure : Message
119119

120120
/**
121121
* Creates report for current hint, assumes that user confirmed that action
122122
*/
123-
object ReportHint : Message
123+
data object ReportHint : Message
124124

125125
/**
126126
* Indicates that report hint succeed
127127
*/
128-
object ReportHintSuccess : Message
128+
data object ReportHintSuccess : Message
129129

130130
/**
131131
* Indicates that report hint failed
132132
*/
133-
object ReportHintFailure : Message
133+
data object ReportHintFailure : Message
134134

135135
/**
136136
* Initiate loading next hint
137137
*/
138-
object LoadHintButtonClicked : Message
138+
data object LoadHintButtonClicked : Message
139139

140140
/**
141141
* Message to fill state with ready data
@@ -170,8 +170,8 @@ object StepQuizHintsFeature {
170170
/**
171171
* Analytic
172172
*/
173-
object ClickedReportEventMessage : Message
174-
object ReportHintNoticeShownEventMessage : Message
173+
data object ClickedReportEventMessage : Message
174+
data object ReportHintNoticeShownEventMessage : Message
175175
data class ReportHintNoticeHiddenEventMessage(val isReported: Boolean) : Message
176176
}
177177

@@ -183,13 +183,22 @@ object StepQuizHintsFeature {
183183
}
184184

185185
sealed interface Action {
186+
sealed interface ViewAction : Action {
187+
/**
188+
* Shows snackbar with error message
189+
*/
190+
data object ShowNetworkError : ViewAction
191+
}
192+
}
193+
194+
internal sealed interface InternalAction : Action {
186195
/**
187196
* Reporting hint action
188197
*
189198
* @property hintId hint ID to be reported
190199
* @property stepId is used to create user storage record
191200
*/
192-
data class ReportHint(val hintId: Long, val stepId: Long) : Action
201+
data class ReportHint(val hintId: Long, val stepId: Long) : InternalAction
193202

194203
/**
195204
* Creating reaction for hint
@@ -198,14 +207,14 @@ object StepQuizHintsFeature {
198207
* @property stepId is used to create user storage record
199208
* @property reaction reaction from user
200209
*/
201-
data class ReactHint(val hintId: Long, val stepId: Long, val reaction: ReactionType) : Action
210+
data class ReactHint(val hintId: Long, val stepId: Long, val reaction: ReactionType) : InternalAction
202211

203212
/**
204213
* Loading all hints IDs for given step
205214
*
206215
* @property stepId step ID to load hints for it
207216
*/
208-
data class FetchHintsIds(val stepId: Long) : Action
217+
data class FetchHintsIds(val stepId: Long) : InternalAction
209218

210219
/**
211220
* Loading next hint action
@@ -220,20 +229,13 @@ object StepQuizHintsFeature {
220229
val remainingHintsIds: List<Long>,
221230
val areHintsLimited: Boolean,
222231
val stepId: Long
223-
) : Action
232+
) : InternalAction
224233

225234
/**
226235
* Logging analytic event action
227236
*
228237
* @property analyticEvent event to be logged
229238
*/
230-
data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : Action
231-
232-
sealed interface ViewAction : Action {
233-
/**
234-
* Shows snackbar with error message
235-
*/
236-
object ShowNetworkError : ViewAction
237-
}
239+
data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction
238240
}
239241
}

0 commit comments

Comments
 (0)