From 2d2403d7fa9b65496fdf208404ca2c1876613760 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 14:14:50 +0200 Subject: [PATCH 1/9] perf: reduce the number of REST API calls - instead of resolving the template for each workspace and then extract agent information, we can rely on workspace.latest_build.resources to retrieve the agents for the running instances - this greatly reduces the number of calls to the REST API, to basically only one call. - icons are retrieved asynchronously --- .../gateway/models/WorkspaceAgentModel.kt | 3 +- .../gateway/sdk/TemplateIconDownloader.kt | 9 +- .../views/steps/CoderWorkspacesStepView.kt | 223 +++++++++--------- 3 files changed, 122 insertions(+), 113 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt index 80b65e57..3a2bdbf8 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt @@ -12,7 +12,8 @@ data class WorkspaceAgentModel( val name: String, val templateID: UUID, val templateName: String, - val templateIcon: Icon, + val templateIconPath: String, + var templateIcon: Icon?, val status: WorkspaceVersionStatus, val agentStatus: WorkspaceAgentStatus, val lastBuildTransition: WorkspaceTransition, diff --git a/src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt b/src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt index c4fe0db4..4b23fa6c 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt @@ -13,6 +13,7 @@ import javax.swing.Icon @Service(Service.Level.APP) class TemplateIconDownloader { private val coderClient: CoderRestClientService = service() + private val cache = mutableMapOf, Icon>() fun load(path: String, templateName: String): Icon { var url: URL? = null @@ -23,9 +24,15 @@ class TemplateIconDownloader { } if (url != null) { + val cachedIcon = cache[Pair(templateName, path)] + if (cachedIcon != null) { + return cachedIcon + } var img = ImageLoader.loadFromUrl(url) if (img != null) { - return IconUtil.toRetinaAwareIcon(Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.ULTRA_QUALITY, 32)) + val icon = IconUtil.toRetinaAwareIcon(Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.ULTRA_QUALITY, 32)) + cache[Pair(templateName, path)] = icon + return icon } } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 66f3753d..e11d7469 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -36,6 +36,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task +import com.intellij.openapi.rd.util.launchUnderBackgroundProgress import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.AnActionButton @@ -57,6 +58,7 @@ import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI import com.intellij.util.ui.ListTableModel import com.intellij.util.ui.table.IconTableCellRenderer +import com.jetbrains.rd.util.lifetime.LifetimeDefinition import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -64,7 +66,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.zeroturnaround.exec.ProcessExecutor import java.awt.Component @@ -123,7 +124,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : enableNextButtonCallback(selectedObject != null && selectedObject?.agentStatus == RUNNING && selectedObject?.agentOS == OS.LINUX) if (selectedObject?.agentOS != OS.LINUX) { notificationBanner.apply { - isVisible = true + component.isVisible = true showInfo(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.unsupported.os.info")) } } else { @@ -391,62 +392,56 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : appPropertiesService.setValue(SESSION_TOKEN, token) val cliManager = CoderCLIManager(localWizardModel.coderURL.toURL(), coderClient.buildVersion) - localWizardModel.apply { this.token = token buildVersion = coderClient.buildVersion localCliPath = cliManager.localCli.toAbsolutePath().toString() } - val authTask = object : Task.Modal(null, CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), false) { - override fun run(pi: ProgressIndicator) { - pi.apply { - isIndeterminate = false - text = "Retrieving Workspaces..." - fraction = 0.1 - } - runBlocking { - loadWorkspaces() - } + LifetimeDefinition().launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), canBeCancelled = false, isIndeterminate = true) { + this.indicator.apply { + isIndeterminate = false + text = "Retrieving Workspaces..." + fraction = 0.1 + } - pi.apply { - isIndeterminate = false - text = "Downloading Coder CLI..." - fraction = 0.3 - } + withContext(Dispatchers.IO) { + loadWorkspaces() + } - cliManager.downloadCLI() - if (getOS() != OS.WINDOWS) { - pi.fraction = 0.4 - val chmodOutput = ProcessExecutor().command("chmod", "+x", localWizardModel.localCliPath).readOutput(true).execute().outputUTF8() - logger.info("chmod +x ${cliManager.localCli.toAbsolutePath()} $chmodOutput") - } - pi.apply { - text = "Configuring Coder CLI..." - fraction = 0.5 - } + this.indicator.apply { + isIndeterminate = false + text = "Downloading Coder CLI..." + fraction = 0.3 + } - val loginOutput = ProcessExecutor().command(localWizardModel.localCliPath, "login", localWizardModel.coderURL, "--token", localWizardModel.token).readOutput(true).execute().outputUTF8() - logger.info("coder-cli login output: $loginOutput") - pi.fraction = 0.8 - val sshConfigOutput = ProcessExecutor().command(localWizardModel.localCliPath, "config-ssh", "--yes", "--use-previous-options").readOutput(true).execute().outputUTF8() - logger.info("Result of `${localWizardModel.localCliPath} config-ssh --yes --use-previous-options`: $sshConfigOutput") + cliManager.downloadCLI() + if (getOS() != OS.WINDOWS) { + this.indicator.fraction = 0.4 + val chmodOutput = ProcessExecutor().command("chmod", "+x", localWizardModel.localCliPath).readOutput(true).execute().outputUTF8() + logger.info("chmod +x ${cliManager.localCli.toAbsolutePath()} $chmodOutput") + } + this.indicator.apply { + text = "Configuring Coder CLI..." + fraction = 0.5 + } - pi.apply { - text = "Remove old Coder CLI versions..." - fraction = 0.9 - } - cliManager.removeOldCli() + val loginOutput = ProcessExecutor().command(localWizardModel.localCliPath, "login", localWizardModel.coderURL, "--token", localWizardModel.token).readOutput(true).execute().outputUTF8() + logger.info("coder-cli login output: $loginOutput") + this.indicator.fraction = 0.8 + val sshConfigOutput = ProcessExecutor().command(localWizardModel.localCliPath, "config-ssh", "--yes", "--use-previous-options").readOutput(true).execute().outputUTF8() + logger.info("Result of `${localWizardModel.localCliPath} config-ssh --yes --use-previous-options`: $sshConfigOutput") - pi.fraction = 1.0 + this.indicator.apply { + text = "Remove old Coder CLI versions..." + fraction = 0.9 } - } + cliManager.removeOldCli() - cs.launch { - ProgressManager.getInstance().run(authTask) + this.indicator.fraction = 1.0 + updateWorkspaceActions() + triggerWorkspacePolling() } - updateWorkspaceActions() - triggerWorkspacePolling() } private fun askToken(): String? { @@ -483,75 +478,66 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } private suspend fun loadWorkspaces() { - withContext(Dispatchers.IO) { + val ws = withContext(Dispatchers.IO) { val timeBeforeRequestingWorkspaces = System.currentTimeMillis() try { val ws = coderClient.workspaces() + val ams = ws.flatMap { it.toAgentModels() }.toSet() val timeAfterRequestingWorkspaces = System.currentTimeMillis() logger.info("Retrieving the workspaces took: ${timeAfterRequestingWorkspaces - timeBeforeRequestingWorkspaces} millis") - ws.resolveAndDisplayAgents() + return@withContext ams } catch (e: Exception) { logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") + emptySet() + } + } + withContext(Dispatchers.Main) { + val selectedWorkspace = tableOfWorkspaces.selectedObject?.name + listTableModelOfWorkspaces.items = ws.toList() + if (selectedWorkspace != null) { + tableOfWorkspaces.selectItem(selectedWorkspace) } } } - private fun List.resolveAndDisplayAgents() { - this.forEach { workspace -> - cs.launch(Dispatchers.IO) { - val timeBeforeRequestingAgents = System.currentTimeMillis() - workspace.agentModels().forEach { am -> + private fun Workspace.toAgentModels(): Set { + return when (this.latestBuild.resources.size) { + 0 -> { + val wm = WorkspaceAgentModel( + this.id, + this.name, + this.name, + this.templateID, + this.templateName, + this.templateIcon, + null, + WorkspaceVersionStatus.from(this), + WorkspaceAgentStatus.from(this), + this.latestBuild.transition, + null, + null, + null + ) + cs.launch(Dispatchers.IO) { + wm.templateIcon = iconDownloader.load(wm.templateIconPath, wm.templateName) withContext(Dispatchers.Main) { - val selectedWorkspace = tableOfWorkspaces.selectedObject?.name - if (listTableModelOfWorkspaces.indexOf(am) >= 0) { - val index = listTableModelOfWorkspaces.indexOf(am) - listTableModelOfWorkspaces.setItem(index, am) - } else { - listTableModelOfWorkspaces.addRow(am) - } - if (selectedWorkspace != null) { - tableOfWorkspaces.selectItem(selectedWorkspace) - } + tableOfWorkspaces.updateUI() } } - val timeAfterRequestingAgents = System.currentTimeMillis() - logger.info("Retrieving the agents for ${workspace.name} took: ${timeAfterRequestingAgents - timeBeforeRequestingAgents} millis") + setOf(wm) } - } - } - - private fun Workspace.agentModels(): List { - return try { - val agents = coderClient.workspaceAgentsByTemplate(this) - when (agents.size) { - 0 -> { - listOf( - WorkspaceAgentModel( - this.id, - this.name, - this.name, - this.templateID, - this.templateName, - iconDownloader.load(this@agentModels.templateIcon, this.name), - WorkspaceVersionStatus.from(this), - WorkspaceAgentStatus.from(this), - this.latestBuild.transition, - null, - null, - null - ) - ) - } - else -> agents.map { agent -> + else -> { + val wam = this.latestBuild.resources.filter { it.agents != null }.flatMap { it.agents!! }.map { agent -> val workspaceWithAgentName = "${this.name}.${agent.name}" - WorkspaceAgentModel( + val wm = WorkspaceAgentModel( this.id, this.name, workspaceWithAgentName, this.templateID, this.templateName, - iconDownloader.load(this@agentModels.templateIcon, workspaceWithAgentName), + this.templateIcon, + null, WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), this.latestBuild.transition, @@ -559,26 +545,41 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : Arch.from(agent.architecture), agent.directory ) - }.toList() + cs.launch(Dispatchers.IO) { + wm.templateIcon = iconDownloader.load(wm.templateIconPath, wm.templateName) + withContext(Dispatchers.Main) { + tableOfWorkspaces.updateUI() + } + } + wm + }.toSet() + + if (wam.isNullOrEmpty()) { + val wm = WorkspaceAgentModel( + this.id, + this.name, + this.name, + this.templateID, + this.templateName, + this.templateIcon, + null, + WorkspaceVersionStatus.from(this), + WorkspaceAgentStatus.from(this), + this.latestBuild.transition, + null, + null, + null + ) + cs.launch(Dispatchers.IO) { + wm.templateIcon = iconDownloader.load(wm.templateIconPath, wm.templateName) + withContext(Dispatchers.Main) { + tableOfWorkspaces.updateUI() + } + } + return setOf(wm) + } + return wam } - } catch (e: Exception) { - logger.warn("Agent(s) for ${this.name} could not be retrieved. Reason: $e") - listOf( - WorkspaceAgentModel( - this.id, - this.name, - this.name, - this.templateID, - this.templateName, - iconDownloader.load(this@agentModels.templateIcon, this.name), - WorkspaceVersionStatus.from(this), - WorkspaceAgentStatus.from(this), - this.latestBuild.transition, - null, - null, - null - ) - ) } } @@ -627,7 +628,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : private class WorkspaceIconColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { - return workspace?.agentOS?.name + return workspace?.templateName } override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { From 4927e4177a1d2bcd2ddf85488dd00388c48490fb Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 14:16:22 +0200 Subject: [PATCH 2/9] fix: map "darwin" agents as macOS --- CHANGELOG.md | 3 ++- src/main/kotlin/com/coder/gateway/sdk/os.kt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3a2a37..c1c96b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ - workspaces and agents are now resolved and displayed progressively ### Fixed -- icon rendering on macOS +- icon rendering on `macOS` +- `darwin` agents are now recognized as `macOS` ## 2.1.3 - 2022-12-09 diff --git a/src/main/kotlin/com/coder/gateway/sdk/os.kt b/src/main/kotlin/com/coder/gateway/sdk/os.kt index c8682b32..9a272a98 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/os.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/os.kt @@ -1,11 +1,13 @@ package com.coder.gateway.sdk +import java.util.Locale + fun getOS(): OS? { return OS.from(System.getProperty("os.name")) } fun getArch(): Arch? { - return Arch.from(System.getProperty("os.arch").toLowerCase()) + return Arch.from(System.getProperty("os.arch").lowercase(Locale.getDefault())) } enum class OS { @@ -22,7 +24,7 @@ enum class OS { LINUX } - os.contains("mac", true) -> { + os.contains("mac", true) || os.contains("darwin", true) -> { MAC } From db7e35178eaf12f61ba55db685dcab6058e44bc3 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 14:20:36 +0200 Subject: [PATCH 3/9] fix: display unsupported OS message only when workspace is running --- CHANGELOG.md | 1 + .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c96b90..9372fbe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixed - icon rendering on `macOS` - `darwin` agents are now recognized as `macOS` +- unsupported OS warning is displayed only for running workspaces ## 2.1.3 - 2022-12-09 diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index e11d7469..b0975a74 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -122,7 +122,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.addListSelectionListener { enableNextButtonCallback(selectedObject != null && selectedObject?.agentStatus == RUNNING && selectedObject?.agentOS == OS.LINUX) - if (selectedObject?.agentOS != OS.LINUX) { + if (selectedObject?.agentStatus == RUNNING && selectedObject?.agentOS != OS.LINUX) { notificationBanner.apply { component.isVisible = true showInfo(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.unsupported.os.info")) From 1793f917d8cd79fee205e24cca901b0759c7799f Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 14:22:19 +0200 Subject: [PATCH 4/9] fix: don't wait for icons to be loaded --- .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index b0975a74..bee75e69 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -405,9 +405,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : fraction = 0.1 } - withContext(Dispatchers.IO) { - loadWorkspaces() - } + loadWorkspaces() this.indicator.apply { isIndeterminate = false From 72f30f0f829f882a74834e64384a0e8454b002d2 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 15:04:49 +0200 Subject: [PATCH 5/9] impl: sort table by workspace name, template name or status --- CHANGELOG.md | 1 + .../views/steps/CoderWorkspacesStepView.kt | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9372fbe1..f5dd7751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Added - ability to open a template in the Dashboard +- ability to sort by workspace name, or by template name or by workspace status ### Changed - renamed the plugin from `Coder Gateway` to `Gateway` diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index bee75e69..cb3a504b 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -656,6 +656,16 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : return workspace?.name } + override fun getComparator(): Comparator? { + return Comparator { a, b -> + if (a === b) 0 + if (a == null) -1 + if (b == null) 1 + + a.name.compareTo(b.name, ignoreCase = true) + } + } + override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { return object : DefaultTableCellRenderer() { override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component { @@ -676,6 +686,16 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : return workspace?.templateName } + override fun getComparator(): java.util.Comparator? { + return Comparator { a, b -> + if (a === b) 0 + if (a == null) -1 + if (b == null) 1 + + a.templateName.compareTo(b.templateName, ignoreCase = true) + } + } + override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { val simpleH3 = JBFont.h3() @@ -729,6 +749,16 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : return workspace?.agentStatus?.label } + override fun getComparator(): java.util.Comparator? { + return Comparator { a, b -> + if (a === b) 0 + if (a == null) -1 + if (b == null) 1 + + a.agentStatus.label.compareTo(b.agentStatus.label, ignoreCase = true) + } + } + override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { return object : DefaultTableCellRenderer() { override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component { From 8707ce7ce9bcb66ba200e528dd1f600a7d7464cc Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 15:36:28 +0200 Subject: [PATCH 6/9] fix: same font size for row like in the header - resolves #132 --- .../views/steps/CoderWorkspacesStepView.kt | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index cb3a504b..4a5de488 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -42,6 +42,7 @@ import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.AnActionButton import com.intellij.ui.AppIcon import com.intellij.ui.JBColor +import com.intellij.ui.RelativeFont import com.intellij.ui.ToolbarDecorator import com.intellij.ui.components.JBTextField import com.intellij.ui.components.dialog @@ -643,7 +644,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : override fun getTableCellRendererComponent(table: JTable?, value: Any?, selected: Boolean, focus: Boolean, row: Int, column: Int): Component { super.getTableCellRendererComponent(table, value, selected, focus, row, column).apply { - border = JBUI.Borders.empty(10) + border = JBUI.Borders.empty(8, 8) } return this } @@ -651,12 +652,12 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private inner class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { return workspace?.name } - override fun getComparator(): Comparator? { + override fun getComparator(): Comparator { return Comparator { a, b -> if (a === b) 0 if (a == null) -1 @@ -673,20 +674,21 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : if (value is String) { text = value } - font = JBFont.h3().asBold() - border = JBUI.Borders.empty() + + font = RelativeFont.BOLD.derive(this@CoderWorkspacesStepView.tableOfWorkspaces.tableHeader.font) + border = JBUI.Borders.empty(0, 8) return this } } } } - private class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private inner class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { return workspace?.templateName } - override fun getComparator(): java.util.Comparator? { + override fun getComparator(): java.util.Comparator { return Comparator { a, b -> if (a === b) 0 if (a == null) -1 @@ -697,7 +699,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { - val simpleH3 = JBFont.h3() + val simpleH3 = this@CoderWorkspacesStepView.tableOfWorkspaces.tableHeader.font val h3AttributesWithUnderlining = simpleH3.attributes as MutableMap h3AttributesWithUnderlining[TextAttribute.UNDERLINE] = UNDERLINE_ON @@ -708,7 +710,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : if (value is String) { text = value } - border = JBUI.Borders.empty() + border = JBUI.Borders.empty(0, 8) if (table.getClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW) != null) { val mouseOverRow = table.getClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW) as Int @@ -724,7 +726,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo(columnName) { + private inner class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { return workspace?.status?.label } @@ -736,20 +738,20 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : if (value is String) { text = value } - font = JBFont.h3() - border = JBUI.Borders.empty() + font = this@CoderWorkspacesStepView.tableOfWorkspaces.tableHeader.font + border = JBUI.Borders.empty(0, 8) return this } } } } - private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo(columnName) { + private inner class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { return workspace?.agentStatus?.label } - override fun getComparator(): java.util.Comparator? { + override fun getComparator(): java.util.Comparator { return Comparator { a, b -> if (a === b) 0 if (a == null) -1 @@ -766,8 +768,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : if (value is String) { text = value } - font = JBFont.h3() - border = JBUI.Borders.empty() + font = this@CoderWorkspacesStepView.tableOfWorkspaces.tableHeader.font + border = JBUI.Borders.empty(0, 8) foreground = (table.model as ListTableModel).getRowValue(row).statusColor() return this } From 1ea9ff427337e68367d559ddbedcbd862d03eb2e Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 21:40:32 +0200 Subject: [PATCH 7/9] impl: request a new toke when old one is expired - resolves #130 --- CHANGELOG.md | 1 + .../coder/gateway/views/steps/CoderWorkspacesStepView.kt | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5dd7751..3542a403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Added - ability to open a template in the Dashboard - ability to sort by workspace name, or by template name or by workspace status +- a new token is requested when the one persisted is expired ### Changed - renamed the plugin from `Coder Gateway` to `Gateway` diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 4a5de488..960ec47d 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -319,7 +319,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : tfUrl?.text = url poller?.cancel() - loginAndLoadWorkspace(token) + try { + coderClient.initClientSession(url.toURL(), token) + loginAndLoadWorkspace(token) + } catch (e: AuthenticationResponseException) { + // probably the token is expired + askTokenAndOpenSession() + } } } updateWorkspaceActions() From e1bcd86f5bfb424c4942471ed1618559ab5d2c56 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 21:56:36 +0200 Subject: [PATCH 8/9] fix: show status color for sorted row - instead of the original row --- .../com/coder/gateway/models/WorkspaceAgentStatus.kt | 9 +++++++++ .../coder/gateway/views/steps/CoderWorkspacesStepView.kt | 9 +-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt index cdb67a3e..44a71614 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt @@ -3,12 +3,19 @@ package com.coder.gateway.models import com.coder.gateway.sdk.v2.models.ProvisionerJobStatus import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceTransition +import com.intellij.ui.JBColor enum class WorkspaceAgentStatus(val label: String) { QUEUED("◍ Queued"), STARTING("⦿ Starting"), STOPPING("◍ Stopping"), DELETING("⦸ Deleting"), RUNNING("⦿ Running"), STOPPED("◍ Stopped"), DELETED("⦸ Deleted"), CANCELING("◍ Canceling action"), CANCELED("◍ Canceled action"), FAILED("ⓧ Failed"); + fun statusColor() = when (this) { + RUNNING -> JBColor.GREEN + FAILED -> JBColor.RED + else -> if (JBColor.isBright()) JBColor.LIGHT_GRAY else JBColor.DARK_GRAY + } + companion object { fun from(workspace: Workspace) = when (workspace.latestBuild.job.status) { ProvisionerJobStatus.PENDING -> QUEUED @@ -28,5 +35,7 @@ enum class WorkspaceAgentStatus(val label: String) { ProvisionerJobStatus.CANCELED -> CANCELED ProvisionerJobStatus.FAILED -> FAILED } + + fun from(str: String) = WorkspaceAgentStatus.values().first { it.label.contains(str, true) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 960ec47d..e0002255 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -41,7 +41,6 @@ import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.AnActionButton import com.intellij.ui.AppIcon -import com.intellij.ui.JBColor import com.intellij.ui.RelativeFont import com.intellij.ui.ToolbarDecorator import com.intellij.ui.components.JBTextField @@ -773,20 +772,14 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) if (value is String) { text = value + foreground = WorkspaceAgentStatus.from(value).statusColor() } font = this@CoderWorkspacesStepView.tableOfWorkspaces.tableHeader.font border = JBUI.Borders.empty(0, 8) - foreground = (table.model as ListTableModel).getRowValue(row).statusColor() return this } } } - - private fun WorkspaceAgentModel.statusColor() = when (this.agentStatus) { - RUNNING -> JBColor.GREEN - FAILED -> JBColor.RED - else -> if (JBColor.isBright()) JBColor.LIGHT_GRAY else JBColor.DARK_GRAY - } } private fun TableView.selectItem(workspaceName: String?) { From 592892cfb591eee7172ff67a677b0ab244c1168a Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 22 Dec 2022 21:57:20 +0200 Subject: [PATCH 9/9] chore: upgrade Coder max supported version to 0.13.6 --- src/main/resources/version/CoderSupportedVersions.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/version/CoderSupportedVersions.properties b/src/main/resources/version/CoderSupportedVersions.properties index 280fb807..c7aa3527 100644 --- a/src/main/resources/version/CoderSupportedVersions.properties +++ b/src/main/resources/version/CoderSupportedVersions.properties @@ -1,2 +1,2 @@ minCompatibleCoderVersion=0.12.9 -maxCompatibleCoderVersion=0.13.5 +maxCompatibleCoderVersion=0.13.6