Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: improve resiliency when resolving IDEs
- catch and treat exceptions
- show a nice message instead of the progress icon telling user that IDEs could not be resolved when something bad happens
- cancel any remaining job when hitting the Back button
- resolves #153
  • Loading branch information
fioan89 committed Jan 26, 2023
commit 4460d7d0d2362e274cc969938af0f3d23fddb3ec
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

## Unreleased

### Fixed
- improved resiliency and error handling when resolving installed IDE's

## 2.1.5 - 2023-01-24

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.coder.gateway.views.steps
import com.coder.gateway.CoderGatewayBundle
import com.coder.gateway.icons.CoderIcons
import com.coder.gateway.models.CoderWorkspacesWizardModel
import com.coder.gateway.models.WorkspaceAgentModel
import com.coder.gateway.sdk.Arch
import com.coder.gateway.sdk.CoderRestClientService
import com.coder.gateway.sdk.OS
Expand Down Expand Up @@ -38,10 +39,13 @@ import com.jetbrains.gateway.ssh.HighLevelHostAccessor
import com.jetbrains.gateway.ssh.IdeStatus
import com.jetbrains.gateway.ssh.IdeWithStatus
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.Component
Expand All @@ -67,6 +71,8 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
private lateinit var tfProject: JBTextField
private lateinit var terminalLink: LazyBrowserLink

private lateinit var ideResolvingJob: Job

override val component = panel {
indent {
row {
Expand Down Expand Up @@ -118,61 +124,73 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
titleLabel.text = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", selectedWorkspace.name)
terminalLink.url = "${coderClient.coderURL}/@${coderClient.me.username}/${selectedWorkspace.name}/terminal"

cs.launch {
logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...")
val hostAccessor = HighLevelHostAccessor.create(
RemoteCredentialsHolder().apply {
setHost("coder.${selectedWorkspace.name}")
userName = "coder"
authType = AuthType.OPEN_SSH
},
true
)
val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers.IO) {
try {
hostAccessor.guessOs()
} catch (e: Exception) {
logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e")
null
}
}
if (workspaceOS == null) {
disableNextAction()
cbIDE.renderer = object : ColoredListCellRenderer<IdeWithStatus>() {
override fun customizeCellRenderer(list: JList<out IdeWithStatus>, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) {
background = UIUtil.getListBackground(isSelected, cellHasFocus)
icon = UIUtil.getBalloonErrorIcon()
append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name))
ideResolvingJob = cs.launch {
try {
retrieveIDES(selectedWorkspace)
} catch (e: Exception) {
when(e) {
is InterruptedException -> Unit
is CancellationException -> Unit
else -> {
logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e")
withContext(Dispatchers.Main) {
disableNextAction()
cbIDE.renderer = object : ColoredListCellRenderer<IdeWithStatus>() {
override fun customizeCellRenderer(list: JList<out IdeWithStatus>, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) {
background = UIUtil.getListBackground(isSelected, cellHasFocus)
icon = UIUtil.getBalloonErrorIcon()
append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name))
}
}
}
}
}
} else {
logger.info("Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS")
val installedIdesJob = async(Dispatchers.IO) {
hostAccessor.getInstalledIDEs().map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.ALREADY_INSTALLED, null, ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
}
val idesWithStatusJob = async(Dispatchers.IO) {
IntelliJPlatformProduct.values()
.filter { it.showInGateway }
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
.map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.DOWNLOAD, ide.download, null, ide.presentableVersion, ide.remoteDevType) }
}
}
}
}

val installedIdes = installedIdesJob.await()
val idesWithStatus = idesWithStatusJob.await()
if (installedIdes.isEmpty()) {
logger.info("No IDE is installed in workspace ${selectedWorkspace.name}")
} else {
ideComboBoxModel.addAll(installedIdes)
cbIDE.selectedIndex = 0
}
private suspend fun retrieveIDES(selectedWorkspace: WorkspaceAgentModel) {
logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...")
val hostAccessor = HighLevelHostAccessor.create(
RemoteCredentialsHolder().apply {
setHost("coder.${selectedWorkspace.name}")
userName = "coder"
authType = AuthType.OPEN_SSH
},
true
)
val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers.IO) {
hostAccessor.guessOs()
}

if (idesWithStatus.isEmpty()) {
logger.warn("Could not resolve any IDE for workspace ${selectedWorkspace.name}, probably $workspaceOS is not supported by Gateway")
} else {
logger.info("Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS")
val installedIdesJob = cs.async(Dispatchers.IO) {
hostAccessor.getInstalledIDEs().map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.ALREADY_INSTALLED, null, ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
}
val idesWithStatusJob = cs.async(Dispatchers.IO) {
IntelliJPlatformProduct.values()
.filter { it.showInGateway }
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
.map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.DOWNLOAD, ide.download, null, ide.presentableVersion, ide.remoteDevType) }
}

ideComboBoxModel.addAll(idesWithStatus)
cbIDE.selectedIndex = 0
}
val installedIdes = installedIdesJob.await()
val idesWithStatus = idesWithStatusJob.await()
if (installedIdes.isEmpty()) {
logger.info("No IDE is installed in workspace ${selectedWorkspace.name}")
} else {
withContext(Dispatchers.Main) {
ideComboBoxModel.addAll(installedIdes)
cbIDE.selectedIndex = 0
}
}

if (idesWithStatus.isEmpty()) {
logger.warn("Could not resolve any IDE for workspace ${selectedWorkspace.name}, probably $workspaceOS is not supported by Gateway")
} else {
withContext(Dispatchers.Main) {
ideComboBoxModel.addAll(idesWithStatus)
cbIDE.selectedIndex = 0
}
}
}
Expand Down Expand Up @@ -213,6 +231,13 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
return true
}

override fun onPrevious() {
super.onPrevious()
cs.launch {
ideResolvingJob.cancelAndJoin()
}
}

override fun dispose() {
cs.cancel()
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/messages/CoderGatewayBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ gateway.connector.view.coder.workspaces.unsupported.os.info=Gateway supports onl
gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version. <a href='https://coder.com/docs/coder-oss/latest/ides/gateway#creating-a-new-jetbrains-gateway-connection'>Connect to a Coder workspace manually</a>
gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version. <a href='https://coder.com/docs/coder-oss/latest/ides/gateway#creating-a-new-jetbrains-gateway-connection'>Connect to a Coder workspace manually</a>
gateway.connector.view.coder.remoteproject.loading.text=Retrieving products...
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered. Please check the logs for more details!
gateway.connector.view.coder.remoteproject.next.text=Start IDE and connect
gateway.connector.view.coder.remoteproject.choose.text=Choose IDE and project for workspace {0}
gateway.connector.recentconnections.title=Recent Coder Workspaces
Expand Down