[gitlab] extract MR discussions model

GitOrigin-RevId: 0e17fc8e31073e5c986fe5e1ea28225b0d1ff91d
This commit is contained in:
Ivan Semenov
2023-01-16 18:24:58 +01:00
committed by intellij-monorepo-bot
parent 9f3269a66a
commit f3d215e735
4 changed files with 124 additions and 61 deletions

View File

@@ -24,4 +24,7 @@ class GraphQLRequestPagination private constructor(
}
fun GraphQLRequestPagination.asParameters(): Map<String, Any?> =
mapOf("pageSize" to pageSize, "cursor" to afterCursor)
mapOf("pageSize" to pageSize, "cursor" to afterCursor)
fun GraphQLRequestPagination?.orDefault(): GraphQLRequestPagination =
this ?: GraphQLRequestPagination.DEFAULT

View File

@@ -1,11 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.mergerequest.api.request
import com.intellij.collaboration.api.data.GraphQLRequestPagination
import com.intellij.collaboration.api.data.asParameters
import com.intellij.collaboration.api.data.orDefault
import com.intellij.collaboration.api.dto.GraphQLConnectionDTO
import com.intellij.collaboration.api.dto.GraphQLCursorPageInfoDTO
import com.intellij.collaboration.api.page.ApiPageUtil
import kotlinx.coroutines.flow.Flow
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabGQLQueries
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
@@ -13,16 +13,17 @@ import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionDTO
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestId
import java.net.http.HttpResponse
suspend fun GitLabApi.loadAllMergeRequestDiscussions(project: GitLabProjectCoordinates,
mr: GitLabMergeRequestId): Flow<List<GitLabDiscussionDTO>> =
ApiPageUtil.createGQLPagesFlow {
val parameters = it.asParameters() + mapOf(
"projectId" to project.projectPath.fullPath(),
"mriid" to mr.iid
)
val request = gqlQuery(project.serverPath.gqlApiUri, GitLabGQLQueries.getMergeRequestDiscussions, parameters)
loadGQLResponse(request, DiscussionConnection::class.java, "project", "mergeRequest", "discussions").body()
}
suspend fun GitLabApi.loadMergeRequestDiscussions(project: GitLabProjectCoordinates,
mr: GitLabMergeRequestId,
pagination: GraphQLRequestPagination? = null)
: GraphQLConnectionDTO<GitLabDiscussionDTO>? {
val parameters = pagination.orDefault().asParameters() + mapOf(
"projectId" to project.projectPath.fullPath(),
"mriid" to mr.iid
)
val request = gqlQuery(project.serverPath.gqlApiUri, GitLabGQLQueries.getMergeRequestDiscussions, parameters)
return loadGQLResponse(request, DiscussionConnection::class.java, "project", "mergeRequest", "discussions").body()
}
private class DiscussionConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<GitLabDiscussionDTO>)
: GraphQLConnectionDTO<GitLabDiscussionDTO>(pageInfo, nodes)

View File

@@ -0,0 +1,48 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.mergerequest.data
import com.intellij.collaboration.api.page.ApiPageUtil
import com.intellij.collaboration.api.page.foldToList
import com.intellij.collaboration.async.mapScoped
import com.intellij.util.childScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import org.jetbrains.plugins.gitlab.api.GitLabProjectConnection
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionDTO
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestDiscussions
interface GitLabMergeRequestDiscussionsModel {
val userDiscussions: Flow<List<GitLabDiscussion>>
val systemDiscussions: Flow<List<GitLabDiscussionDTO>>
}
class GitLabMergeRequestDiscussionsModelImpl(
parentCs: CoroutineScope,
private val connection: GitLabProjectConnection,
private val mr: GitLabMergeRequestId
) : GitLabMergeRequestDiscussionsModel {
private val cs = parentCs.childScope(Dispatchers.Default)
private val nonEmptyDiscussionsData = flow {
ApiPageUtil.createGQLPagesFlow {
connection.apiClient.loadMergeRequestDiscussions(connection.repo.repository, mr, it)
}.map { discussions ->
discussions.filter { it.notes.isNotEmpty() }
}.foldToList()
.let { emit(it) }
}.shareIn(cs, SharingStarted.Lazily, 1)
override val userDiscussions: Flow<List<GitLabDiscussion>> =
nonEmptyDiscussionsData.mapScoped { discussions ->
val cs = this
discussions.filter { !it.notes.first().system }
.map { LoadedGitLabDiscussion(cs, connection, it) }
}
override val systemDiscussions: Flow<List<GitLabDiscussionDTO>> =
nonEmptyDiscussionsData.map { discussions ->
discussions.filter { it.notes.first().system }
}
}

View File

@@ -1,22 +1,19 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.mergerequest.ui.timeline
import com.intellij.collaboration.async.mapScoped
import com.intellij.util.childScope
import com.intellij.util.concurrency.annotations.RequiresEdt
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
import org.jetbrains.plugins.gitlab.api.GitLabProjectConnection
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadAllMergeRequestDiscussions
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestLabelEvents
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestMilestoneEvents
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestStateEvents
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestDiscussionsModelImpl
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestId
import org.jetbrains.plugins.gitlab.mergerequest.data.LoadedGitLabDiscussion
import org.jetbrains.plugins.gitlab.mergerequest.ui.timeline.GitLabMergeRequestTimelineViewModel.LoadingState
import java.util.concurrent.ConcurrentSkipListSet
import java.util.*
interface GitLabMergeRequestTimelineViewModel {
val timelineLoadingFlow: Flow<LoadingState?>
@@ -38,86 +35,100 @@ class LoadAllGitLabMergeRequestTimelineViewModel(
) : GitLabMergeRequestTimelineViewModel {
private val cs = parentCs.childScope(Dispatchers.Default)
private var loadingRequestState = MutableStateFlow<Deferred<List<GitLabMergeRequestTimelineItemViewModel>>?>(null)
private var loadingRequestState = MutableStateFlow<Flow<List<GitLabMergeRequestTimelineItemViewModel>>?>(null)
private val discussionsDataProvider = GitLabMergeRequestDiscussionsModelImpl(cs, connection, mr)
@OptIn(ExperimentalCoroutinesApi::class)
override val timelineLoadingFlow: Flow<LoadingState?> =
loadingRequestState.transform {
if (it == null) {
loadingRequestState.transformLatest { itemsFlow ->
if (itemsFlow == null) {
emit(null)
return@transform
return@transformLatest
}
emit(LoadingState.Loading)
val result = try {
val result = it.await()
LoadingState.Result(result)
// so it's not a supervisor
coroutineScope {
itemsFlow.catch {
emit(LoadingState.Error(it))
}.collectLatest {
emit(LoadingState.Result(it))
}
}
catch (e: Exception) {
LoadingState.Error(e)
}
catch (ce: CancellationException) {
throw ce
}
emit(result)
}
@RequiresEdt
override fun startLoading() {
loadingRequestState.update {
it ?: requestItemsAsync()
it ?: createItemsFlow()
}
}
private fun requestItemsAsync(): Deferred<List<GitLabMergeRequestTimelineItemViewModel>> =
cs.async {
val api = connection.apiClient
val project = connection.repo.repository
private fun createItemsFlow(): Flow<List<GitLabMergeRequestTimelineItemViewModel>> {
val api = connection.apiClient
val project = connection.repo.repository
val result = ConcurrentSkipListSet(Comparator.comparing(GitLabMergeRequestTimelineItemViewModel::date))
val discussionsFlow = discussionsDataProvider.userDiscussions.mapScoped { discussions ->
val discussionsScope = this
discussions.map {
GitLabMergeRequestTimelineItemViewModel.Discussion(discussionsScope, it)
}
}
launch {
val simpleEventsFlow: Flow<List<GitLabMergeRequestTimelineItemViewModel.Immutable>> =
channelFlow {
launch {
api.loadAllMergeRequestDiscussions(project, mr).collect { discussions ->
result.addAll(discussions.filter {
it.notes.isNotEmpty()
}.map {
if (it.notes.first().system) {
GitLabMergeRequestTimelineItemViewModel.SystemDiscussion(it)
}
else {
GitLabMergeRequestTimelineItemViewModel.Discussion(cs, LoadedGitLabDiscussion(cs, connection, it))
}
})
discussionsDataProvider.systemDiscussions.collect { discussions ->
send(discussions.map { GitLabMergeRequestTimelineItemViewModel.SystemDiscussion(it) })
}
}
launch {
api.loadMergeRequestStateEvents(project, mr).body().let { events ->
result.addAll(events.map { GitLabMergeRequestTimelineItemViewModel.StateEvent(it) })
events.map { GitLabMergeRequestTimelineItemViewModel.StateEvent(it) }
}.also {
send(it)
}
}
launch {
api.loadMergeRequestStateEvents(project, mr).body().let { events ->
events.map { GitLabMergeRequestTimelineItemViewModel.StateEvent(it) }
}.also {
send(it)
}
}
launch {
api.loadMergeRequestLabelEvents(project, mr).body().let { events ->
result.addAll(events.map { GitLabMergeRequestTimelineItemViewModel.LabelEvent(it) })
events.map { GitLabMergeRequestTimelineItemViewModel.LabelEvent(it) }
}.also {
send(it)
}
}
launch {
api.loadMergeRequestMilestoneEvents(project, mr).body().let { events ->
result.addAll(events.map { GitLabMergeRequestTimelineItemViewModel.MilestoneEvent(it) })
events.map { GitLabMergeRequestTimelineItemViewModel.MilestoneEvent(it) }
}.also {
send(it)
}
}
}.join()
}.flowOn(Dispatchers.IO)
result.toList()
return combine(discussionsFlow, simpleEventsFlow) { discussions, simpleEvents ->
TreeSet(Comparator.comparing(GitLabMergeRequestTimelineItemViewModel::date)).apply {
addAll(simpleEvents)
addAll(discussions)
}.toList()
}
}
@RequiresEdt
override fun reset() {
loadingRequestState.update {
it?.cancel()
null
}
loadingRequestState.value = null
}
}