[collab/github/gitlab] Add an action link to a selector error status panel master
authorPavel Gromov <pavel.gromov@jetbrains.com>
Mon, 21 Nov 2022 22:26:00 +0000 (23:26 +0100)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Wed, 30 Nov 2022 00:52:12 +0000 (00:52 +0000)
* Reset review list after re-login

GitOrigin-RevId: 7694d7279c37a5b161cf0c95bdb10f6170b0f64a

platform/collaboration-tools/resources/messages/CollaborationToolsBundle.properties
platform/collaboration-tools/src/com/intellij/collaboration/ui/ActionLinkListener.kt [new file with mode: 0644]
plugins/git4idea/src/git4idea/remote/hosting/ui/RepositoryAndAccountSelectorComponentFactory.kt
plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/GHRepositoryAndAccountSelectorComponentFactory.kt
plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/GHSelectorErrorStatusPresenter.kt
plugins/gitlab/src/org/jetbrains/plugins/gitlab/exception/GitLabHttpStatusErrorAction.kt
plugins/gitlab/src/org/jetbrains/plugins/gitlab/mergerequest/ui/GitLabRepositoryAndAccountSelectorViewModel.kt
plugins/gitlab/src/org/jetbrains/plugins/gitlab/mergerequest/ui/GitLabSelectorErrorStatusPresenter.kt
plugins/gitlab/src/org/jetbrains/plugins/gitlab/mergerequest/ui/GitLabToolWindowTabController.kt
plugins/gitlab/src/org/jetbrains/plugins/gitlab/mergerequest/ui/list/GitLabMergeRequestErrorStatusPresenter.kt

index 052cf6c7740844d52e7158bbed119e79d369fb16..629946ca00291ffc58416a398e75059bf652a163 100644 (file)
@@ -6,7 +6,6 @@ accounts.add.link=Add account\u2026
 accounts.set.default=Set as Default
 accounts.none.added=No accounts added
 account.choose.link=Choose an account
-account.refresh.token=Refresh token\u2026
 diff.add.comment.icon.tooltip=Leave a comment
 action.CodeReview.CreateDiffComment.text=Add Review Comment
 action.CodeReview.MarkChangesViewed.text=Mark as Viewed
@@ -25,6 +24,12 @@ login.token.empty=Token is empty
 login.token.generate=Generate\u2026
 login.progress=Logging in\u2026
 login.dialog.title=Log In with Access Token
+accounts.saving.credentials=Saving credentials
+
+# Repository and account selector
+repository.and.account.selector.login.again.action.text=Log in again\u2026
+
+# Review list
 review.list.connection.failed=Could not connect to repository
 review.list.connection.failed.repository.account=Could not connect to repository {0} with account {1}
 review.list.empty.state.loading=Loading\u2026
@@ -32,5 +37,4 @@ review.list.info={0} \u00B7 created {1}
 review.list.info.author={0} \u00B7 created {1}, by {2}
 review.list.filter.quick.title=Quick Filters
 review.list.filter.quick.clear=Clear {0} Filters
-review.login.note=Change repository or account later under the gear icon
-accounts.saving.credentials=Saving credentials
\ No newline at end of file
+review.login.note=Change repository or account later under the gear icon
\ No newline at end of file
diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/ActionLinkListener.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/ActionLinkListener.kt
new file mode 100644 (file)
index 0000000..93ef200
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.collaboration.ui
+
+import com.intellij.ide.BrowserUtil
+import com.intellij.ui.HyperlinkAdapter
+import java.awt.event.ActionEvent
+import java.awt.event.ActionListener
+import java.awt.event.KeyEvent
+import javax.swing.Action
+import javax.swing.JComponent
+import javax.swing.KeyStroke
+import javax.swing.event.HyperlinkEvent
+
+class ActionLinkListener(
+  private val component: JComponent,
+) : HyperlinkAdapter() {
+  var action: Action? = null
+
+  init {
+    component.registerKeyboardAction(
+      ActionListener { action?.actionPerformed(it) },
+      KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
+      JComponent.WHEN_FOCUSED
+    )
+  }
+
+  override fun hyperlinkActivated(event: HyperlinkEvent) {
+    if (event.description == ERROR_ACTION_HREF) {
+      val actionEvent = ActionEvent(component, ActionEvent.ACTION_PERFORMED, ACTION_EVENT_LINK_COMMAND)
+      action?.actionPerformed(actionEvent)
+    }
+    else {
+      BrowserUtil.browse(event.description)
+    }
+  }
+
+  companion object {
+    private const val ACTION_EVENT_LINK_COMMAND = "perform"
+    const val ERROR_ACTION_HREF = "ERROR_ACTION"
+  }
+}
\ No newline at end of file
index 9800c96ca89c78b29c599fb04d20a150f375d253..5aba935ae20b6dbf23097d145a108eb1b1d7b501 100644 (file)
@@ -6,6 +6,7 @@ import com.intellij.collaboration.auth.ServerAccount
 import com.intellij.collaboration.auth.ui.LoadingAccountsDetailsProvider
 import com.intellij.collaboration.messages.CollaborationToolsBundle
 import com.intellij.collaboration.ui.AccountSelectorComponentFactory
+import com.intellij.collaboration.ui.ActionLinkListener
 import com.intellij.collaboration.ui.CollaborationToolsUIUtil.isDefault
 import com.intellij.collaboration.ui.SimpleComboboxWithActionsFactory
 import com.intellij.collaboration.ui.codereview.BaseHtmlEditorPane
@@ -13,10 +14,13 @@ import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
 import com.intellij.collaboration.ui.util.bindDisabled
 import com.intellij.collaboration.ui.util.bindText
 import com.intellij.collaboration.ui.util.bindVisibility
+import com.intellij.collaboration.ui.util.getName
 import com.intellij.icons.AllIcons
 import com.intellij.ide.plugins.newui.HorizontalLayout
 import com.intellij.openapi.util.text.HtmlBuilder
+import com.intellij.openapi.util.text.HtmlChunk
 import com.intellij.ui.AnimatedIcon
+import com.intellij.ui.BrowserHyperlinkListener
 import com.intellij.util.ui.JBUI
 import com.intellij.util.ui.UI
 import com.intellij.util.ui.UIUtil
@@ -90,6 +94,10 @@ class RepositoryAndAccountSelectorComponentFactory<M : HostedGitRepositoryMappin
       }
 
       val errorTextPane = BaseHtmlEditorPane().apply htmlPane@{
+        val actionLinkListener = ActionLinkListener(this@htmlPane)
+        removeHyperlinkListener(BrowserHyperlinkListener.INSTANCE)
+        addHyperlinkListener(actionLinkListener)
+
         bindText(scope, vm.errorState.map { error ->
           if (error == null) return@map ""
           HtmlBuilder().append(errorPresenter.getErrorTitle(error)).br().apply {
@@ -97,6 +105,12 @@ class RepositoryAndAccountSelectorComponentFactory<M : HostedGitRepositoryMappin
             if (errorDescription != null) {
               append("$errorDescription ")
             }
+
+            val errorAction = errorPresenter.getErrorAction(error)
+            actionLinkListener.action = errorAction
+            if (errorAction != null) {
+              append(HtmlChunk.link(ActionLinkListener.ERROR_ACTION_HREF, errorAction.getName()))
+            }
           }.toString()
         })
       }
index 5a5127776ef2c3453f7ee6417f4c40a096a866a5..0d3054482facc2a8fcac37a634fe06ab43d374c3 100644 (file)
@@ -27,7 +27,9 @@ class GHRepositoryAndAccountSelectorComponentFactory internal constructor(privat
 
   fun create(scope: CoroutineScope): JComponent {
     val accountDetailsProvider = GHAccountsDetailsProvider(scope, accountManager)
-    val errorPresenter = GHSelectorErrorStatusPresenter()
+    val errorPresenter = GHSelectorErrorStatusPresenter(project) {
+      vm.submitSelection()
+    }
 
     return RepositoryAndAccountSelectorComponentFactory(vm)
       .create(scope = scope,
index 134425ac6ff2f83c6d2b14b78397a99eb9567934..02496c197f38f5c584ca905363891512c4c21dab 100644 (file)
@@ -4,11 +4,21 @@ package org.jetbrains.plugins.github.pullrequest.ui.toolwindow
 import com.intellij.collaboration.messages.CollaborationToolsBundle
 import com.intellij.collaboration.ui.ExceptionUtil
 import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
+import com.intellij.openapi.project.Project
 import git4idea.remote.hosting.ui.RepositoryAndAccountSelectorViewModel
+import org.jetbrains.plugins.github.authentication.AuthorizationType
+import org.jetbrains.plugins.github.authentication.GHAccountsUtil
+import org.jetbrains.plugins.github.authentication.accounts.GithubAccount
 import org.jetbrains.plugins.github.i18n.GithubBundle
+import java.awt.event.ActionEvent
+import javax.swing.AbstractAction
 import javax.swing.Action
+import javax.swing.JComponent
 
-class GHSelectorErrorStatusPresenter : ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error> {
+class GHSelectorErrorStatusPresenter(
+  private val project: Project,
+  private val resetAction: () -> Unit = {}
+) : ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error> {
   override fun getErrorTitle(error: RepositoryAndAccountSelectorViewModel.Error): String = when (error) {
     is RepositoryAndAccountSelectorViewModel.Error.SubmissionError -> CollaborationToolsBundle.message(
       "review.list.connection.failed.repository.account",
@@ -23,5 +33,22 @@ class GHSelectorErrorStatusPresenter : ErrorStatusPresenter<RepositoryAndAccount
     is RepositoryAndAccountSelectorViewModel.Error.MissingCredentials -> GithubBundle.message("account.token.missing")
   }
 
-  override fun getErrorAction(error: RepositoryAndAccountSelectorViewModel.Error): Action? = null
+  override fun getErrorAction(error: RepositoryAndAccountSelectorViewModel.Error): Action? = when (error) {
+    is RepositoryAndAccountSelectorViewModel.Error.SubmissionError -> LogInAgain(project, error.account as GithubAccount, resetAction)
+    else -> null
+  }
+
+  private class LogInAgain(
+    private val project: Project,
+    private val account: GithubAccount,
+    private val resetAction: () -> Unit
+  ) : AbstractAction(CollaborationToolsBundle.message("repository.and.account.selector.login.again.action.text")) {
+    override fun actionPerformed(event: ActionEvent) {
+      val parentComponent = event.source as? JComponent ?: return
+      val authData = GHAccountsUtil.requestReLogin(account, project, parentComponent, authType = AuthorizationType.UNDEFINED)
+      if (authData != null) {
+        resetAction()
+      }
+    }
+  }
 }
\ No newline at end of file
index 0c9b1bd57e341bb963864cc0d107ca21f9765616..40b54b65fa38a395dbf2f690da47a709899beb07 100644 (file)
@@ -14,17 +14,19 @@ import javax.swing.AbstractAction
 import javax.swing.JComponent
 
 internal sealed class GitLabHttpStatusErrorAction(@Nls name: String) : AbstractAction(name) {
-  class RefreshToken(
+  class LogInAgain(
     private val project: Project,
     private val parentScope: CoroutineScope,
     private val account: GitLabAccount,
-    private val accountManager: GitLabAccountManager
-  ) : GitLabHttpStatusErrorAction(CollaborationToolsBundle.message("account.refresh.token")) {
+    private val accountManager: GitLabAccountManager,
+    private val resetAction: () -> Unit = {}
+  ) : GitLabHttpStatusErrorAction(CollaborationToolsBundle.message("repository.and.account.selector.login.again.action.text")) {
     override fun actionPerformed(event: ActionEvent) {
       val parentComponent = event.source as? JComponent ?: return
       val token = GitLabLoginUtil.updateToken(project, parentComponent, account) { _, _ -> true } ?: return
       parentScope.launch {
         accountManager.updateAccount(account, token)
+        resetAction()
       }
     }
   }
index 8b47211ea79feb7d21ae918aea5ab08114b4d698..92970309ebce39e0400826598a45321dff23e38c 100644 (file)
@@ -17,7 +17,7 @@ import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
 internal class GitLabRepositoryAndAccountSelectorViewModel(
   private val scope: CoroutineScope,
   projectsManager: GitLabProjectsManager,
-  private val accountManager: GitLabAccountManager,
+  val accountManager: GitLabAccountManager,
   onSelected: suspend (GitLabProjectMapping, GitLabAccount) -> Unit,
 )
   : RepositoryAndAccountSelectorViewModelBase<GitLabProjectMapping, GitLabAccount>(
index 9e48e05de83387fd67afd4eaaf7c44b20bb9a5d0..747c025c1b73b79786fedc21a025975e3fa1a62f 100644 (file)
@@ -4,11 +4,21 @@ package org.jetbrains.plugins.gitlab.mergerequest.ui
 import com.intellij.collaboration.messages.CollaborationToolsBundle
 import com.intellij.collaboration.ui.ExceptionUtil
 import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
+import com.intellij.openapi.project.Project
 import git4idea.remote.hosting.ui.RepositoryAndAccountSelectorViewModel
+import kotlinx.coroutines.CoroutineScope
+import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
+import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
+import org.jetbrains.plugins.gitlab.exception.GitLabHttpStatusErrorAction
 import org.jetbrains.plugins.gitlab.util.GitLabBundle
 import javax.swing.Action
 
-class GitLabSelectorErrorStatusPresenter : ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error> {
+internal class GitLabSelectorErrorStatusPresenter(
+  private val project: Project,
+  private val parentScope: CoroutineScope,
+  private val accountManager: GitLabAccountManager,
+  private val resetAction: () -> Unit
+) : ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error> {
   override fun getErrorTitle(error: RepositoryAndAccountSelectorViewModel.Error): String = when (error) {
     is RepositoryAndAccountSelectorViewModel.Error.SubmissionError -> CollaborationToolsBundle.message(
       "review.list.connection.failed.repository.account",
@@ -23,5 +33,13 @@ class GitLabSelectorErrorStatusPresenter : ErrorStatusPresenter<RepositoryAndAcc
     is RepositoryAndAccountSelectorViewModel.Error.MissingCredentials -> GitLabBundle.message("account.token.missing")
   }
 
-  override fun getErrorAction(error: RepositoryAndAccountSelectorViewModel.Error): Action? = null
+  override fun getErrorAction(error: RepositoryAndAccountSelectorViewModel.Error): Action? = when (error) {
+    is RepositoryAndAccountSelectorViewModel.Error.SubmissionError -> GitLabHttpStatusErrorAction.LogInAgain(
+      project, parentScope,
+      account = error.account as GitLabAccount,
+      accountManager = accountManager,
+      resetAction
+    )
+    else -> null
+  }
 }
\ No newline at end of file
index 4730c0498a0843d17c518eda6352899c8d22ffec..8a7d155909899eec016b430051c41608beed2094 100644 (file)
@@ -65,7 +65,9 @@ internal class GitLabToolWindowTabController(private val project: Project,
       accountsPopupActionsSupplier = { createPopupLoginActions(selectorVm, it) },
       submitActionText = GitLabBundle.message("view.merge.requests.button"),
       loginButtons = createLoginButtons(scope, selectorVm),
-      errorPresenter = GitLabSelectorErrorStatusPresenter()
+      errorPresenter = GitLabSelectorErrorStatusPresenter(project, scope, selectorVm.accountManager) {
+        selectorVm.submitSelection()
+      }
     )
 
     scope.launch {
index 7494c0a31e6ee24c2d27bd51e3ab7c6330d2afb1..3f4a7cd7d75ebcd10c2848017bf80ceeae2cf804 100644 (file)
@@ -35,7 +35,7 @@ internal class GitLabMergeRequestErrorStatusPresenter(
   override fun getErrorAction(error: Throwable): Action? {
     val httpStatusError = parseHttpStatusError(error) ?: return null
     return when (httpStatusError.statusErrorType) {
-      HttpStatusErrorType.INVALID_TOKEN -> GitLabHttpStatusErrorAction.RefreshToken(project, scope, account, accountManager)
+      HttpStatusErrorType.INVALID_TOKEN -> GitLabHttpStatusErrorAction.LogInAgain(project, scope, account, accountManager)
       HttpStatusErrorType.UNKNOWN -> null
     }
   }