[gitlab] Try to check permissions for multiple reviewers through work item widget

* #IDEA-327475

GitOrigin-RevId: bc39b2e7d8edc666cbc64c7f280d95165f53ae90
This commit is contained in:
Pavel Gromov
2023-11-29 13:09:10 +01:00
committed by intellij-monorepo-bot
parent a4b9906d0e
commit bae92e77ae
10 changed files with 164 additions and 18 deletions

View File

@@ -0,0 +1,9 @@
fragment workItem on WorkItem {
workItemType {
name
}
widgets {
__typename
...workItemWidgetAssignees
}
}

View File

@@ -0,0 +1,3 @@
fragment workItemWidgetAssignees on WorkItemWidgetAssignees {
allowsMultipleAssignees
}

View File

@@ -0,0 +1,12 @@
query($fullPath: ID!, $pageSize: Int = 100, $cursor: String) {
project(fullPath: $fullPath) {
workItems(first: $pageSize, after: $cursor) {
nodes {
...workItem
}
pageInfo {
...pageInfo
}
}
}
}

View File

@@ -17,6 +17,8 @@ enum class GitLabGQLQuery(val filePath: String) {
GET_PROJECT_LABELS("graphql/query/getProjectLabels.graphql"),
@SinceGitLab("12.0")
GET_PROJECT_REPOSITORY("graphql/query/getProjectRepository.graphql"),
@SinceGitLab("15.2")
GET_PROJECT_WORK_ITEMS("graphql/query/getProjectWidgets.graphql"),
@SinceGitLab("13.0")
GET_MEMBER_PROJECTS("graphql/query/getMemberProjects.graphql"),

View File

@@ -0,0 +1,59 @@
// 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.api.dto
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.intellij.collaboration.api.dto.GraphQLFragment
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO.GitLabWidgetDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO.GitLabWidgetDTO.*
@SinceGitLab("14.8")
@GraphQLFragment("graphql/fragment/workItem.graphql")
data class GitLabWorkItemDTO(
val workItemType: WorkItemType,
val widgets: List<GitLabWidgetDTO>
) {
data class WorkItemType(val name: String) {
companion object {
const val ISSUE_TYPE = "Issue"
}
}
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "__typename",
visible = false,
defaultImpl = GitLabWidgetDTO::class
)
@JsonSubTypes(
JsonSubTypes.Type(name = "WorkItemWidgetAssignees", value = WorkItemWidgetAssignees::class),
JsonSubTypes.Type(name = "WorkItemWidgetAwardEmoji", value = WorkItemWidgetAwardEmoji::class),
JsonSubTypes.Type(name = "WorkItemWidgetCurrentUserTodos", value = WorkItemWidgetCurrentUserTodos::class),
JsonSubTypes.Type(name = "WorkItemWidgetDescription", value = WorkItemWidgetDescription::class),
JsonSubTypes.Type(name = "WorkItemWidgetHierarchy", value = WorkItemWidgetHierarchy::class),
JsonSubTypes.Type(name = "WorkItemWidgetLabels", value = WorkItemWidgetLabels::class),
JsonSubTypes.Type(name = "WorkItemWidgetLinkedItems", value = WorkItemWidgetLinkedItems::class),
JsonSubTypes.Type(name = "WorkItemWidgetMilestone", value = WorkItemWidgetMilestone::class),
JsonSubTypes.Type(name = "WorkItemWidgetNotes", value = WorkItemWidgetNotes::class),
JsonSubTypes.Type(name = "WorkItemWidgetNotifications", value = WorkItemWidgetNotifications::class),
JsonSubTypes.Type(name = "WorkItemWidgetStartAndDueDate", value = WorkItemWidgetStartAndDueDate::class),
)
@SinceGitLab("15.1")
interface GitLabWidgetDTO {
@SinceGitLab("15.2")
@GraphQLFragment("graphql/fragment/workItemWidgetAssignees.graphql")
data class WorkItemWidgetAssignees(val allowsMultipleAssignees: Boolean) : GitLabWidgetDTO
class WorkItemWidgetAwardEmoji : GitLabWidgetDTO
class WorkItemWidgetCurrentUserTodos : GitLabWidgetDTO
class WorkItemWidgetDescription : GitLabWidgetDTO
class WorkItemWidgetHierarchy : GitLabWidgetDTO
class WorkItemWidgetLabels : GitLabWidgetDTO
class WorkItemWidgetLinkedItems : GitLabWidgetDTO
class WorkItemWidgetMilestone : GitLabWidgetDTO
class WorkItemWidgetNotes : GitLabWidgetDTO
class WorkItemWidgetNotifications : GitLabWidgetDTO
class WorkItemWidgetStartAndDueDate : GitLabWidgetDTO
}
}

View File

@@ -17,6 +17,7 @@ import org.jetbrains.plugins.gitlab.api.dto.GitLabLabelDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabNamespaceRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabRepositoryDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestDTO
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import java.net.URI
@@ -34,6 +35,18 @@ fun GitLabApi.GraphQL.createAllProjectLabelsFlow(project: GitLabProjectCoordinat
}
}.map { it.nodes }
@SinceGitLab("15.2")
fun GitLabApi.GraphQL.createAllWorkItemsFlow(project: GitLabProjectCoordinates): Flow<List<GitLabWorkItemDTO>> =
ApiPageUtil.createGQLPagesFlow { page ->
val parameters = page.asParameters() + mapOf(
"fullPath" to project.projectPath.fullPath()
)
val request = gitLabQuery(GitLabGQLQuery.GET_PROJECT_WORK_ITEMS, parameters)
withErrorStats(GitLabGQLQuery.GET_PROJECT_WORK_ITEMS) {
loadResponse<WorkItemConnection>(request, "project", "workItems").body()
}
}.map { it.nodes }
@SinceGitLab("7.0", note = "No exact version")
fun getProjectUsersURI(project: GitLabProjectCoordinates) = project.restApiUri.resolveRelative("users")
@@ -91,6 +104,9 @@ suspend fun GitLabApi.GraphQL.createMergeRequest(
private class LabelConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<GitLabLabelDTO>)
: GraphQLConnectionDTO<GitLabLabelDTO>(pageInfo, nodes)
private class WorkItemConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<GitLabWorkItemDTO>)
: GraphQLConnectionDTO<GitLabWorkItemDTO>(pageInfo, nodes)
private class GitLabCreateMergeRequestResult(
mergeRequest: GitLabMergeRequestDTO,
errors: List<String>?,

View File

@@ -14,6 +14,8 @@ import org.jetbrains.plugins.gitlab.api.*
import org.jetbrains.plugins.gitlab.api.data.GitLabPlan
import org.jetbrains.plugins.gitlab.api.dto.GitLabLabelDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO.GitLabWidgetDTO.WorkItemWidgetAssignees
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO.WorkItemType
import org.jetbrains.plugins.gitlab.api.request.*
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestDTO
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequest
@@ -31,7 +33,7 @@ interface GitLabProject {
val labels: SharedFlow<Result<List<GitLabLabelDTO>>>
val members: SharedFlow<Result<List<GitLabUserDTO>>>
val defaultBranch: Deferred<String>
val plan: Deferred<GitLabPlan?>
val allowsMultipleReviewers: SharedFlow<Boolean>
/**
* Creates a merge request on the GitLab server and returns a DTO containing the merge request
@@ -81,13 +83,30 @@ class GitLabLazyProject(
projectRepository.rootRef
}
override val plan: Deferred<GitLabPlan?> = cs.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
private val plan: Deferred<GitLabPlan?> = cs.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
runCatchingUser {
val namespace = api.rest.getProjectNamespace(projectMapping.repository.projectPath.owner).body()
namespace?.plan
}.getOrNull()
}
// TODO: Change the implementation after adding `allowsMultipleReviewers` field to the API
// https://gitlab.com/gitlab-org/gitlab/-/issues/431829
override val allowsMultipleReviewers: SharedFlow<Boolean> = channelFlow {
val glPlan = plan.await()
if (glPlan != null) {
send(glPlan != GitLabPlan.FREE)
return@channelFlow
}
if (glMetadata != null && glMetadata.version >= GitLabVersion(15, 2)) {
send(getAllowsMultipleAssigneesPropertyFromIssueWidget())
return@channelFlow
}
send(false)
}.modelFlow(parentCs, LOG)
@Throws(GitLabGraphQLMutationException::class)
override suspend fun createMergeRequestAndAwaitCompletion(sourceBranch: String, targetBranch: String, title: String): GitLabMergeRequestDTO {
return withContext(cs.coroutineContext + Dispatchers.IO) {
@@ -127,6 +146,29 @@ class GitLabLazyProject(
}
}
private suspend fun getAllowsMultipleAssigneesPropertyFromIssueWidget(): Boolean {
val widgetAssignees: WorkItemWidgetAssignees? = resultListFlow {
api.graphQL.createAllWorkItemsFlow(projectMapping.repository)
}.transformWhile { resultedWorkItems ->
val items = resultedWorkItems.getOrNull() ?: return@transformWhile false
val widget = items.find { workItem -> workItem.workItemType.name == WorkItemType.ISSUE_TYPE }
?.widgets
?.asSequence()
?.filterIsInstance<WorkItemWidgetAssignees>()
?.first()
if (widget != null) {
emit(widget)
return@transformWhile false
}
else {
return@transformWhile true
}
}.firstOrNull()
return widgetAssignees?.allowsMultipleAssignees ?: false
}
private fun <T> resultListFlow(flowProvider: () -> Flow<List<T>>): Flow<Result<List<T>>> = channelFlow<Result<List<T>>> {
runCatchingUser {
val loadedItems = mutableListOf<T>()

View File

@@ -25,7 +25,6 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.future.await
import org.jetbrains.plugins.gitlab.GitLabProjectsManager
import org.jetbrains.plugins.gitlab.api.data.GitLabPlan
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestReviewersUtil
@@ -40,7 +39,7 @@ internal interface GitLabMergeRequestCreateViewModel {
val isBusy: Flow<Boolean>
val plan: Deferred<GitLabPlan?>
val allowsMultipleReviewers: Flow<Boolean>
val branchState: Flow<BranchState?>
val existingMergeRequest: Flow<String?>
@@ -78,7 +77,7 @@ internal class GitLabMergeRequestCreateViewModelImpl(
override val isBusy: Flow<Boolean> = taskLauncher.busy
override val plan: Deferred<GitLabPlan?> = projectData.plan
override val allowsMultipleReviewers: Flow<Boolean> = projectData.allowsMultipleReviewers
private val listenableProgressIndicator = ListenableProgressIndicator()
override val creatingProgressText: Flow<String?> = callbackFlow {
@@ -168,11 +167,12 @@ internal class GitLabMergeRequestCreateViewModelImpl(
override fun adjustReviewer(point: RelativePoint) {
cs.launchNow(Dispatchers.Main) {
val allowsMultipleReviewers = allowsMultipleReviewers.first()
val originalReviewersIds = adjustedReviewers.value.mapTo(mutableSetOf<String>(), GitLabUserDTO::id)
val updatedReviewers = if (plan.await() == GitLabPlan.FREE)
GitLabMergeRequestReviewersUtil.selectReviewer(point, originalReviewersIds, potentialReviewers, avatarIconProvider)
else
val updatedReviewers = if (allowsMultipleReviewers == true)
GitLabMergeRequestReviewersUtil.selectReviewers(point, originalReviewersIds, potentialReviewers, avatarIconProvider)
else
GitLabMergeRequestReviewersUtil.selectReviewer(point, originalReviewersIds, potentialReviewers, avatarIconProvider)
updatedReviewers ?: return@launchNow
_adjustedReviewers.value = updatedReviewers

View File

@@ -21,7 +21,6 @@ import com.intellij.ui.awt.RelativePoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.data.GitLabPlan
import org.jetbrains.plugins.gitlab.api.dto.GitLabCiJobDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabReviewerDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
@@ -39,7 +38,7 @@ import org.jetbrains.plugins.gitlab.util.GitLabBundle
internal interface GitLabMergeRequestReviewFlowViewModel : CodeReviewFlowViewModel<GitLabReviewerDTO> {
val isBusy: Flow<Boolean>
val plan: Deferred<GitLabPlan?>
val allowsMultipleReviewers: Flow<Boolean>
val currentUser: GitLabUserDTO
val author: GitLabUserDTO
@@ -105,7 +104,7 @@ internal class GitLabMergeRequestReviewFlowViewModelImpl(
override val isBusy: Flow<Boolean> = taskLauncher.busy
override val plan: Deferred<GitLabPlan?> = projectData.plan
override val allowsMultipleReviewers: Flow<Boolean> = projectData.allowsMultipleReviewers
override val author: GitLabUserDTO = mergeRequest.author
@@ -251,11 +250,12 @@ internal class GitLabMergeRequestReviewFlowViewModelImpl(
override fun adjustReviewers(point: RelativePoint) {
scope.launchNow(Dispatchers.Main) {
val allowsMultipleReviewers = allowsMultipleReviewers.first()
val originalReviewersIds = reviewers.value.mapTo(mutableSetOf<String>(), GitLabUserDTO::id)
val updatedReviewers = if (plan.await() == GitLabPlan.FREE)
GitLabMergeRequestReviewersUtil.selectReviewer(point, originalReviewersIds, potentialReviewers, avatarIconsProvider)
else
val updatedReviewers = if (allowsMultipleReviewers == true)
GitLabMergeRequestReviewersUtil.selectReviewers(point, originalReviewersIds, potentialReviewers, avatarIconsProvider)
else
GitLabMergeRequestReviewersUtil.selectReviewer(point, originalReviewersIds, potentialReviewers, avatarIconsProvider)
updatedReviewers ?: return@launchNow
setReviewers(updatedReviewers)
@@ -264,11 +264,12 @@ internal class GitLabMergeRequestReviewFlowViewModelImpl(
@SinceGitLab("13.8")
override fun setMyselfAsReviewer() = runAction {
if (plan.await() == GitLabPlan.FREE) {
mergeRequest.setReviewers(listOf(currentUser))
val allowsMultipleReviewers = allowsMultipleReviewers.first()
if (allowsMultipleReviewers == true) {
mergeRequest.setReviewers(listOf(currentUser) + reviewers.value)
}
else {
mergeRequest.setReviewers(listOf(currentUser) + reviewers.value)
mergeRequest.setReviewers(listOf(currentUser))
}
}

View File

@@ -38,7 +38,7 @@ internal object GitLabStatistics {
//endregion
//region Counters
private val COUNTERS_GROUP = EventLogGroup("vcs.gitlab.counters", version = 16)
private val COUNTERS_GROUP = EventLogGroup("vcs.gitlab.counters", version = 17)
/**
* Server metadata was fetched
@@ -289,6 +289,7 @@ enum class GitLabApiRequestName {
GQL_GET_MERGE_REQUEST_DISCUSSIONS,
GQL_GET_PROJECT_LABELS,
GQL_GET_PROJECT_REPOSITORY,
GQL_GET_PROJECT_WORK_ITEMS,
GQL_GET_MEMBER_PROJECTS,
GQL_TOGGLE_MERGE_REQUEST_DISCUSSION_RESOLVE,
GQL_CREATE_NOTE,
@@ -314,6 +315,7 @@ enum class GitLabApiRequestName {
GitLabGQLQuery.GET_MERGE_REQUEST_DISCUSSIONS -> GQL_GET_MERGE_REQUEST_DISCUSSIONS
GitLabGQLQuery.GET_PROJECT_LABELS -> GQL_GET_PROJECT_LABELS
GitLabGQLQuery.GET_PROJECT_REPOSITORY -> GQL_GET_PROJECT_REPOSITORY
GitLabGQLQuery.GET_PROJECT_WORK_ITEMS -> GQL_GET_PROJECT_WORK_ITEMS
GitLabGQLQuery.GET_MEMBER_PROJECTS -> GQL_GET_MEMBER_PROJECTS
GitLabGQLQuery.TOGGLE_MERGE_REQUEST_DISCUSSION_RESOLVE -> GQL_TOGGLE_MERGE_REQUEST_DISCUSSION_RESOLVE
GitLabGQLQuery.CREATE_NOTE -> GQL_CREATE_NOTE