mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
[gh] Add an action to remove a branch after merge (IJPL-74161)
#IJPL-74161 Fixed GitOrigin-RevId: c9f7cb782593d07534e770736b96255bc6ebc45a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f61fd6c1cc
commit
7bb09ab6b1
@@ -1566,6 +1566,7 @@ f:com.intellij.collaboration.ui.codereview.diff.viewer.DiffViewerUtilKt
|
||||
- sf:bindSelectedIn(javax.swing.JCheckBox,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
|
||||
- sf:bindSelectedItemIn(com.intellij.ui.dsl.builder.Cell,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):com.intellij.ui.dsl.builder.Cell
|
||||
- sf:bindSelectedItemIn(javax.swing.ComboBoxModel,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
|
||||
- sf:bindText(javax.swing.JLabel,java.lang.String,kotlinx.coroutines.flow.Flow):V
|
||||
- sf:bindTextHtmlIn(javax.swing.JEditorPane,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.Flow):V
|
||||
- sf:bindTextIn(com.intellij.openapi.editor.Document,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
|
||||
- sf:bindTextIn(com.intellij.openapi.editor.Document,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.StateFlow,kotlin.jvm.functions.Function1):V
|
||||
|
||||
@@ -240,6 +240,12 @@ fun JEditorPane.bindTextHtmlIn(scope: CoroutineScope, textFlow: Flow<@Nls String
|
||||
}
|
||||
}
|
||||
|
||||
fun JLabel.bindText(debugName: String, textFlow: Flow<@Nls String>) {
|
||||
launchOnShow(debugName) {
|
||||
bindTextIn(this, textFlow)
|
||||
}
|
||||
}
|
||||
|
||||
fun JLabel.bindTextIn(scope: CoroutineScope, textFlow: Flow<@Nls String>) {
|
||||
scope.launch(start = CoroutineStart.UNDISPATCHED) {
|
||||
textFlow.collect {
|
||||
|
||||
@@ -16,9 +16,14 @@ fragment pullRequestInfo on PullRequest {
|
||||
...repository
|
||||
}
|
||||
|
||||
headRef {
|
||||
id
|
||||
}
|
||||
headRefName
|
||||
headRefOid
|
||||
headRepository {
|
||||
...repository
|
||||
}
|
||||
|
||||
viewerCanDeleteHeadRef
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mutation ($refId: ID!) {
|
||||
deleteRef(input: {refId: $refId}) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,10 @@
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
<action id="GitHub.Timeline.DeleteMergedBranch"
|
||||
class="org.jetbrains.plugins.github.pullrequest.action.GHPRDeleteMergedHeadAction"
|
||||
icon="AllIcons.General.Delete"/>
|
||||
|
||||
<action id="Github.Sync.Fork"
|
||||
class="org.jetbrains.plugins.github.GithubSyncForkAction"
|
||||
icon="AllIcons.Vcs.Vendors.Github">
|
||||
|
||||
@@ -323,6 +323,16 @@ pull.request.timeline.comment.suggested.changes.tooltip.different.branch=Unable
|
||||
pull.request.comment.suggested.changes.applying=Applying patch
|
||||
pull.request.comment.suggested.changes.resolving=Resolving thread
|
||||
pull.request.comment.suggested.changes.committing=Applying and committing changes
|
||||
|
||||
pullRequest.timeline.merged.title=Pull Request Successfully Merged
|
||||
pullRequest.timeline.merged.body.restore=Restore\u2026
|
||||
pullRequest.timeline.merged.body.delete=delete
|
||||
pullRequest.timeline.merged.body.withoutDelete={0} has been deleted. {1}
|
||||
pullRequest.timeline.merged.body.withDelete=You can now {0} {1}.
|
||||
|
||||
action.GitHub.Timeline.DeleteMergedBranch.text=Delete Branch
|
||||
action.GitHub.Timeline.DeleteMergedBranch.description=Delete the merged branch on GitHub
|
||||
|
||||
#branch widget
|
||||
pull.request.on.branch=\#{0,number,#} on {1}
|
||||
pull.request.on.branch.description=Pull request \#{0,number,#} exists for current branch {1}
|
||||
|
||||
@@ -21,6 +21,7 @@ object GHGQLQueries {
|
||||
const val pullRequestCommits = "graphql/query/pullRequestCommits.graphql"
|
||||
const val pullRequestMergeabilityData = "graphql/query/findPullRequestMergeability.graphql"
|
||||
const val pullRequestFiles = "graphql/query/pullRequestFiles.graphql"
|
||||
const val deleteRef = "graphql/query/deleteRef.graphql"
|
||||
const val markFileAsViewed = "graphql/query/markFileAsViewed.graphql"
|
||||
const val unmarkFileAsViewed = "graphql/query/unmarkFileAsViewed.graphql"
|
||||
const val createReview = "graphql/query/createReview.graphql"
|
||||
|
||||
@@ -195,6 +195,15 @@ object GHGQLRequests {
|
||||
}
|
||||
}
|
||||
|
||||
object Ref {
|
||||
fun delete(server: GithubServerPath, refId: String): GQLQuery<Unit> =
|
||||
GQLQuery.Parsed(
|
||||
server.toGraphQLUrl(), GHGQLQueries.deleteRef,
|
||||
mapOf("refId" to refId),
|
||||
Unit::class.java
|
||||
)
|
||||
}
|
||||
|
||||
object PullRequest {
|
||||
fun findOneId(repository: GHRepositoryCoordinates, number: Long): GQLQuery<GHPRIdentifier?> =
|
||||
GQLQuery.OptionalTraversedParsed(
|
||||
|
||||
+38
-27
@@ -9,47 +9,56 @@ import org.jetbrains.plugins.github.api.data.*
|
||||
import java.util.*
|
||||
|
||||
@GraphQLFragment("/graphql/fragment/pullRequestInfo.graphql")
|
||||
class GHPullRequest(id: String,
|
||||
url: String,
|
||||
number: Long,
|
||||
title: String,
|
||||
state: GHPullRequestState,
|
||||
isDraft: Boolean,
|
||||
author: GHActor?,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
@JsonProperty("assignees") assignees: GraphQLNodesDTO<GHUser>,
|
||||
@JsonProperty("labels") labels: GraphQLNodesDTO<GHLabel>?,
|
||||
@JsonProperty("reviewRequests") reviewRequests: GraphQLNodesDTO<GHPullRequestReviewRequest>?,
|
||||
@JsonProperty("reviewThreads") reviewThreads: GraphQLNodesDTO<ReviewThreadDetails>,
|
||||
@JsonProperty("reviews") reviews: GraphQLNodesDTO<GHPullRequestReview>?,
|
||||
val reviewDecision: GHPullRequestReviewDecision?,
|
||||
mergeable: GHPullRequestMergeableState,
|
||||
viewerCanUpdate: Boolean,
|
||||
viewerCanReact: Boolean,
|
||||
viewerDidAuthor: Boolean,
|
||||
@NlsSafe val body: String,
|
||||
val baseRefName: String,
|
||||
val baseRefOid: String,
|
||||
val baseRepository: GHRepository?,
|
||||
val headRefName: String,
|
||||
val headRefOid: String,
|
||||
val headRepository: GHRepository?,
|
||||
override val reactions: GHReactable.ReactionConnection)
|
||||
class GHPullRequest(
|
||||
id: String,
|
||||
url: String,
|
||||
number: Long,
|
||||
title: String,
|
||||
state: GHPullRequestState,
|
||||
isDraft: Boolean,
|
||||
author: GHActor?,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
@JsonProperty("assignees") assignees: GraphQLNodesDTO<GHUser>,
|
||||
@JsonProperty("labels") labels: GraphQLNodesDTO<GHLabel>?,
|
||||
@JsonProperty("reviewRequests") reviewRequests: GraphQLNodesDTO<GHPullRequestReviewRequest>?,
|
||||
@JsonProperty("reviewThreads") reviewThreads: GraphQLNodesDTO<ReviewThreadDetails>,
|
||||
@JsonProperty("reviews") reviews: GraphQLNodesDTO<GHPullRequestReview>?,
|
||||
val reviewDecision: GHPullRequestReviewDecision?,
|
||||
mergeable: GHPullRequestMergeableState,
|
||||
viewerCanUpdate: Boolean,
|
||||
viewerCanReact: Boolean,
|
||||
viewerDidAuthor: Boolean,
|
||||
val viewerCanDeleteHeadRef: Boolean,
|
||||
val body: @NlsSafe String,
|
||||
val baseRefName: String,
|
||||
val baseRefOid: String,
|
||||
val baseRepository: GHRepository?,
|
||||
private val headRef: HeadRef?,
|
||||
val headRefName: String,
|
||||
val headRefOid: String,
|
||||
val headRepository: GHRepository?,
|
||||
override val reactions: GHReactable.ReactionConnection,
|
||||
)
|
||||
: GHPullRequestShort(id, url, number, title, state, isDraft, author, createdAt, updatedAt,
|
||||
assignees, labels, reviewRequests, reviewThreads,
|
||||
reviews, mergeable, viewerCanUpdate, viewerCanReact, viewerDidAuthor, reactions) {
|
||||
val headRefId: String? get() = headRef?.id
|
||||
|
||||
data class HeadRef(val id: String?)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is GHPullRequest) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
if (viewerCanDeleteHeadRef != other.viewerCanDeleteHeadRef) return false
|
||||
if (reviewDecision != other.reviewDecision) return false
|
||||
if (body != other.body) return false
|
||||
if (baseRefName != other.baseRefName) return false
|
||||
if (baseRefOid != other.baseRefOid) return false
|
||||
if (baseRepository != other.baseRepository) return false
|
||||
if (headRef != other.headRef) return false
|
||||
if (headRefName != other.headRefName) return false
|
||||
if (headRefOid != other.headRefOid) return false
|
||||
if (headRepository != other.headRepository) return false
|
||||
@@ -60,11 +69,13 @@ class GHPullRequest(id: String,
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + viewerCanDeleteHeadRef.hashCode()
|
||||
result = 31 * result + (reviewDecision?.hashCode() ?: 0)
|
||||
result = 31 * result + body.hashCode()
|
||||
result = 31 * result + baseRefName.hashCode()
|
||||
result = 31 * result + baseRefOid.hashCode()
|
||||
result = 31 * result + (baseRepository?.hashCode() ?: 0)
|
||||
result = 31 * result + headRef.hashCode()
|
||||
result = 31 * result + headRefName.hashCode()
|
||||
result = 31 * result + headRefOid.hashCode()
|
||||
result = 31 * result + (headRepository?.hashCode() ?: 0)
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.github.pullrequest.action
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.timeline.GHPRTimelineViewModel
|
||||
|
||||
internal class GHPRDeleteMergedHeadAction : DumbAwareAction() {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = false
|
||||
|
||||
val vm = e.getData(GHPRTimelineViewModel.DATA_KEY) ?: return
|
||||
e.presentation.isEnabledAndVisible = vm.detailsVm.canDeleteMergedBranch.value
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val vm = e.getData(GHPRTimelineViewModel.DATA_KEY) ?: return
|
||||
vm.detailsVm.deleteMergedBranch()
|
||||
}
|
||||
}
|
||||
+2
@@ -42,6 +42,8 @@ interface GHPRDetailsDataProvider {
|
||||
|
||||
suspend fun squashMerge(commitMessage: Pair<String, String>, currentHeadRef: String)
|
||||
|
||||
suspend fun deleteMergedBranch(refId: String)
|
||||
|
||||
suspend fun signalDetailsNeedReload()
|
||||
|
||||
suspend fun signalMergeabilityNeedsReload()
|
||||
|
||||
+9
@@ -113,6 +113,15 @@ internal class GHPRDetailsDataProviderImpl(parentCs: CoroutineScope,
|
||||
notifyNeedsReload()
|
||||
}
|
||||
|
||||
override suspend fun deleteMergedBranch(refId: String) {
|
||||
try {
|
||||
detailsService.deleteMergedBranch(pullRequestId, refId)
|
||||
}
|
||||
finally {
|
||||
notifyNeedsReload()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notifyNeedsReload(state: Boolean = true) {
|
||||
withContext(NonCancellable) {
|
||||
signalDetailsNeedReload()
|
||||
|
||||
+2
@@ -35,4 +35,6 @@ internal interface GHPRDetailsService {
|
||||
suspend fun rebaseMerge(pullRequestId: GHPRIdentifier, currentHeadRef: String)
|
||||
|
||||
suspend fun squashMerge(pullRequestId: GHPRIdentifier, commitMessage: Pair<String, String>, currentHeadRef: String)
|
||||
|
||||
suspend fun deleteMergedBranch(pullRequestId: GHPRIdentifier, refId: String)
|
||||
}
|
||||
+10
@@ -180,6 +180,16 @@ internal class GHPRDetailsServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteMergedBranch(pullRequestId: GHPRIdentifier, refId: String) {
|
||||
runCatching {
|
||||
requestExecutor.executeSuspend(
|
||||
GHGQLRequests.Ref.delete(repository.serverPath, refId)
|
||||
)
|
||||
}.processErrorAndGet { e ->
|
||||
LOG.info("Error occurred while deleting branch for PR ${pullRequestId.number}", e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = logger<GHPRDetailsService>()
|
||||
}
|
||||
|
||||
+17
-10
@@ -4,16 +4,23 @@ package org.jetbrains.plugins.github.pullrequest.ui.details.model
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.plugins.github.api.data.GHActor
|
||||
import org.jetbrains.plugins.github.api.data.GHReaction
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestState
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRIdentifier
|
||||
import java.util.*
|
||||
|
||||
data class GHPRDetailsFull(val id: GHPRIdentifier,
|
||||
val url: String,
|
||||
val author: GHActor,
|
||||
val createdAt: Date,
|
||||
val titleHtml: @NlsSafe String,
|
||||
val description: String?,
|
||||
val descriptionHtml: @NlsSafe String?,
|
||||
val canEditDescription: Boolean,
|
||||
val canReactDescription: Boolean,
|
||||
val reactions: List<GHReaction>)
|
||||
data class GHPRDetailsFull(
|
||||
val id: GHPRIdentifier,
|
||||
val url: String,
|
||||
val author: GHActor,
|
||||
val createdAt: Date,
|
||||
val state: GHPullRequestState,
|
||||
val titleHtml: @NlsSafe String,
|
||||
val description: String?,
|
||||
val descriptionHtml: @NlsSafe String?,
|
||||
val headRefId: String?,
|
||||
val headRefName: String?,
|
||||
val canEditDescription: Boolean,
|
||||
val canReactDescription: Boolean,
|
||||
val canDeleteHeadRef: Boolean,
|
||||
val reactions: List<GHReaction>,
|
||||
)
|
||||
|
||||
+51
-10
@@ -2,20 +2,25 @@
|
||||
package org.jetbrains.plugins.github.pullrequest.ui.timeline
|
||||
|
||||
import com.intellij.collaboration.async.childScope
|
||||
import com.intellij.collaboration.async.mapState
|
||||
import com.intellij.collaboration.async.stateInNow
|
||||
import com.intellij.collaboration.ui.codereview.comment.CodeReviewSubmittableTextViewModelBase
|
||||
import com.intellij.collaboration.ui.codereview.comment.CodeReviewTextEditingViewModel
|
||||
import com.intellij.collaboration.ui.icon.IconsProvider
|
||||
import com.intellij.collaboration.util.ComputedResult
|
||||
import com.intellij.collaboration.util.ResultUtil.runCatchingUser
|
||||
import com.intellij.collaboration.util.getOrNull
|
||||
import com.intellij.collaboration.util.map
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.plugins.github.api.data.GHReactionContent
|
||||
import org.jetbrains.plugins.github.api.data.GHUser
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequest
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestState
|
||||
import org.jetbrains.plugins.github.pullrequest.comment.convertToHtml
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRDataContext
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHReactionsService
|
||||
@@ -26,10 +31,16 @@ import org.jetbrains.plugins.github.pullrequest.ui.details.model.GHPRDetailsFull
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionViewModelImpl
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsViewModel
|
||||
|
||||
class GHPRDetailsTimelineViewModel internal constructor(private val project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
private val dataContext: GHPRDataContext,
|
||||
private val dataProvider: GHPRDataProvider) {
|
||||
class GHPRDetailsTimelineViewModel internal constructor(
|
||||
private val project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
private val dataContext: GHPRDataContext,
|
||||
private val dataProvider: GHPRDataProvider,
|
||||
) {
|
||||
companion object {
|
||||
private val LOG = logger<GHPRDetailsTimelineViewModel>()
|
||||
}
|
||||
|
||||
private val cs = parentCs.childScope(this::class)
|
||||
|
||||
private val currentUser: GHUser = dataContext.securityService.currentUser
|
||||
@@ -47,16 +58,31 @@ class GHPRDetailsTimelineViewModel internal constructor(private val project: Pro
|
||||
val reactionsVm: GHReactionsViewModel =
|
||||
GHReactionViewModelImpl(cs, dataProvider.id.id, loadedReactionsState, currentUser, reactionsService, reactionIconsProvider)
|
||||
|
||||
val isMerged: StateFlow<Boolean> =
|
||||
details.mapState { it.getOrNull()?.state == GHPullRequestState.MERGED }
|
||||
val canDeleteMergedBranch: StateFlow<Boolean> =
|
||||
details.mapState {
|
||||
val details = it.getOrNull() ?: return@mapState false
|
||||
details.canDeleteHeadRef && details.state == GHPullRequestState.MERGED
|
||||
}
|
||||
|
||||
val headRefName: StateFlow<String?> =
|
||||
details.mapState { it.getOrNull()?.headRefName }
|
||||
|
||||
private fun createDetails(data: GHPullRequest): GHPRDetailsFull = GHPRDetailsFull(
|
||||
dataProvider.id,
|
||||
data.url,
|
||||
data.author ?: dataContext.securityService.ghostUser,
|
||||
data.createdAt,
|
||||
data.state,
|
||||
data.title.convertToHtml(project),
|
||||
data.body,
|
||||
data.body.convertToHtml(project),
|
||||
data.headRefId,
|
||||
data.headRefName,
|
||||
data.viewerCanUpdate,
|
||||
data.viewerCanReact,
|
||||
data.viewerCanDeleteHeadRef && data.headRefId != null,
|
||||
data.reactions.nodes
|
||||
)
|
||||
|
||||
@@ -73,14 +99,29 @@ class GHPRDetailsTimelineViewModel internal constructor(private val project: Pro
|
||||
requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMergedBranch() {
|
||||
if (!canDeleteMergedBranch.value) return
|
||||
val details = details.value.getOrNull() ?: return
|
||||
val refId = details.headRefId ?: return
|
||||
|
||||
cs.launch {
|
||||
runCatchingUser {
|
||||
dataProvider.detailsData.deleteMergedBranch(refId)
|
||||
}.onFailure { e ->
|
||||
LOG.warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GHPREditDescriptionViewModel internal constructor(project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
private val detailsData: GHPRDetailsDataProvider,
|
||||
initialText: String,
|
||||
private val onDone: () -> Unit)
|
||||
: CodeReviewSubmittableTextViewModelBase(project, parentCs, initialText), CodeReviewTextEditingViewModel {
|
||||
class GHPREditDescriptionViewModel internal constructor(
|
||||
project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
private val detailsData: GHPRDetailsDataProvider,
|
||||
initialText: String,
|
||||
private val onDone: () -> Unit,
|
||||
) : CodeReviewSubmittableTextViewModelBase(project, parentCs, initialText), CodeReviewTextEditingViewModel {
|
||||
override fun save() {
|
||||
submit {
|
||||
detailsData.updateDetails(description = it)
|
||||
|
||||
+76
-8
@@ -13,11 +13,10 @@ import com.intellij.collaboration.ui.codereview.comment.submitActionIn
|
||||
import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPanelFactory
|
||||
import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
|
||||
import com.intellij.collaboration.ui.codereview.timeline.comment.CommentTextFieldFactory
|
||||
import com.intellij.collaboration.ui.util.bindContent
|
||||
import com.intellij.collaboration.ui.util.bindTextHtmlIn
|
||||
import com.intellij.collaboration.ui.util.bindTextIn
|
||||
import com.intellij.collaboration.ui.util.bindVisibilityIn
|
||||
import com.intellij.collaboration.ui.util.*
|
||||
import com.intellij.collaboration.util.getOrNull
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
@@ -31,6 +30,7 @@ import com.intellij.ui.PopupHandler
|
||||
import com.intellij.ui.ScrollPaneFactory
|
||||
import com.intellij.ui.components.panels.ListLayout
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.util.IconUtil
|
||||
import com.intellij.util.asDisposable
|
||||
import com.intellij.util.ui.JBFont
|
||||
import com.intellij.util.ui.JBUI
|
||||
@@ -38,6 +38,7 @@ import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.update.UiNotifyConnector
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.plugins.github.GithubIcons
|
||||
import org.jetbrains.plugins.github.ai.GHPRAISummaryExtension
|
||||
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException
|
||||
import org.jetbrains.plugins.github.i18n.GithubBundle
|
||||
@@ -45,13 +46,16 @@ import org.jetbrains.plugins.github.pullrequest.ui.GHPRConnectedProjectViewModel
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.details.model.GHPRDetailsFull
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsComponentFactory
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsPickerComponentFactory
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.timeline.GHPRTimelineEventComponentFactoryImpl.Companion.branchHTML
|
||||
import org.jetbrains.plugins.github.ui.component.GHHtmlErrorPanel
|
||||
import org.jetbrains.plugins.github.ui.util.addGithubHyperlinkListener
|
||||
import java.awt.Font
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.event.ChangeEvent
|
||||
import javax.swing.event.ChangeListener
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
|
||||
internal class GHPRFileEditorComponentFactory(
|
||||
private val cs: CoroutineScope,
|
||||
@@ -69,7 +73,15 @@ internal class GHPRFileEditorComponentFactory(
|
||||
val description = createDescription(loadedDetails)
|
||||
val itemComponentFactory = createItemComponentFactory()
|
||||
|
||||
val timeline = ComponentListPanelFactory.createVertical(cs, timelineVm.timelineItems, componentFactory = itemComponentFactory)
|
||||
val timelineEvents = ComponentListPanelFactory.createVertical(cs, timelineVm.timelineItems, componentFactory = itemComponentFactory)
|
||||
val mergedTimelineItem = Wrapper().apply {
|
||||
bindContent(
|
||||
debugName = "mergeActionChild",
|
||||
dataFlow = combine(timelineVm.detailsVm.isMerged, timelineVm.loadingError, ::Pair).distinctUntilChanged()
|
||||
) { (isMerged, loadingError) ->
|
||||
if (isMerged && loadingError == null) createMergedTimelineItem(timelineVm.detailsVm) else null
|
||||
}
|
||||
}
|
||||
|
||||
val progressAndErrorPanel = JPanel(ListLayout.vertical(0, ListLayout.Alignment.CENTER)).apply {
|
||||
isOpaque = false
|
||||
@@ -112,9 +124,10 @@ internal class GHPRFileEditorComponentFactory(
|
||||
})
|
||||
|
||||
add(description)
|
||||
add(timeline)
|
||||
add(timelineEvents)
|
||||
|
||||
add(progressAndErrorPanel)
|
||||
add(mergedTimelineItem)
|
||||
|
||||
timelineVm.commentVm?.also {
|
||||
val commentTextField = createCommentField(it).apply {
|
||||
@@ -138,8 +151,7 @@ internal class GHPRFileEditorComponentFactory(
|
||||
}
|
||||
})
|
||||
}
|
||||
UiNotifyConnector.doWhenFirstShown(scrollPane)
|
||||
{
|
||||
UiNotifyConnector.doWhenFirstShown(scrollPane) {
|
||||
timelineVm.requestMore()
|
||||
}
|
||||
|
||||
@@ -226,6 +238,57 @@ internal class GHPRFileEditorComponentFactory(
|
||||
return CodeReviewCommentTextFieldFactory.createIn(cs, vm, actions, icon)
|
||||
}
|
||||
|
||||
private fun createMergedTimelineItem(detailsVm: GHPRDetailsTimelineViewModel): JComponent {
|
||||
return CodeReviewChatItemUIUtil.build(
|
||||
type = CodeReviewChatItemUIUtil.ComponentType.FULL,
|
||||
iconProvider = { iconSize -> IconUtil.resizeSquared(GithubIcons.PullRequestMerged, iconSize) },
|
||||
content = HorizontalListPanel(gap = 4).apply {
|
||||
add(SimpleHtmlPane(addBrowserListener = false).apply {
|
||||
addHyperlinkListener { e ->
|
||||
if (e.eventType != HyperlinkEvent.EventType.ACTIVATED) return@addHyperlinkListener
|
||||
|
||||
if (e.description == DELETE_ACTION_NAME) {
|
||||
detailsVm.deleteMergedBranch()
|
||||
}
|
||||
else if (e.description == RESTORE_ACTION_NAME) {
|
||||
val url = detailsVm.details.value.getOrNull()?.url ?: return@addHyperlinkListener
|
||||
BrowserUtil.browse(url)
|
||||
}
|
||||
}
|
||||
|
||||
bindTextHtml(
|
||||
"mergedTimelineItem.body.text",
|
||||
combine(detailsVm.canDeleteMergedBranch, detailsVm.headRefName) { canDeleteMergedBranch, headRefName ->
|
||||
val branchHtml = headRefName?.let(::branchHTML) ?: "branch"
|
||||
if (canDeleteMergedBranch) {
|
||||
val deleteBranchHtml = GithubBundle.message("pullRequest.timeline.merged.body.delete")
|
||||
val link = HtmlChunk.link(DELETE_ACTION_NAME, HtmlChunk.raw(deleteBranchHtml))
|
||||
|
||||
GithubBundle.message("pullRequest.timeline.merged.body.withDelete", link, branchHtml)
|
||||
}
|
||||
else {
|
||||
val restoreText = GithubBundle.message("pullRequest.timeline.merged.body.restore")
|
||||
val link = HtmlChunk.link(RESTORE_ACTION_NAME, HtmlChunk.raw(restoreText))
|
||||
|
||||
GithubBundle.message("pullRequest.timeline.merged.body.withoutDelete", branchHtml, link)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
add(JLabel().apply {
|
||||
bindIcon("mergedTimelineItem.body.icon", detailsVm.canDeleteMergedBranch.map { canDeleteMergedBranch ->
|
||||
if (!canDeleteMergedBranch) AllIcons.Ide.External_link_arrow else null
|
||||
})
|
||||
})
|
||||
}
|
||||
) {
|
||||
withHeader(JLabel(GithubBundle.message("pullRequest.timeline.merged.title")).apply {
|
||||
font = font.deriveFont(Font.BOLD)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun createItemComponentFactory() = GHPRTimelineItemComponentFactory(project, timelineVm)
|
||||
|
||||
private val noDescriptionHtmlText by lazy {
|
||||
@@ -235,4 +298,9 @@ internal class GHPRFileEditorComponentFactory(
|
||||
.wrapWith("i")
|
||||
.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESTORE_ACTION_NAME = "restore"
|
||||
private const val DELETE_ACTION_NAME = "delete"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -248,7 +248,7 @@ internal class GHPRTimelineEventComponentFactoryImpl(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun branchHTML(name: @Nls String): HtmlChunk {
|
||||
internal fun branchHTML(name: @Nls String): HtmlChunk {
|
||||
val foreground = CurrentBranchComponent.TEXT_COLOR
|
||||
val background = CurrentBranchComponent.getBranchPresentationBackground(UIUtil.getListBackground())
|
||||
|
||||
|
||||
+2
-2
@@ -91,7 +91,7 @@ internal class GHPRTimelineViewModelImpl(
|
||||
override val ghostUser: GHUser = securityService.ghostUser
|
||||
override val currentUser: GHUser = securityService.currentUser
|
||||
|
||||
override val detailsVm = GHPRDetailsTimelineViewModel(project, parentCs, dataContext, dataProvider)
|
||||
override val detailsVm = GHPRDetailsTimelineViewModel(project, cs, dataContext, dataProvider)
|
||||
private val timelineLoader = dataProvider.acquireTimelineLoader(cs)
|
||||
|
||||
override val loadingErrorHandler: GHLoadingErrorHandler =
|
||||
@@ -99,7 +99,7 @@ internal class GHPRTimelineViewModelImpl(
|
||||
|
||||
override val commentVm: GHPRNewCommentViewModel? =
|
||||
if (securityService.currentUserHasPermissionLevel(GHRepositoryPermissionLevel.READ)) {
|
||||
GHPRNewCommentViewModel(project, parentCs, commentsData)
|
||||
GHPRNewCommentViewModel(project, cs, commentsData)
|
||||
}
|
||||
else null
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.jetbrains.plugins.github.api.data.GHReactionContent
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.util.*
|
||||
@@ -526,6 +525,8 @@ private object TestCases {
|
||||
GHGQLRequests.PullRequest.markFileAsViewed(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT),
|
||||
GHGQLRequests.PullRequest.unmarkFileAsViewed(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT),
|
||||
|
||||
GHGQLRequests.Ref.delete(DUMMY_SERVER_PATH, DUMMY_TEXT),
|
||||
|
||||
GHGQLRequests.PullRequest.Timeline.items(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT, DUMMY_NUMBER, DUMMY_PAGINATION),
|
||||
GHGQLRequests.PullRequest.Timeline.items(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT, DUMMY_NUMBER, null),
|
||||
GHGQLRequests.PullRequest.Review.create(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_PR_EVENT, DUMMY_TEXT, DUMMY_TEXT, listOf()),
|
||||
@@ -586,8 +587,8 @@ class GHGQLDeserializationAssumptionsTest {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `deserializing a missing value instead of a list is not fine`() {
|
||||
assertThrows<Exception> { mapper.readValue("""{}""", ListHolder::class.java) }
|
||||
fun `deserializing a missing value instead of a list is fine`() {
|
||||
assertDoesNotThrow { mapper.readValue("""{}""", ListHolder::class.java) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user