Skip to content

fix: skip installed EAP, RC, NIGHTLY and PREVIEW ides from showing if they are superseded #548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,11 @@

### Changed

Retrieve workspace directly in link handler when using wildcardSSH feature
- Retrieve workspace directly in link handler when using wildcardSSH feature

### Fixed

- installed EAP, RC, NIGHTLY and PREVIEW IDEs are no longer displayed if there is a higher released version available for download.

## 2.19.0 - 2025-02-21

2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ pluginUntilBuild=251.*
# that exists, ideally the most recent one, for example
# 233.15325-EAP-CANDIDATE-SNAPSHOT).
platformType=GW
platformVersion=233.15619-EAP-CANDIDATE-SNAPSHOT
platformVersion=241.19416-EAP-CANDIDATE-SNAPSHOT
instrumentationCompiler=243.15521-EAP-CANDIDATE-SNAPSHOT
# Gateway does not have open sources.
platformDownloadSources=true
71 changes: 54 additions & 17 deletions src/main/kotlin/com/coder/gateway/models/WorkspaceProjectIDE.kt
Original file line number Diff line number Diff line change
@@ -6,11 +6,14 @@ import com.jetbrains.gateway.ssh.IdeStatus
import com.jetbrains.gateway.ssh.IdeWithStatus
import com.jetbrains.gateway.ssh.InstalledIdeUIEx
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
import com.jetbrains.gateway.ssh.ReleaseType
import com.jetbrains.gateway.ssh.deploy.ShellArgument
import java.net.URL
import java.nio.file.Path
import kotlin.io.path.name

private val NON_STABLE_RELEASE_TYPES = setOf("EAP", "RC", "NIGHTLY", "PREVIEW")

/**
* Validated parameters for downloading and opening a project using an IDE on a
* workspace.
@@ -101,7 +104,8 @@ class WorkspaceProjectIDE(
name = name,
hostname = hostname,
projectPath = projectPath,
ideProduct = IntelliJPlatformProduct.fromProductCode(ideProductCode) ?: throw Exception("invalid product code"),
ideProduct = IntelliJPlatformProduct.fromProductCode(ideProductCode)
?: throw Exception("invalid product code"),
ideBuildNumber = ideBuildNumber,
idePathOnHost = idePathOnHost,
downloadSource = downloadSource,
@@ -126,13 +130,13 @@ fun RecentWorkspaceConnection.toWorkspaceProjectIDE(): WorkspaceProjectIDE {
// connections page, so it could be missing. Try to get it from the
// host name.
name =
if (name.isNullOrBlank() && !hostname.isNullOrBlank()) {
hostname
.removePrefix("coder-jetbrains--")
.removeSuffix("--${hostname.split("--").last()}")
} else {
name
},
if (name.isNullOrBlank() && !hostname.isNullOrBlank()) {
hostname
.removePrefix("coder-jetbrains--")
.removeSuffix("--${hostname.split("--").last()}")
} else {
name
},
hostname = hostname,
projectPath = projectPath,
ideProductCode = ideProductCode,
@@ -146,17 +150,17 @@ fun RecentWorkspaceConnection.toWorkspaceProjectIDE(): WorkspaceProjectIDE {
// the config directory). For backwards compatibility with existing
// entries, extract the URL from the config directory or host name.
deploymentURL =
if (deploymentURL.isNullOrBlank()) {
if (!dir.isNullOrBlank()) {
"https://${Path.of(dir).parent.name}"
} else if (!hostname.isNullOrBlank()) {
"https://${hostname.split("--").last()}"
if (deploymentURL.isNullOrBlank()) {
if (!dir.isNullOrBlank()) {
"https://${Path.of(dir).parent.name}"
} else if (!hostname.isNullOrBlank()) {
"https://${hostname.split("--").last()}"
} else {
deploymentURL
}
} else {
deploymentURL
}
} else {
deploymentURL
},
},
lastOpened = lastOpened,
)
}
@@ -195,6 +199,39 @@ fun AvailableIde.toIdeWithStatus(): IdeWithStatus = IdeWithStatus(
remoteDevType = remoteDevType,
)

/**
* Returns a list of installed IDEs that don't have a RELEASED version available for download.
* Typically, installed EAP, RC, nightly or preview builds should be superseded by released versions.
*/
fun List<InstalledIdeUIEx>.filterOutAvailableReleasedIdes(availableIde: List<AvailableIde>): List<InstalledIdeUIEx> {
val availableReleasedByProductCode = availableIde
.filter { it.releaseType == ReleaseType.RELEASE }
.groupBy { it.product.productCode }
val result = mutableListOf<InstalledIdeUIEx>()

this.forEach { installedIde ->
// installed IDEs have the release type embedded in the presentable version
// which is a string in the form: 2024.2.4 NIGHTLY
if (NON_STABLE_RELEASE_TYPES.any { it in installedIde.presentableVersion }) {
// we can show the installed IDe if there isn't a higher released version available for download
if (installedIde.isSNotSupersededBy(availableReleasedByProductCode[installedIde.product.productCode])) {
result.add(installedIde)
}
} else {
result.add(installedIde)
}
}

return result
}

private fun InstalledIdeUIEx.isSNotSupersededBy(availableIdes: List<AvailableIde>?): Boolean {
if (availableIdes.isNullOrEmpty()) {
return true
}
return !availableIdes.any { it.buildNumber >= this.buildNumber }
}

/**
* Convert an installed IDE to an IDE with status.
*/
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import com.coder.gateway.cli.CoderCLIManager
import com.coder.gateway.icons.CoderIcons
import com.coder.gateway.models.WorkspaceProjectIDE
import com.coder.gateway.models.filterOutAvailableReleasedIdes
import com.coder.gateway.models.toIdeWithStatus
import com.coder.gateway.models.withWorkspaceProject
import com.coder.gateway.sdk.v2.models.Workspace
@@ -82,9 +83,12 @@
import javax.swing.event.DocumentEvent

// Just extracting the way we display the IDE info into a helper function.
private fun displayIdeWithStatus(ideWithStatus: IdeWithStatus): String = "${ideWithStatus.product.productCode} ${ideWithStatus.presentableVersion} ${ideWithStatus.buildNumber} | ${ideWithStatus.status.name.lowercase(
Locale.getDefault(),
)}"
private fun displayIdeWithStatus(ideWithStatus: IdeWithStatus): String =
"${ideWithStatus.product.productCode} ${ideWithStatus.presentableVersion} ${ideWithStatus.buildNumber} | ${
ideWithStatus.status.name.lowercase(
Locale.getDefault(),
)
}"

/**
* View for a single workspace. In particular, show available IDEs and a button
@@ -222,12 +226,21 @@
cbIDE.renderer =
if (attempt > 1) {
IDECellRenderer(
CoderGatewayBundle.message("gateway.connector.view.coder.connect-ssh.retry", attempt),
CoderGatewayBundle.message(
"gateway.connector.view.coder.connect-ssh.retry",
attempt
),
)
} else {
IDECellRenderer(CoderGatewayBundle.message("gateway.connector.view.coder.connect-ssh"))
}
val executor = createRemoteExecutor(CoderCLIManager(data.client.url).getBackgroundHostName(data.workspace, data.client.me, data.agent))
val executor = createRemoteExecutor(
CoderCLIManager(data.client.url).getBackgroundHostName(
data.workspace,
data.client.me,
data.agent
)
)

if (ComponentValidator.getInstance(tfProject).isEmpty) {
logger.info("Installing remote path validator...")
@@ -238,7 +251,10 @@
cbIDE.renderer =
if (attempt > 1) {
IDECellRenderer(
CoderGatewayBundle.message("gateway.connector.view.coder.retrieve-ides.retry", attempt),
CoderGatewayBundle.message(
"gateway.connector.view.coder.retrieve-ides.retry",
attempt
),
)
} else {
IDECellRenderer(CoderGatewayBundle.message("gateway.connector.view.coder.retrieve-ides"))
@@ -247,9 +263,9 @@
},
retryIf = {
it is ConnectionException ||
it is TimeoutException ||
it is SSHException ||
it is DeployException
it is TimeoutException ||
it is SSHException ||
it is DeployException
},
onException = { attempt, nextMs, e ->
logger.error("Failed to retrieve IDEs (attempt $attempt; will retry in $nextMs ms)")
@@ -273,7 +289,7 @@
)

// Check the provided setting to see if there's a default IDE to set.
val defaultIde = ides.find { it ->

Check notice on line 292 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt

GitHub Actions / Qodana Community for JVM

Redundant lambda arrow

Redundant lambda arrow
// Using contains on the displayable version of the ide means they can be as specific or as vague as they want
// CL 2023.3.6 233.15619.8 -> a specific Clion build
// CL 2023.3.6 -> a specific Clion version
@@ -311,7 +327,10 @@
* Validate the remote path whenever it changes.
*/
private fun installRemotePathValidator(executor: HighLevelHostAccessor) {
val disposable = Disposer.newDisposable(ApplicationManager.getApplication(), CoderWorkspaceProjectIDEStepView::class.java.name)
val disposable = Disposer.newDisposable(
ApplicationManager.getApplication(),
CoderWorkspaceProjectIDEStepView::class.java.name
)
ComponentValidator(disposable).installOn(tfProject)

tfProject.document.addDocumentListener(
@@ -324,7 +343,12 @@
val isPathPresent = validateRemotePath(tfProject.text, executor)
if (isPathPresent.pathOrNull == null) {
ComponentValidator.getInstance(tfProject).ifPresent {
it.updateInfo(ValidationInfo("Can't find directory: ${tfProject.text}", tfProject))
it.updateInfo(
ValidationInfo(
"Can't find directory: ${tfProject.text}",
tfProject
)
)
}
} else {
ComponentValidator.getInstance(tfProject).ifPresent {
@@ -333,7 +357,12 @@
}
} catch (e: Exception) {
ComponentValidator.getInstance(tfProject).ifPresent {
it.updateInfo(ValidationInfo("Can't validate directory: ${tfProject.text}", tfProject))
it.updateInfo(
ValidationInfo(
"Can't validate directory: ${tfProject.text}",
tfProject
)
)
}
}
}
@@ -377,27 +406,34 @@
}

logger.info("Resolved OS and Arch for $name is: $workspaceOS")
val installedIdesJob =
cs.async(Dispatchers.IO) {
executor.getInstalledIDEs().map { it.toIdeWithStatus() }
}
val idesWithStatusJob =
cs.async(Dispatchers.IO) {
IntelliJPlatformProduct.entries
.filter { it.showInGateway }
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
.map { it.toIdeWithStatus() }
}
val installedIdesJob = cs.async(Dispatchers.IO) {
executor.getInstalledIDEs()
}
val availableToDownloadIdesJob = cs.async(Dispatchers.IO) {
IntelliJPlatformProduct.entries
.filter { it.showInGateway }
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
}

val installedIdes = installedIdesJob.await()
val availableIdes = availableToDownloadIdesJob.await()

val installedIdes = installedIdesJob.await().sorted()
val idesWithStatus = idesWithStatusJob.await().sorted()
if (installedIdes.isEmpty()) {
logger.info("No IDE is installed in $name")
}
if (idesWithStatus.isEmpty()) {
if (availableIdes.isEmpty()) {
logger.warn("Could not resolve any IDE for $name, probably $workspaceOS is not supported by Gateway")
}
return installedIdes + idesWithStatus

val remainingInstalledIdes = installedIdes.filterOutAvailableReleasedIdes(availableIdes)
if (remainingInstalledIdes.size < installedIdes.size) {
logger.info(
"Skipping the following list of installed IDEs because there is already a released version " +
"available for download: ${(installedIdes - remainingInstalledIdes).joinToString { "${it.product.productCode} ${it.presentableVersion}" }}"

Check notice on line 432 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt

GitHub Actions / Qodana Community for JVM

Argument could be converted to 'Set' to improve performance

The argument can be converted to 'Set' to improve performance
)
}
return remainingInstalledIdes.map { it.toIdeWithStatus() }.sorted() + availableIdes.map { it.toIdeWithStatus() }
.sorted()
}

private fun toDeployedOS(
@@ -455,7 +491,8 @@
override fun getSelectedItem(): IdeWithStatus? = super.getSelectedItem() as IdeWithStatus?
}

private class IDECellRenderer(message: String, cellIcon: Icon = AnimatedIcon.Default.INSTANCE) : ListCellRenderer<IdeWithStatus> {
private class IDECellRenderer(message: String, cellIcon: Icon = AnimatedIcon.Default.INSTANCE) :
ListCellRenderer<IdeWithStatus> {
private val loadingComponentRenderer: ListCellRenderer<IdeWithStatus> =
object : ColoredListCellRenderer<IdeWithStatus>() {
override fun customizeCellRenderer(
336 changes: 336 additions & 0 deletions src/test/kotlin/com/coder/gateway/models/WorkspaceProjectIDETest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
package com.coder.gateway.models

import com.jetbrains.gateway.ssh.AvailableIde
import com.jetbrains.gateway.ssh.Download
import com.jetbrains.gateway.ssh.InstalledIdeUIEx
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.GOIDE
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.IDEA
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.IDEA_IC
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.PYCHARM
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.RUBYMINE
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct.RUSTROVER
import com.jetbrains.gateway.ssh.ReleaseType
import com.jetbrains.gateway.ssh.ReleaseType.EAP
import com.jetbrains.gateway.ssh.ReleaseType.NIGHTLY
import com.jetbrains.gateway.ssh.ReleaseType.PREVIEW
import com.jetbrains.gateway.ssh.ReleaseType.RC
import com.jetbrains.gateway.ssh.ReleaseType.RELEASE
import org.junit.jupiter.api.DisplayName
import java.net.URL
import kotlin.test.Test
import kotlin.test.assertContains
@@ -125,4 +142,323 @@ internal class WorkspaceProjectIDETest {
},
)
}

@Test
@DisplayName("test that installed IDEs filter returns an empty list when there are available IDEs but none are installed")
fun testFilterOutWhenNoIdeIsInstalledButAvailableIsPopulated() {
assertEquals(
emptyList(), emptyList<InstalledIdeUIEx>().filterOutAvailableReleasedIdes(
listOf(
availableIde(IDEA, "242.23726.43", EAP),
availableIde(IDEA_IC, "251.23726.43", RELEASE)
)
)
)
}

@Test
@DisplayName("test that unreleased installed IDEs are not filtered out when available list of IDEs is empty")
fun testFilterOutAvailableReleaseIdesWhenAvailableIsEmpty() {
// given an eap installed ide
val installedEAPs = listOf(installedIde(IDEA, "242.23726.43", EAP))

// expect
assertEquals(installedEAPs, installedEAPs.filterOutAvailableReleasedIdes(emptyList()))

// given an RC installed ide
val installedRCs = listOf(installedIde(RUSTROVER, "243.63726.48", RC))

// expect
assertEquals(installedRCs, installedRCs.filterOutAvailableReleasedIdes(emptyList()))

// given a preview installed ide
val installedPreviews = listOf(installedIde(IDEA_IC, "244.63726.48", ReleaseType.PREVIEW))

// expect
assertEquals(installedPreviews, installedPreviews.filterOutAvailableReleasedIdes(emptyList()))

// given a nightly installed ide
val installedNightlys = listOf(installedIde(RUBYMINE, "244.63726.48", NIGHTLY))

// expect
assertEquals(installedNightlys, installedNightlys.filterOutAvailableReleasedIdes(emptyList()))
}

@Test
@DisplayName("test that unreleased EAP ides are superseded by available RELEASED ides with the same or higher build number")
fun testUnreleasedAndInstalledEAPIdesAreSupersededByAvailableReleasedWithSameOrHigherBuildNr() {
// given an eap installed ide
val installedEapIdea = installedIde(IDEA, "242.23726.43", EAP)
val installedReleasedRustRover = installedIde(RUSTROVER, "251.55667.23", RELEASE)
// and a released idea with same build number
val availableReleasedIdeaWithSameBuild = availableIde(IDEA, "242.23726.43", RELEASE)

// expect the installed eap idea to be filtered out
assertEquals(
listOf(installedReleasedRustRover),
listOf(installedEapIdea, installedReleasedRustRover).filterOutAvailableReleasedIdes(
listOf(
availableReleasedIdeaWithSameBuild
)
)
)

// given a released idea with higher build number
val availableIdeaWithHigherBuild = availableIde(IDEA, "243.21726.43", RELEASE)

// expect the installed eap idea to be filtered out
assertEquals(
listOf(installedReleasedRustRover),
listOf(installedEapIdea, installedReleasedRustRover).filterOutAvailableReleasedIdes(
listOf(
availableIdeaWithHigherBuild
)
)
)
}

@Test
@DisplayName("test that unreleased RC ides are superseded by available RELEASED ides with the same or higher build number")
fun testUnreleasedAndInstalledRCIdesAreSupersededByAvailableReleasedWithSameOrHigherBuildNr() {
// given an RC installed ide
val installedRCRustRover = installedIde(RUSTROVER, "242.23726.43", RC)
val installedReleasedGoLand = installedIde(GOIDE, "251.55667.23", RELEASE)
// and a released idea with same build number
val availableReleasedRustRoverWithSameBuild = availableIde(RUSTROVER, "242.23726.43", RELEASE)

// expect the installed RC rust rover to be filtered out
assertEquals(
listOf(installedReleasedGoLand),
listOf(installedRCRustRover, installedReleasedGoLand).filterOutAvailableReleasedIdes(
listOf(
availableReleasedRustRoverWithSameBuild
)
)
)

// given a released rust rover with higher build number
val availableRustRoverWithHigherBuild = availableIde(RUSTROVER, "243.21726.43", RELEASE)

// expect the installed RC rust rover to be filtered out
assertEquals(
listOf(installedReleasedGoLand),
listOf(installedRCRustRover, installedReleasedGoLand).filterOutAvailableReleasedIdes(
listOf(
availableRustRoverWithHigherBuild
)
)
)
}

@Test
@DisplayName("test that unreleased PREVIEW ides are superseded by available RELEASED ides with the same or higher build number")
fun testUnreleasedAndInstalledPreviewIdesAreSupersededByAvailableReleasedWithSameOrHigherBuildNr() {
// given a PREVIEW installed ide
val installedPreviewRubyMine = installedIde(RUBYMINE, "242.23726.43", PREVIEW)
val installedReleasedIntelliJCommunity = installedIde(IDEA_IC, "251.55667.23", RELEASE)
// and a released ruby mine with same build number
val availableReleasedRubyMineWithSameBuild = availableIde(RUBYMINE, "242.23726.43", RELEASE)

// expect the installed PREVIEW idea to be filtered out
assertEquals(
listOf(installedReleasedIntelliJCommunity),
listOf(installedPreviewRubyMine, installedReleasedIntelliJCommunity).filterOutAvailableReleasedIdes(
listOf(
availableReleasedRubyMineWithSameBuild
)
)
)

// given a released ruby mine with higher build number
val availableRubyMineWithHigherBuild = availableIde(RUBYMINE, "243.21726.43", RELEASE)

// expect the installed PREVIEW ruby mine to be filtered out
assertEquals(
listOf(installedReleasedIntelliJCommunity),
listOf(installedPreviewRubyMine, installedReleasedIntelliJCommunity).filterOutAvailableReleasedIdes(
listOf(
availableRubyMineWithHigherBuild
)
)
)
}

@Test
@DisplayName("test that unreleased NIGHTLY ides are superseded by available RELEASED ides with the same or higher build number")
fun testUnreleasedAndInstalledNightlyIdesAreSupersededByAvailableReleasedWithSameOrHigherBuildNr() {
// given a NIGHTLY installed ide
val installedNightlyPyCharm = installedIde(PYCHARM, "242.23726.43", NIGHTLY)
val installedReleasedRubyMine = installedIde(RUBYMINE, "251.55667.23", RELEASE)
// and a released pycharm with same build number
val availableReleasedPyCharmWithSameBuild = availableIde(PYCHARM, "242.23726.43", RELEASE)

// expect the installed NIGHTLY pycharm to be filtered out
assertEquals(
listOf(installedReleasedRubyMine),
listOf(installedNightlyPyCharm, installedReleasedRubyMine).filterOutAvailableReleasedIdes(
listOf(
availableReleasedPyCharmWithSameBuild
)
)
)

// given a released pycharm with higher build number
val availablePyCharmWithHigherBuild = availableIde(PYCHARM, "243.21726.43", RELEASE)

// expect the installed NIGHTLY pycharm to be filtered out
assertEquals(
listOf(installedReleasedRubyMine),
listOf(installedNightlyPyCharm, installedReleasedRubyMine).filterOutAvailableReleasedIdes(
listOf(
availablePyCharmWithHigherBuild
)
)
)
}

@Test
@DisplayName("test that unreleased installed ides are NOT superseded by available unreleased IDEs with higher build numbers")
fun testUnreleasedIdesAreNotSupersededByAvailableUnreleasedIdesWithHigherBuildNr() {
// given installed and unreleased ides
val installedEap = listOf(installedIde(RUSTROVER, "203.87675.5", EAP))
val installedRC = listOf(installedIde(RUSTROVER, "203.87675.5", RC))
val installedPreview = listOf(installedIde(RUSTROVER, "203.87675.5", PREVIEW))
val installedNightly = listOf(installedIde(RUSTROVER, "203.87675.5", NIGHTLY))

// and available unreleased ides
val availableHigherAndUnreleasedIdes = listOf(
availableIde(RUSTROVER, "204.34567.1", EAP),
availableIde(RUSTROVER, "205.45678.2", RC),
availableIde(RUSTROVER, "206.24667.3", PREVIEW),
availableIde(RUSTROVER, "207.24667.4", NIGHTLY),
)

assertEquals(
installedEap,
installedEap.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedRC,
installedRC.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedPreview,
installedPreview.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedNightly,
installedNightly.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
}

@Test
@DisplayName("test that unreleased installed ides are NOT superseded by available unreleased IDEs with same major number but higher minor build numbers")
fun testUnreleasedIdesAreNotSupersededByAvailableUnreleasedIdesWithSameMajorButHigherMinorBuildNr() {
// given installed and unreleased ides
val installedEap = listOf(installedIde(RUSTROVER, "203.12345.5", EAP))
val installedRC = listOf(installedIde(RUSTROVER, "203.12345.5", RC))
val installedPreview = listOf(installedIde(RUSTROVER, "203.12345.5", PREVIEW))
val installedNightly = listOf(installedIde(RUSTROVER, "203.12345.5", NIGHTLY))

// and available unreleased ides
val availableHigherAndUnreleasedIdes = listOf(
availableIde(RUSTROVER, "203.34567.1", EAP),
availableIde(RUSTROVER, "203.45678.2", RC),
availableIde(RUSTROVER, "203.24667.3", PREVIEW),
availableIde(RUSTROVER, "203.24667.4", NIGHTLY),
)

assertEquals(
installedEap,
installedEap.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedRC,
installedRC.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedPreview,
installedPreview.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedNightly,
installedNightly.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
}

@Test
@DisplayName("test that unreleased installed ides are NOT superseded by available unreleased IDEs with same major and minor number but higher patch numbers")
fun testUnreleasedIdesAreNotSupersededByAvailableUnreleasedIdesWithSameMajorAndMinorButHigherPatchNr() {
// given installed and unreleased ides
val installedEap = listOf(installedIde(RUSTROVER, "203.12345.1", EAP))
val installedRC = listOf(installedIde(RUSTROVER, "203.12345.1", RC))
val installedPreview = listOf(installedIde(RUSTROVER, "203.12345.1", PREVIEW))
val installedNightly = listOf(installedIde(RUSTROVER, "203.12345.1", NIGHTLY))

// and available unreleased ides
val availableHigherAndUnreleasedIdes = listOf(
availableIde(RUSTROVER, "203.12345.2", EAP),
availableIde(RUSTROVER, "203.12345.3", RC),
availableIde(RUSTROVER, "203.12345.4", PREVIEW),
availableIde(RUSTROVER, "203.12345.5", NIGHTLY),
)

assertEquals(
installedEap,
installedEap.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedRC,
installedRC.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedPreview,
installedPreview.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
assertEquals(
installedNightly,
installedNightly.filterOutAvailableReleasedIdes(availableHigherAndUnreleasedIdes)
)
}

companion object {
private val fakeDownload = Download(
"https://download.jetbrains.com/idea/ideaIU-2024.1.7.tar.gz",
1328462259,
"https://download.jetbrains.com/idea/ideaIU-2024.1.7.tar.gz.sha256"
)

private fun installedIde(
product: IntelliJPlatformProduct,
buildNumber: String,
releaseType: ReleaseType
): InstalledIdeUIEx {
return InstalledIdeUIEx(
product,
buildNumber,
"/home/coder/.cache/JetBrains/",
toPresentableVersion(buildNumber) + " " + releaseType.toString()
)
}

private fun availableIde(
product: IntelliJPlatformProduct,
buildNumber: String,
releaseType: ReleaseType
): AvailableIde {
return AvailableIde(
product,
buildNumber,
fakeDownload,
toPresentableVersion(buildNumber) + " " + releaseType.toString(),
null,
releaseType
)
}

private fun toPresentableVersion(buildNr: String): String {

return "20" + buildNr.substring(0, 2) + "." + buildNr.substring(2, 3)
}
}
}

Unchanged files with check annotations Beta

indicator,
)
var workspace : Workspace

Check warning on line 68 in src/main/kotlin/com/coder/gateway/util/LinkHandler.kt

GitHub Actions / Qodana Community for JVM

Local 'var' is never modified and can be declared as 'val'

Variable is never modified, so it can be declared using 'val'
var workspaces : List<Workspace> = emptyList()
var workspacesAndAgents : Set<Pair<Workspace, WorkspaceAgent>> = emptySet()
if (cli.features.wildcardSSH) {
coderConfigPath.toString(),
"start",
"--yes",
workspaceOwner + "/" + workspaceName,

Check notice on line 487 in src/main/kotlin/com/coder/gateway/cli/CoderCLIManager.kt

GitHub Actions / Qodana Community for JVM

String concatenation that can be converted to string template

'String' concatenation can be converted to a template
)
private fun exec(vararg args: String): String {
/*
* This function returns the ssh-host-prefix used for Host entries.
*/
fun getHostPrefix(): String = "coder-jetbrains-${deploymentURL.safeHost()}"

Check notice on line 521 in src/main/kotlin/com/coder/gateway/cli/CoderCLIManager.kt

GitHub Actions / Qodana Community for JVM

Class member can have 'private' visibility

Function 'getHostPrefix' could be private
/**
* This function returns the ssh host name generated for connecting to the workspace.
}
// non-wildcard case
if (parts[0] == "coder-jetbrains") {
return hostname + "--bg"

Check notice on line 577 in src/main/kotlin/com/coder/gateway/cli/CoderCLIManager.kt

GitHub Actions / Qodana Community for JVM

String concatenation that can be converted to string template

'String' concatenation can be converted to a template
}
// wildcard case
parts[0] += "-bg"
init {
init()
title = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", CoderCLIManager.getWorkspaceParts(state.workspace, state.agent))

Check warning on line 41 in src/main/kotlin/com/coder/gateway/util/Dialogs.kt

GitHub Actions / Qodana Community for JVM

Incorrect string capitalization

String 'Choose IDE and project for workspace {0}' is not properly capitalized. It should have title capitalization
}
override fun show() {
}
private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentListModel?): String? = if (workspace == null) {

Check warning on line 913 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

GitHub Actions / Qodana Community for JVM

Redundant nullable return type

'valueOf' always returns non-null type
"Unknown"
} else if (workspace.workspace.outdated) {
"Outdated"