mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[gitlab]: ability to set labels for new merge requests
IJPL-84272 GitOrigin-RevId: a1f80dd23994c28be6963b33ba01bc1b8d699977
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a17bd75480
commit
8e0f5ef8fb
@@ -265,7 +265,15 @@ f:com.intellij.collaboration.api.json.JsonHttpApiHelperKt
|
||||
- f:attachSearch(javax.swing.JList,com.intellij.ui.SearchTextField,kotlin.jvm.functions.Function1):V
|
||||
- f:cleanupUrl(java.lang.String):java.lang.String
|
||||
- f:createTagLabel(com.intellij.collaboration.ui.SingleValueModel):javax.swing.JComponent
|
||||
- f:createTagLabel(com.intellij.collaboration.ui.SingleValueModel,java.awt.Color):javax.swing.JComponent
|
||||
- f:createTagLabel(com.intellij.collaboration.ui.SingleValueModel,java.awt.Color,java.awt.Color):javax.swing.JComponent
|
||||
- f:createTagLabel(com.intellij.collaboration.ui.SingleValueModel,java.awt.Color,java.awt.Color,Z):javax.swing.JComponent
|
||||
- f:createTagLabel(java.lang.String):javax.swing.JComponent
|
||||
- f:createTagLabel(java.lang.String,java.awt.Color):javax.swing.JComponent
|
||||
- f:createTagLabel(java.lang.String,java.awt.Color,java.awt.Color):javax.swing.JComponent
|
||||
- f:createTagLabel(java.lang.String,java.awt.Color,java.awt.Color,Z):javax.swing.JComponent
|
||||
- bs:createTagLabel$default(com.intellij.collaboration.ui.CollaborationToolsUIUtil,com.intellij.collaboration.ui.SingleValueModel,java.awt.Color,java.awt.Color,Z,I,java.lang.Object):javax.swing.JComponent
|
||||
- bs:createTagLabel$default(com.intellij.collaboration.ui.CollaborationToolsUIUtil,java.lang.String,java.awt.Color,java.awt.Color,Z,I,java.lang.Object):javax.swing.JComponent
|
||||
- f:defaultButton(javax.swing.JButton):javax.swing.JButton
|
||||
- f:focusPanel(javax.swing.JComponent):V
|
||||
- f:getAnimatedLoadingIcon():javax.swing.Icon
|
||||
|
||||
+20
-5
@@ -308,20 +308,35 @@ object CollaborationToolsUIUtil {
|
||||
* A text label with a rounded rectangle as a background
|
||||
* To be used for various tags and badges
|
||||
*/
|
||||
fun createTagLabel(text: @Nls String): JComponent = createTagLabel(SingleValueModel(text))
|
||||
@JvmOverloads
|
||||
fun createTagLabel(
|
||||
text: @Nls String,
|
||||
textColor: Color = CodeReviewColorUtil.Review.stateForeground,
|
||||
backgroundColor: Color = CodeReviewColorUtil.Review.stateBackground,
|
||||
compact: Boolean = true,
|
||||
): JComponent = createTagLabel(SingleValueModel(text), textColor, backgroundColor, compact)
|
||||
|
||||
fun createTagLabel(model: SingleValueModel<@Nls String?>): JComponent =
|
||||
@JvmOverloads
|
||||
fun createTagLabel(
|
||||
model: SingleValueModel<@Nls String?>,
|
||||
textColor: Color = CodeReviewColorUtil.Review.stateForeground,
|
||||
backgroundColor: Color = CodeReviewColorUtil.Review.stateBackground,
|
||||
compact: Boolean = true,
|
||||
): JComponent =
|
||||
JLabel(model.value).apply {
|
||||
font = JBFont.small()
|
||||
foreground = CodeReviewColorUtil.Review.stateForeground
|
||||
if (compact) {
|
||||
font = JBFont.small()
|
||||
}
|
||||
foreground = textColor
|
||||
border = JBUI.Borders.empty(0, 4)
|
||||
model.addListener {
|
||||
text = it
|
||||
}
|
||||
}.let {
|
||||
BackgroundRoundedPanel(4, SingleComponentCenteringLayout()).apply {
|
||||
fillBorder = false
|
||||
border = JBUI.Borders.empty()
|
||||
background = CodeReviewColorUtil.Review.stateBackground
|
||||
background = backgroundColor
|
||||
add(it)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -7,7 +7,6 @@ import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.util.ui.InlineIconButton
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.JBUI.Panels.simplePanel
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.WrapLayout
|
||||
import com.intellij.util.ui.launchOnShow
|
||||
@@ -108,7 +107,10 @@ object LabeledListComponentsFactory {
|
||||
}
|
||||
val lastItem = newList.last()
|
||||
// attach controls to the last items so they are moved to the next line together
|
||||
val itemWithControls = simplePanel(itemComponentFactory(lastItem)).andTransparent().addToRight(editButton)
|
||||
val itemWithControls = HorizontalListPanel().apply {
|
||||
add(itemComponentFactory(lastItem))
|
||||
add(editButton)
|
||||
}
|
||||
add(itemWithControls)
|
||||
}
|
||||
revalidate()
|
||||
|
||||
+14
-2
@@ -2,17 +2,25 @@
|
||||
package com.intellij.ui.components.panels
|
||||
|
||||
import com.intellij.util.ui.GraphicsUtil
|
||||
import com.intellij.util.ui.JBInsets
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.awt.Graphics
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.LayoutManager
|
||||
import javax.swing.JPanel
|
||||
import kotlin.properties.Delegates.observable
|
||||
|
||||
//TODO: provide an external API
|
||||
@ApiStatus.Internal
|
||||
open class BackgroundRoundedPanel(private val arcSize: Int, layoutManager: LayoutManager? = null) : JPanel(layoutManager) {
|
||||
var fillBorder: Boolean by observable(true) { _, _, _ -> repaint() }
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
super.isOpaque = false
|
||||
}
|
||||
|
||||
final override fun setOpaque(isOpaque: Boolean) {
|
||||
// this panel is always non-opaque because of rounding
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
@@ -20,9 +28,13 @@ open class BackgroundRoundedPanel(private val arcSize: Int, layoutManager: Layou
|
||||
//java.awt.Graphics2D#clip provides worse painting result than explicit rounded rectangle painting
|
||||
val g2 = g.create() as Graphics2D
|
||||
try {
|
||||
val rect = bounds
|
||||
if (!fillBorder) {
|
||||
JBInsets.removeFrom(rect, insets)
|
||||
}
|
||||
GraphicsUtil.setupAAPainting(g2)
|
||||
g2.color = background
|
||||
g2.fillRoundRect(0, 0, width, height, arcSize, arcSize)
|
||||
g2.fillRoundRect(rect.x, rect.y, rect.width, rect.height, arcSize, arcSize)
|
||||
}
|
||||
finally {
|
||||
g2.dispose()
|
||||
|
||||
@@ -134,6 +134,8 @@ merge.request.create.no.reviewers=No reviewers
|
||||
merge.request.create.reviewers=Reviewers:
|
||||
merge.request.create.no.assignees=No assignees
|
||||
merge.request.create.assignees=Assignees:
|
||||
merge.request.create.no.labels=No labels
|
||||
merge.request.create.labels=Labels:
|
||||
merge.request.create.tab.title=New Merge Request at {0}
|
||||
merge.request.create.title.placeholder=Title
|
||||
merge.request.create.description.placeholder=Description
|
||||
|
||||
+2
@@ -49,6 +49,7 @@ suspend fun GitLabApi.Rest.createMergeRequest(
|
||||
description: String? = null,
|
||||
reviewerIds: List<String>? = null,
|
||||
assigneeIds: List<String>? = null,
|
||||
labels: List<String>? = null
|
||||
): HttpResponse<out GitLabMergeRequestShortRestDTO> {
|
||||
val uri = project.restApiUri
|
||||
.resolveRelative("merge_requests")
|
||||
@@ -59,6 +60,7 @@ suspend fun GitLabApi.Rest.createMergeRequest(
|
||||
"description" eq description
|
||||
"reviewer_ids" eq reviewerIds
|
||||
"assignee_ids" eq assigneeIds
|
||||
"labels" eq labels
|
||||
}
|
||||
val request = request(uri).POST(HttpRequest.BodyPublishers.noBody()).build()
|
||||
return withErrorStats(GitLabApiRequestName.REST_CREATE_MERGE_REQUEST) {
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
// Copyright 2000-2026 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.openapi.util.NlsSafe
|
||||
|
||||
class GitLabLabel(
|
||||
val title: String,
|
||||
val title: @NlsSafe String,
|
||||
val colorHex: String,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
+5
-1
@@ -90,6 +90,7 @@ interface GitLabProject {
|
||||
description: String?,
|
||||
reviewers: List<GitLabUserDTO> = emptyList(),
|
||||
assignees: List<GitLabUserDTO> = emptyList(),
|
||||
labels: List<GitLabLabel> = emptyList(),
|
||||
): GitLabMergeRequestDTO
|
||||
|
||||
fun reloadData()
|
||||
@@ -181,10 +182,12 @@ class GitLabLazyProject(
|
||||
description: String?,
|
||||
reviewers: List<GitLabUserDTO>,
|
||||
assignees: List<GitLabUserDTO>,
|
||||
labels: List<GitLabLabel>,
|
||||
): GitLabMergeRequestDTO {
|
||||
return cs.async(Dispatchers.IO) {
|
||||
val reviewerIds = reviewers.nullize()?.map { GitLabGidData(it.id).guessRestId() }
|
||||
val assigneeIds = assignees.nullize()?.map { GitLabGidData(it.id).guessRestId() }
|
||||
val labelTitles = labels.nullize()?.map { it.title }
|
||||
val iid = api.rest.createMergeRequest(
|
||||
projectCoordinates,
|
||||
sourceBranch,
|
||||
@@ -192,7 +195,8 @@ class GitLabLazyProject(
|
||||
title,
|
||||
description,
|
||||
reviewerIds,
|
||||
assigneeIds
|
||||
assigneeIds,
|
||||
labelTitles
|
||||
).body().iid
|
||||
val attempts = GitLabRegistry.getRequestPollingAttempts()
|
||||
repeat(attempts) {
|
||||
|
||||
+1
@@ -146,6 +146,7 @@ internal object GitLabMergeRequestCreateComponentFactory {
|
||||
val lists = buildList {
|
||||
add(GitLabMergeRequestCreateMetadataComponentFactory.createAssigneesListPanelHandle(createVm))
|
||||
add(GitLabMergeRequestCreateMetadataComponentFactory.createReviewersListPanelHandle(createVm))
|
||||
add(GitLabMergeRequestCreateMetadataComponentFactory.createLabelsListPanelHandle(createVm))
|
||||
}
|
||||
|
||||
return LabeledListComponentsFactory.createGrid(lists)
|
||||
|
||||
+40
@@ -2,6 +2,7 @@
|
||||
package org.jetbrains.plugins.gitlab.mergerequest.ui.create
|
||||
|
||||
import com.intellij.collaboration.async.mapState
|
||||
import com.intellij.collaboration.ui.CollaborationToolsUIUtil
|
||||
import com.intellij.collaboration.ui.LabeledListComponentsFactory
|
||||
import com.intellij.collaboration.ui.codereview.avatar.Avatar
|
||||
import com.intellij.collaboration.ui.codereview.list.search.ShowDirection
|
||||
@@ -12,6 +13,7 @@ import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabLabel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.create.model.GitLabMergeRequestCreateViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestChoosersUtil
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabBundle
|
||||
@@ -97,6 +99,35 @@ internal object GitLabMergeRequestCreateMetadataComponentFactory {
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
fun createLabelsListPanelHandle(
|
||||
vm: GitLabMergeRequestCreateViewModel,
|
||||
): Pair<JComponent, JComponent> {
|
||||
val label = LabeledListComponentsFactory.createLabelPanel(
|
||||
vm.labels.mapState { it.isEmpty() },
|
||||
GitLabBundle.message("merge.request.create.no.labels"),
|
||||
GitLabBundle.message("merge.request.create.labels")
|
||||
)
|
||||
|
||||
val list = LabeledListComponentsFactory.createListPanel(
|
||||
vm.labels,
|
||||
{ comp, _ -> chooseLabels(comp, vm) },
|
||||
{ LabelLabel(it) }
|
||||
)
|
||||
|
||||
return label to list
|
||||
}
|
||||
|
||||
private suspend fun chooseLabels(
|
||||
parentComponent: JComponent,
|
||||
vm: GitLabMergeRequestCreateViewModel,
|
||||
) {
|
||||
val point = RelativePoint.getNorthEastOf(parentComponent)
|
||||
val currentLabels = vm.labels.value
|
||||
val potentialLabels = vm.projectLabels
|
||||
val newLabels = GitLabMergeRequestChoosersUtil.chooseLabels(point, currentLabels, potentialLabels, ShowDirection.ABOVE)
|
||||
vm.setLabels(newLabels)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
@@ -104,3 +135,12 @@ private fun UserLabel(user: GitLabUserDTO, avatarIconsProvider: IconsProvider<Gi
|
||||
JLabel(user.name, avatarIconsProvider.getIcon(user, Avatar.Sizes.BASE), SwingConstants.LEFT).apply {
|
||||
border = JBUI.Borders.empty(0, UIUtil.DEFAULT_HGAP / 2)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private fun LabelLabel(label: GitLabLabel): JComponent {
|
||||
val background = CollaborationToolsUIUtil.getLabelBackground(label.colorHex)
|
||||
val foreground = CollaborationToolsUIUtil.getLabelForeground(background)
|
||||
return CollaborationToolsUIUtil.createTagLabel(label.title, foreground, background, compact = false).apply {
|
||||
border = JBUI.Borders.empty(0, UIUtil.DEFAULT_HGAP / 2)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -44,6 +44,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
@@ -52,6 +53,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.plugins.gitlab.GitLabProjectsManager
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabLabel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestState
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabBundle
|
||||
@@ -82,12 +84,16 @@ internal interface GitLabMergeRequestCreateViewModel : CodeReviewTitleDescriptio
|
||||
val reviewers: StateFlow<List<GitLabUserDTO>>
|
||||
val assignees: StateFlow<List<GitLabUserDTO>>
|
||||
|
||||
val projectLabels: StateFlow<IncrementallyComputedValue<List<GitLabLabel>>>
|
||||
val labels: StateFlow<List<GitLabLabel>>
|
||||
|
||||
val openReviewTabAction: suspend (mrIid: String) -> Unit
|
||||
|
||||
fun updateBranchState(state: BranchState?)
|
||||
|
||||
fun setReviewers(reviewers: List<GitLabUserDTO>)
|
||||
fun setAssignees(assignees: List<GitLabUserDTO>)
|
||||
fun setLabels(labels: List<GitLabLabel>)
|
||||
|
||||
fun createMergeRequest()
|
||||
}
|
||||
@@ -203,6 +209,14 @@ internal class GitLabMergeRequestCreateViewModelImpl(
|
||||
private val _assignees: MutableStateFlow<List<GitLabUserDTO>> = MutableStateFlow(listOf())
|
||||
override val assignees: StateFlow<List<GitLabUserDTO>> = _assignees.asStateFlow()
|
||||
|
||||
override val projectLabels: StateFlow<IncrementallyComputedValue<List<GitLabLabel>>> =
|
||||
projectData.dataReloadSignal.withInitial(Unit).transformLatest {
|
||||
projectData.getLabelsBatches().collectIncrementallyTo(this)
|
||||
}.stateIn(cs, SharingStarted.Lazily, IncrementallyComputedValue.loading())
|
||||
|
||||
private val _labels: MutableStateFlow<List<GitLabLabel>> = MutableStateFlow(listOf())
|
||||
override val labels: StateFlow<List<GitLabLabel>> = _labels.asStateFlow()
|
||||
|
||||
private val _title: MutableStateFlow<String> = MutableStateFlow("")
|
||||
override val titleText: StateFlow<String> = _title.asStateFlow()
|
||||
|
||||
@@ -272,6 +286,10 @@ internal class GitLabMergeRequestCreateViewModelImpl(
|
||||
_assignees.value = assignees
|
||||
}
|
||||
|
||||
override fun setLabels(labels: List<GitLabLabel>) {
|
||||
_labels.value = labels
|
||||
}
|
||||
|
||||
override fun createMergeRequest() {
|
||||
taskLauncher.launch {
|
||||
GitLabStatistics.logMrCreationStarted(project)
|
||||
@@ -285,7 +303,8 @@ internal class GitLabMergeRequestCreateViewModelImpl(
|
||||
title = titleText.value.ifBlank { gitRemoteBranch.nameForRemoteOperations },
|
||||
description = descriptionText.value.ifBlank { null },
|
||||
reviewers = reviewers.value,
|
||||
assignees = assignees.value
|
||||
assignees = assignees.value,
|
||||
labels = labels.value
|
||||
)
|
||||
openReviewTabAction(mergeRequest.iid)
|
||||
onReviewCreated()
|
||||
|
||||
+13
@@ -44,6 +44,19 @@ internal object GitLabMergeRequestChoosersUtil {
|
||||
PopupConfig.DEFAULT.copy(showDirection = showDirection)
|
||||
)
|
||||
|
||||
suspend fun chooseLabels(
|
||||
point: RelativePoint,
|
||||
chosenLabels: List<GitLabLabel>,
|
||||
potentialLabels: StateFlow<IncrementallyComputedValue<List<GitLabLabel>>>,
|
||||
showDirection: ShowDirection = ShowDirection.BELOW,
|
||||
): List<GitLabLabel> = ChooserPopupUtil.showMultipleChooserPopupWithIncrementalLoading(
|
||||
point,
|
||||
chosenLabels,
|
||||
potentialLabels,
|
||||
presenter = getLabelPresenter(),
|
||||
PopupConfig.DEFAULT.copy(showDirection = showDirection)
|
||||
)
|
||||
|
||||
fun getUserPresenter(avatarIconsProvider: IconsProvider<GitLabUserDTO>): (GitLabUserDTO) -> PopupItemPresentation {
|
||||
return {
|
||||
PopupItemPresentation.Simple(
|
||||
|
||||
Reference in New Issue
Block a user