mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[gitlab] ability to resolve MR discussions from timeline
GitOrigin-RevId: 73d790c6024042500503a67164d9609a136a526c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9db486be3d
commit
02af04d951
@@ -0,0 +1,7 @@
|
||||
mutation($discussionId: DiscussionID!, $resolved: Boolean!) {
|
||||
discussionToggleResolve(input: {id: $discussionId, resolve: $resolved}) {
|
||||
discussion {
|
||||
...discussion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@ object GitLabGQLQueries {
|
||||
const val getProjectMembers = "graphql/query/getProjectMembers.graphql"
|
||||
const val getProjectLabels = "graphql/query/getProjectLabels.graphql"
|
||||
const val getMergeRequestDiscussions = "graphql/query/getMergeRequestDiscussions.graphql"
|
||||
const val toggleMergeRequestDiscussionResolve = "graphql/query/toggleMergeRequestDiscussionResolve.graphql"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.asParameters
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
||||
private class DiscussionConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<GitLabDiscussionDTO>)
|
||||
: GraphQLConnectionDTO<GitLabDiscussionDTO>(pageInfo, nodes)
|
||||
|
||||
suspend fun GitLabApi.changeMergeRequestDiscussionResolve(
|
||||
project: GitLabProjectCoordinates,
|
||||
discussionId: String,
|
||||
resolved: Boolean
|
||||
): HttpResponse<out GitLabDiscussionDTO?> {
|
||||
val parameters = mapOf(
|
||||
"discussionId" to discussionId,
|
||||
"resolved" to resolved
|
||||
)
|
||||
val request = gqlQuery(project.serverPath.gqlApiUri, GitLabGQLQueries.toggleMergeRequestDiscussionResolve, parameters)
|
||||
return loadGQLResponse(request, GitLabDiscussionDTO::class.java, "discussionToggleResolve", "discussion")
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
// Copyright 2000-2022 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.asParameters
|
||||
import com.intellij.collaboration.api.dto.GraphQLConnectionDTO
|
||||
import com.intellij.collaboration.api.dto.GraphQLCursorPageInfoDTO
|
||||
import com.intellij.collaboration.api.json.loadJsonList
|
||||
import com.intellij.collaboration.api.page.ApiPageUtil
|
||||
import com.intellij.collaboration.util.resolveRelative
|
||||
import com.intellij.collaboration.util.withQuery
|
||||
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
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionDTO
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabResourceLabelEventDTO
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabResourceMilestoneEventDTO
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabResourceStateEventDTO
|
||||
@@ -28,20 +21,6 @@ suspend fun GitLabApi.loadMergeRequests(project: GitLabProjectCoordinates,
|
||||
return loadJsonList(request)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private class DiscussionConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<GitLabDiscussionDTO>)
|
||||
: GraphQLConnectionDTO<GitLabDiscussionDTO>(pageInfo, nodes)
|
||||
|
||||
suspend fun GitLabApi.loadMergeRequestStateEvents(project: GitLabProjectCoordinates,
|
||||
mr: GitLabMergeRequestId)
|
||||
: HttpResponse<out List<GitLabResourceStateEventDTO>> {
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
// 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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.util.childScope
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabProjectConnection
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionDTO
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabNoteDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.request.changeMergeRequestDiscussionResolve
|
||||
import java.util.*
|
||||
|
||||
interface GitLabDiscussion {
|
||||
val createdAt: Date
|
||||
val notes: Flow<List<GitLabNoteDTO>>
|
||||
|
||||
val canResolve: Boolean
|
||||
val resolved: Flow<Boolean>
|
||||
|
||||
suspend fun changeResolvedState()
|
||||
}
|
||||
|
||||
class LoadedGitLabDiscussion(
|
||||
discussion: GitLabDiscussionDTO
|
||||
parentCs: CoroutineScope,
|
||||
private val connection: GitLabProjectConnection,
|
||||
private val discussion: GitLabDiscussionDTO
|
||||
) : GitLabDiscussion {
|
||||
init {
|
||||
require(discussion.notes.isNotEmpty()) { "Discussion with empty notes" }
|
||||
@@ -21,5 +37,31 @@ class LoadedGitLabDiscussion(
|
||||
|
||||
override val createdAt: Date = discussion.createdAt
|
||||
|
||||
override val notes: Flow<List<GitLabNoteDTO>> = MutableStateFlow(discussion.notes)
|
||||
private val cs = parentCs.childScope(CoroutineExceptionHandler { _, e ->
|
||||
logger<GitLabDiscussion>().info(e.localizedMessage)
|
||||
})
|
||||
|
||||
private val operationsGuard = Mutex()
|
||||
|
||||
private val _notes = MutableStateFlow(discussion.notes)
|
||||
override val notes: Flow<List<GitLabNoteDTO>> = _notes.asStateFlow()
|
||||
|
||||
private val firstNote = notes.map { it.first() }
|
||||
|
||||
// a little cheat that greatly simplifies the implementation
|
||||
override val canResolve: Boolean = discussion.notes.first().let { it.resolvable && it.userPermissions.resolveNote }
|
||||
override val resolved: Flow<Boolean> = firstNote.map { it.resolved }
|
||||
|
||||
override suspend fun changeResolvedState() {
|
||||
withContext(cs.coroutineContext) {
|
||||
operationsGuard.withLock {
|
||||
val resolved = resolved.first()
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
connection.apiClient
|
||||
.changeMergeRequestDiscussionResolve(connection.repo.repository, discussion.id, !resolved).body()!!
|
||||
}
|
||||
_notes.value = result.notes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.ui.comment
|
||||
|
||||
import com.intellij.util.childScope
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabDiscussion
|
||||
import java.util.*
|
||||
|
||||
interface GitLabMergeRequestDiscussionResolveViewModel {
|
||||
val resolved: Flow<Boolean>
|
||||
val busy: Flow<Boolean>
|
||||
|
||||
fun changeResolvedState()
|
||||
}
|
||||
|
||||
class GitLabMergeRequestDiscussionResolveViewModelImpl(parentCs: CoroutineScope, private val discussion: GitLabDiscussion)
|
||||
: GitLabMergeRequestDiscussionResolveViewModel {
|
||||
private val cs = parentCs.childScope()
|
||||
|
||||
override val resolved: Flow<Boolean> = discussion.resolved
|
||||
|
||||
private val currentTaskKey = MutableStateFlow<UUID?>(null)
|
||||
override val busy: Flow<Boolean> = currentTaskKey.map { it !== null }
|
||||
|
||||
override fun changeResolvedState() {
|
||||
cs.launch {
|
||||
val key = UUID.randomUUID()
|
||||
// other task in progress
|
||||
if (!currentTaskKey.compareAndSet(null, key)) return@launch
|
||||
try {
|
||||
discussion.changeResolvedState()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
//TODO: handle???
|
||||
}
|
||||
finally {
|
||||
currentTaskKey.compareAndSet(key, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.GitLabMergeRequestDiscussionResolveViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.GitLabMergeRequestNoteViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.timeline.GitLabMergeRequestTimelineUIUtil.createTitleTextPane
|
||||
import javax.swing.JComponent
|
||||
@@ -121,13 +122,39 @@ object GitLabMergeRequestTimelineDiscussionComponentFactory {
|
||||
})
|
||||
}
|
||||
|
||||
return HorizontalListPanel(Replies.ActionsFolded.HORIZONTAL_GAP).apply {
|
||||
val repliesActions = HorizontalListPanel(Replies.ActionsFolded.HORIZONTAL_GAP).apply {
|
||||
add(authorsLabel)
|
||||
add(repliesLink)
|
||||
add(lastReplyDateLabel)
|
||||
}.apply {
|
||||
bindVisibility(cs, item.replies.map { it.isNotEmpty() })
|
||||
}
|
||||
return HorizontalListPanel(Replies.ActionsFolded.HORIZONTAL_GROUP_GAP).apply {
|
||||
add(repliesActions)
|
||||
|
||||
item.resolvedVm?.let {
|
||||
createUnResolveLink(cs, it).also(::add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUnResolveLink(cs: CoroutineScope,
|
||||
vm: GitLabMergeRequestDiscussionResolveViewModel): LinkLabel<Any> =
|
||||
LinkLabel<Any>("", null) { _, _ ->
|
||||
vm.changeResolvedState()
|
||||
}.apply {
|
||||
isFocusable = true
|
||||
bindDisabled(cs, vm.busy)
|
||||
bindText(cs, vm.resolved.map { resolved ->
|
||||
if (resolved) {
|
||||
CollaborationToolsBundle.message("review.comments.unresolve.action")
|
||||
}
|
||||
else {
|
||||
CollaborationToolsBundle.message("review.comments.resolve.action")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun createNoteItem(cs: CoroutineScope,
|
||||
avatarIconsProvider: IconsProvider<GitLabUserDTO>,
|
||||
vm: GitLabMergeRequestNoteViewModel): JComponent {
|
||||
|
||||
@@ -8,6 +8,8 @@ import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabNoteDTO
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabDiscussion
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.GitLabMergeRequestDiscussionResolveViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.GitLabMergeRequestDiscussionResolveViewModelImpl
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.GitLabMergeRequestNoteViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.comment.LoadedGitLabMergeRequestNoteViewModel
|
||||
|
||||
@@ -19,12 +21,14 @@ interface GitLabMergeRequestTimelineDiscussionViewModel {
|
||||
|
||||
val repliesFolded: Flow<Boolean>
|
||||
|
||||
val resolvedVm: GitLabMergeRequestDiscussionResolveViewModel?
|
||||
|
||||
fun setRepliesFolded(folded: Boolean)
|
||||
}
|
||||
|
||||
class GitLabMergeRequestTimelineDiscussionViewModelImpl(
|
||||
parentCs: CoroutineScope,
|
||||
discussion: GitLabDiscussion
|
||||
private val discussion: GitLabDiscussion
|
||||
) : GitLabMergeRequestTimelineDiscussionViewModel {
|
||||
|
||||
private val cs = parentCs.childScope()
|
||||
@@ -43,6 +47,9 @@ class GitLabMergeRequestTimelineDiscussionViewModelImpl(
|
||||
|
||||
override val author: Flow<GitLabUserDTO> = mainNote.map { it.author }
|
||||
|
||||
override val resolvedVm: GitLabMergeRequestDiscussionResolveViewModel? =
|
||||
if (discussion.canResolve) GitLabMergeRequestDiscussionResolveViewModelImpl(cs, discussion) else null
|
||||
|
||||
override fun setRepliesFolded(folded: Boolean) {
|
||||
_repliesFolded.value = folded
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
GitLabMergeRequestTimelineItemViewModel.SystemDiscussion(it)
|
||||
}
|
||||
else {
|
||||
GitLabMergeRequestTimelineItemViewModel.Discussion(cs, LoadedGitLabDiscussion(it))
|
||||
GitLabMergeRequestTimelineItemViewModel.Discussion(cs, LoadedGitLabDiscussion(cs, connection, it))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user