IJPL-180489: [gl] Track GitLab login events

(cherry picked from commit 8ff50d228d349c8cda4071fe84784ebf8427b8eb)

IJ-CR-167948

GitOrigin-RevId: 13d2535a1dcbef3d848d731565107b9ad5dc73d7
This commit is contained in:
Aleksandr Krasilnikov
2025-07-01 15:59:33 +02:00
committed by intellij-monorepo-bot
parent f0d7878bb6
commit 1406f6aa77
14 changed files with 56 additions and 27 deletions

View File

@@ -31,13 +31,15 @@ object GitLabLoginUtil {
fun logInViaToken(
project: Project, parentComponent: JComponent?,
serverPath: GitLabServerPath = GitLabServerPath.DEFAULT_SERVER,
loginSource: GitLabLoginSource,
uniqueAccountPredicate: (GitLabServerPath, String) -> Boolean
): LoginResult = logInViaToken(project, parentComponent, serverPath, null, uniqueAccountPredicate)
): LoginResult = logInViaToken(project, parentComponent, serverPath, null, loginSource, uniqueAccountPredicate)
@RequiresEdt
internal fun logInViaToken(
project: Project, parentComponent: JComponent?,
serverPath: GitLabServerPath = GitLabServerPath.DEFAULT_SERVER, requiredUsername: String? = null,
loginSource: GitLabLoginSource,
uniqueAccountPredicate: (GitLabServerPath, String) -> Boolean
): LoginResult {
@@ -50,6 +52,8 @@ object GitLabLoginUtil {
return when (exitCode) {
DialogWrapper.OK_EXIT_CODE -> {
val loginResult = model.loginState.value.asSafely<LoginModel.LoginState.Connected>() ?: return LoginResult.Failure
val loginData = GitLabLoginData(loginSource, isReLogin = false, isGitLabDotCom = serverPath.isDefault)
GitLabLoginCollector.login(loginData)
return LoginResult.Success(GitLabAccount(name = loginResult.username, server = model.getServerPath()), model.token)
}
DialogWrapper.NEXT_USER_EXIT_CODE -> LoginResult.OtherMethod
@@ -62,13 +66,16 @@ object GitLabLoginUtil {
fun updateToken(
project: Project, parentComponent: JComponent?,
account: GitLabAccount,
loginSource: GitLabLoginSource,
uniqueAccountPredicate: (GitLabServerPath, String) -> Boolean
): LoginResult = updateToken(project, parentComponent, account, null, uniqueAccountPredicate)
): LoginResult = updateToken(project, parentComponent, account, null, loginSource, uniqueAccountPredicate)
@RequiresEdt
internal fun updateToken(
project: Project, parentComponent: JComponent?,
account: GitLabAccount, requiredUsername: String? = null,
account: GitLabAccount,
requiredUsername: String? = null,
loginSource: GitLabLoginSource,
uniqueAccountPredicate: (GitLabServerPath, String) -> Boolean
): LoginResult {
val predicateWithoutCurrent: (GitLabServerPath, String) -> Boolean = { serverPath, username ->
@@ -83,6 +90,8 @@ object GitLabLoginUtil {
val exitState = showLoginDialog(project, parentComponent, model, title, true)
val loginState = model.loginState.value
if (exitState == DialogWrapper.OK_EXIT_CODE && loginState is LoginModel.LoginState.Connected) {
val loginData = GitLabLoginData(loginSource, isReLogin = true, isGitLabDotCom = model.getServerPath().isDefault)
GitLabLoginCollector.login(loginData)
return LoginResult.Success(
GitLabAccount(id = account.id, name = loginState.username, server = model.getServerPath()),
model.token

View File

@@ -5,12 +5,13 @@ import com.intellij.openapi.project.Project
import com.intellij.platform.util.coroutines.childScope
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.exception.GitLabHttpStatusErrorAction
import javax.swing.Action
@ApiStatus.Internal
interface GitLabAccountViewModel {
fun loginAction(): Action
fun loginAction(loginSource: GitLabLoginSource): Action
}
internal class GitLabAccountViewModelImpl(
@@ -21,7 +22,7 @@ internal class GitLabAccountViewModelImpl(
) : GitLabAccountViewModel {
private val cs: CoroutineScope = parentCs.childScope("GitLab Account VM")
override fun loginAction(): Action {
return GitLabHttpStatusErrorAction.LogInAgain(project, cs, account, accountManager)
override fun loginAction(loginSource: GitLabLoginSource): Action {
return GitLabHttpStatusErrorAction.LogInAgain(project, cs, account, accountManager, loginSource)
}
}

View File

@@ -7,6 +7,7 @@ import com.intellij.ui.awt.RelativePoint
import com.intellij.util.asSafely
import com.intellij.util.concurrency.annotations.RequiresEdt
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
@@ -19,14 +20,14 @@ internal class GitLabAccountsPanelActionsController(private val project: Project
@RequiresEdt
override fun addAccount(parentComponent: JComponent, point: RelativePoint?) {
// ignoring the point since we know there will be a simple dialog for now
val loginResult = GitLabLoginUtil.logInViaToken(project, parentComponent, uniqueAccountPredicate = ::isAccountUnique)
val loginResult = GitLabLoginUtil.logInViaToken(project, parentComponent, loginSource = GitLabLoginSource.SETTINGS, uniqueAccountPredicate = ::isAccountUnique)
.asSafely<LoginResult.Success>() ?: return
model.add(loginResult.account, loginResult.token)
}
@RequiresEdt
override fun editAccount(parentComponent: JComponent, account: GitLabAccount) {
val loginResult = GitLabLoginUtil.updateToken(project, parentComponent, account, ::isAccountUnique)
val loginResult = GitLabLoginUtil.updateToken(project, parentComponent, account, loginSource = GitLabLoginSource.SETTINGS, ::isAccountUnique)
.asSafely<LoginResult.Success>() ?: return
model.update(account, loginResult.token)
}

View File

@@ -7,6 +7,7 @@ import com.intellij.util.asSafely
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.Nls
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
@@ -21,11 +22,12 @@ internal sealed class GitLabHttpStatusErrorAction(@Nls name: String) : AbstractA
private val parentScope: CoroutineScope,
private val account: GitLabAccount,
private val accountManager: GitLabAccountManager,
private val loginSource: GitLabLoginSource,
private val resetAction: () -> Unit = {}
) : GitLabHttpStatusErrorAction(CollaborationToolsBundle.message("login.again.action.text")) {
override fun actionPerformed(event: ActionEvent) {
val parentComponent = event.source as? JComponent ?: return
val loginResult = GitLabLoginUtil.updateToken(project, parentComponent, account) { _, _ -> true }
val loginResult = GitLabLoginUtil.updateToken(project, parentComponent, account, loginSource) { _, _ -> true }
.asSafely<LoginResult.Success>()
?: return
parentScope.launch {

View File

@@ -17,6 +17,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.GitLabServersManager
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
@@ -77,7 +78,7 @@ private suspend fun GitLabAccountManager.tryCreateAccount(
val isGitLabServer = service<GitLabServersManager>().checkIsGitLabServer(server)
if (!isGitLabServer) return LoginResult.OtherMethod
return withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
GitLabLoginUtil.logInViaToken(project, null, server, login, ::isAccountUnique)
GitLabLoginUtil.logInViaToken(project, null, server, login, loginSource = GitLabLoginSource.GIT,::isAccountUnique)
}
}
@@ -87,7 +88,7 @@ private suspend fun GitLabAccountManager.reLogInWithAccount(
account: GitLabAccount,
login: String?,
): LoginResult = withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
GitLabLoginUtil.updateToken(project, null, account, login, ::isAccountUnique)
GitLabLoginUtil.updateToken(project, null, account, login, loginSource = GitLabLoginSource.GIT, ::isAccountUnique)
}
private suspend fun GitLabAccountManager.selectAccountAndLogIn(
@@ -101,7 +102,7 @@ private suspend fun GitLabAccountManager.selectAccountAndLogIn(
?: return@withContext LoginResult.Failure
val token = accountsWithTokens[account]
if (token == null) {
GitLabLoginUtil.updateToken(project, null, account, login, ::isAccountUnique)
GitLabLoginUtil.updateToken(project, null, account, login, loginSource = GitLabLoginSource.GIT, ::isAccountUnique)
}
else {
LoginResult.Success(account, token)

View File

@@ -28,10 +28,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabConnectedProjectViewModel
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabProjectViewModel
import org.jetbrains.plugins.gitlab.mergerequest.ui.timeline.GitLabMergeRequestTimelineComponentFactory
import org.jetbrains.plugins.gitlab.mergerequest.ui.timeline.GitLabMergeRequestTimelineViewModel
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabProjectViewModel
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestErrorUtil
import org.jetbrains.plugins.gitlab.util.GitLabBundle
import java.beans.PropertyChangeListener
@@ -119,7 +120,8 @@ class GitLabMergeRequestTimelineEditorFactory(private val project: Project, pare
projectVm.accountVm,
swingAction(GitLabBundle.message("merge.request.reload")) {
projectVm.reloadMergeRequestDetails(mergeRequestId)
})
},
GitLabLoginSource.MR_TIMELINE)
val errorPanel = ErrorStatusPanelFactory.create(error, errorPresenter).let {
CollaborationToolsUIUtil.moveToCenter(it)
}

View File

@@ -33,6 +33,7 @@ import net.miginfocom.layout.LC
import net.miginfocom.swing.MigLayout
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountViewModel
import org.jetbrains.plugins.gitlab.mergerequest.action.GitLabMergeRequestActionPlaces
import org.jetbrains.plugins.gitlab.mergerequest.ui.details.model.GitLabCommitViewModel
@@ -138,7 +139,8 @@ object GitLabMergeRequestDetailsComponentFactory {
accountVm,
swingAction(GitLabBundle.message("merge.request.reload")) {
detailsLoadingVm.reloadData()
})
},
GitLabLoginSource.MR_DETAILS)
val errorPanel = ErrorStatusPanelFactory.create(cs, flowOf(loadingState.exception), errorPresenter)
CollaborationToolsUIUtil.moveToCenter(errorPanel)
return errorPanel

View File

@@ -11,6 +11,7 @@ import com.intellij.util.ui.SingleComponentCenteringLayout
import com.intellij.util.ui.StatusText
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountViewModel
import org.jetbrains.plugins.gitlab.mergerequest.ui.filters.GitLabMergeRequestsFiltersValue
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestErrorUtil
@@ -69,7 +70,8 @@ internal class GitLabMergeRequestsListController(
accountVm,
swingAction(GitLabBundle.message("merge.request.list.reload")) {
listVm.reload()
})
},
GitLabLoginSource.MR_LIST)
val errorPanel = ErrorStatusPanelFactory.create(scope, listVm.error, errorPresenter)
return JPanel(SingleComponentCenteringLayout()).apply {

View File

@@ -17,6 +17,7 @@ import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.api.GitLabApiManager
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabAccountsDetailsProvider
@@ -32,7 +33,7 @@ import javax.swing.JComponent
@ApiStatus.Internal
object GitLabMergeRequestSelectorsComponentFactory {
fun createSelectorsComponent(cs: CoroutineScope, selectorVm: GitLabRepositoryAndAccountSelectorViewModel): JComponent {
fun createSelectorsComponent(cs: CoroutineScope, selectorVm: GitLabRepositoryAndAccountSelectorViewModel, loginSource: GitLabLoginSource): JComponent {
val accountsDetailsProvider = GitLabAccountsDetailsProvider(cs, selectorVm.accountManager) { account ->
// TODO: separate loader
@@ -51,7 +52,7 @@ object GitLabMergeRequestSelectorsComponentFactory {
accountsPopupActionsSupplier = { createPopupLoginActions(selectorVm, it) },
submitActionText = GitLabBundle.message("view.merge.requests.button"),
loginButtons = createLoginButtons(cs, selectorVm),
errorPresenter = GitLabSelectorErrorStatusPresenter(selectorVm.project, cs, selectorVm.accountManager) {
errorPresenter = GitLabSelectorErrorStatusPresenter(selectorVm.project, cs, selectorVm.accountManager, loginSource = loginSource) {
selectorVm.submitSelection()
}
)
@@ -60,13 +61,13 @@ object GitLabMergeRequestSelectorsComponentFactory {
selectorVm.loginRequestsFlow.collect { req ->
val account = req.account
if (account == null) {
val (newAccount, token) = GitLabLoginUtil.logInViaToken(selectorVm.project, selectors, req.repo.repository.serverPath) { server, name ->
val (newAccount, token) = GitLabLoginUtil.logInViaToken(selectorVm.project, selectors, req.repo.repository.serverPath, loginSource = loginSource) { server, name ->
GitLabLoginUtil.isAccountUnique(req.accounts, server, name)
}.asSafely<LoginResult.Success>() ?: return@collect
req.login(newAccount, token)
}
else {
val loginResult = GitLabLoginUtil.updateToken(selectorVm.project, selectors, account) { server, name ->
val loginResult = GitLabLoginUtil.updateToken(selectorVm.project, selectors, account, loginSource = loginSource, ) { server, name ->
GitLabLoginUtil.isAccountUnique(req.accounts, server, name)
}.asSafely<LoginResult.Success>() ?: return@collect
req.login(account, loginResult.token)

View File

@@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.launchOnShow
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabProjectViewModel
import org.jetbrains.plugins.gitlab.mergerequest.ui.create.GitLabMergeRequestCreateComponentFactory
import org.jetbrains.plugins.gitlab.mergerequest.ui.details.GitLabMergeRequestDetailsComponentFactory
@@ -61,7 +62,7 @@ internal class GitLabReviewTabComponentFactory(
launchOnShow("SelectorsComponent") {
bindChildIn(this, vm.selectorVm, BorderLayout.NORTH) { selectorVm ->
if (selectorVm == null) return@bindChildIn null
GitLabMergeRequestSelectorsComponentFactory.createSelectorsComponent(this, selectorVm)
GitLabMergeRequestSelectorsComponentFactory.createSelectorsComponent(this, selectorVm, GitLabLoginSource.MR_TW)
}
}
}

View File

@@ -8,6 +8,7 @@ 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.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
import org.jetbrains.plugins.gitlab.exception.GitLabHttpStatusErrorAction
@@ -17,6 +18,7 @@ internal class GitLabSelectorErrorStatusPresenter(
private val project: Project,
private val parentScope: CoroutineScope,
private val accountManager: GitLabAccountManager,
private val loginSource: GitLabLoginSource,
private val resetAction: () -> Unit
) : ErrorStatusPresenter.Text<RepositoryAndAccountSelectorViewModel.Error> {
override fun getErrorTitle(error: RepositoryAndAccountSelectorViewModel.Error): String = when (error) {
@@ -41,7 +43,8 @@ internal class GitLabSelectorErrorStatusPresenter(
project, parentScope,
account = error.account as GitLabAccount,
accountManager = accountManager,
resetAction
loginSource = loginSource,
resetAction = resetAction
)
else -> null
}

View File

@@ -7,6 +7,7 @@ import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
import org.jetbrains.annotations.Nls
import org.jetbrains.plugins.gitlab.api.data.GitLabHttpStatusError.HttpStatusErrorType
import org.jetbrains.plugins.gitlab.api.data.asGitLabStatusError
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountViewModel
import org.jetbrains.plugins.gitlab.util.GitLabBundle
import java.net.ConnectException
@@ -16,6 +17,7 @@ object GitLabMergeRequestErrorUtil {
internal fun createErrorStatusPresenter(
accountVm: GitLabAccountViewModel,
reloadAction: Action?,
loginSource: GitLabLoginSource,
): ErrorStatusPresenter.Text<Throwable> = ErrorStatusPresenter.simple(
GitLabBundle.message("merge.request.list.error"),
descriptionProvider = descriptionProvider@{ error ->
@@ -36,7 +38,7 @@ object GitLabMergeRequestErrorUtil {
is HttpStatusErrorException -> {
val actualError = error.asGitLabStatusError() ?: return@actionProvider null
when (actualError.statusErrorType) {
HttpStatusErrorType.INVALID_TOKEN -> accountVm.loginAction()
HttpStatusErrorType.INVALID_TOKEN -> accountVm.loginAction(loginSource)
HttpStatusErrorType.UNKNOWN -> null
}
}

View File

@@ -27,6 +27,7 @@ import org.jetbrains.plugins.gitlab.api.data.GitLabVisibilityLevel
import org.jetbrains.plugins.gitlab.api.dto.GitLabSnippetBlobAction
import org.jetbrains.plugins.gitlab.api.getResultOrThrow
import org.jetbrains.plugins.gitlab.api.request.getCurrentUser
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
@@ -105,7 +106,7 @@ internal class GitLabSnippetService(private val project: Project, private val se
private suspend fun attemptLogin(accountManager: GitLabAccountManager): Boolean {
return coroutineScope {
async(Dispatchers.Main) {
val (account, token) = GitLabLoginUtil.logInViaToken(project, null) { server, name ->
val (account, token) = GitLabLoginUtil.logInViaToken(project, null, loginSource = GitLabLoginSource.SNIPPET) { server, name ->
GitLabLoginUtil.isAccountUnique(accountManager.accountsState.value, server, name)
}.asSafely<LoginResult.Success>() ?: return@async false
@@ -124,7 +125,7 @@ internal class GitLabSnippetService(private val project: Project, private val se
result: GitLabCreateSnippetResult): GitLabApi? {
return coroutineScope {
async(Dispatchers.EDT) {
val loginResult = GitLabLoginUtil.updateToken(project, null, result.account) { server, name ->
val loginResult = GitLabLoginUtil.updateToken(project, null, result.account, loginSource = GitLabLoginSource.SNIPPET) { server, name ->
GitLabLoginUtil.isAccountUnique(accountManager.accountsState.value, server, name)
}.asSafely<LoginResult.Success>() ?: return@async null

View File

@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.api.dto.GitLabGroupDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabNamespaceDTO
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource
import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil
import org.jetbrains.plugins.gitlab.authentication.LoginResult
import org.jetbrains.plugins.gitlab.util.GitLabBundle
@@ -55,7 +56,7 @@ internal object GitLabShareProjectDialogComponentFactory {
.align(AlignX.FILL).resizableColumn()
link(GitLabBundle.message("share.dialog.account.addButton")) { event ->
val (account, token) = GitLabLoginUtil.logInViaToken(project, event.source as JComponent, uniqueAccountPredicate = { server, username ->
val (account, token) = GitLabLoginUtil.logInViaToken(project, event.source as JComponent, loginSource = GitLabLoginSource.SHARE, uniqueAccountPredicate = { server, username ->
vm.accounts.value.none { it.server == server && it.name == username }
}) as? LoginResult.Success ?: return@link
@@ -68,7 +69,7 @@ internal object GitLabShareProjectDialogComponentFactory {
link(GitLabBundle.message("share.dialog.tokenExpired.label")) { event ->
val account = vm.account.value ?: return@link
val (_, token) = GitLabLoginUtil.updateToken(project, event.source as JComponent, account, uniqueAccountPredicate = { server, username ->
val (_, token) = GitLabLoginUtil.updateToken(project, event.source as JComponent, account, loginSource = GitLabLoginSource.SHARE, uniqueAccountPredicate = { server, username ->
vm.accounts.value.none { it.server == server && it.name == username }
}) as? LoginResult.Success ?: return@link