Skip to content

Commit d7c3a9d

Browse files
committed
Merge branch 'main' into eap
2 parents a80354e + 08887b3 commit d7c3a9d

File tree

9 files changed

+163
-34
lines changed

9 files changed

+163
-34
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105

106106
# Run Qodana inspections
107107
- name: Qodana - Code Inspection
108-
uses: JetBrains/qodana-action@v2022.2.4
108+
uses: JetBrains/qodana-action@v2022.3.0
109109

110110
# Prepare plugin archive content for creating artifact
111111
- name: Prepare Plugin Artifact

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
## Unreleased
66

7+
### Added
8+
- ability to open a template in the Dashboard
9+
10+
### Changed
11+
- renamed the plugin from `Coder Gateway` to `Gateway`
12+
- workspaces and agents are now resolved and displayed progressively
13+
14+
### Fixed
15+
- icon rendering on macO
16+
717
## 2.1.3-eap.0 - 2022-12-12
818
Bug fixes and enhancements included in `2.1.3` release:
919

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,30 @@ Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=soc
77
[![Coder Gateway Plugin Build](https://github.com/coder/coder-jetbrains/actions/workflows/build.yml/badge.svg)](https://github.com/coder/coder-jetbrains/actions/workflows/build.yml)
88

99
<!-- Plugin description -->
10-
**Coder Gateway** connects your JetBrains IDE to your [Coder Workspaces](https://coder.com/docs/coder-oss/latest/workspaces) so that you can develop from anywhere.
10+
**Coder Gateway** connects your JetBrains IDE to [Coder](https://coder.com/docs/coder-oss/) workspaces so that you can develop from anywhere.
1111

1212
**Manage less**
1313

1414
- Ensure your entire team is using the same tools and resources
15+
- Rollout critical updates to your developers with one command
16+
- Automatically shut down expensive cloud resources
1517
- Keep your source code and data behind your firewall
1618

1719
**Code more**
1820

1921
- Build and test faster
20-
- Leveraging cloud CPUs, RAM, network speeds, etc.
21-
- Access your environment from any place
22+
- Leveraging cloud CPUs, RAM, network speeds, etc.
23+
- Access your environment from any place on any client (even an iPad)
2224
- Onboard instantly then stay up to date continuously
2325

2426
<!-- Plugin description end -->
2527

2628
## Getting Started
2729

30+
![Install this plugin from the JetBrains Marketplace](https://plugins.jetbrains.com/plugin/19620-coder-gateway)
31+
32+
## Manually Building
33+
2834
To manually install a local build:
2935

3036
1. Install [Jetbrains Gateway](https://www.jetbrains.com/help/phpstorm/remote-development-a.html#gateway)

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ plugins {
1111
// Kotlin support
1212
id("org.jetbrains.kotlin.jvm") version "1.7.22"
1313
// Gradle IntelliJ Plugin
14-
id("org.jetbrains.intellij") version "1.10.0"
14+
id("org.jetbrains.intellij") version "1.10.1"
1515
// Gradle Changelog Plugin
1616
id("org.jetbrains.changelog") version "2.0.0"
1717
// Gradle Qodana Plugin

src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,28 @@ data class WorkspaceAgentModel(
1919
val agentOS: OS?,
2020
val agentArch: Arch?,
2121
val homeDirectory: String?
22-
)
22+
) {
23+
override fun equals(other: Any?): Boolean {
24+
if (this === other) return true
25+
if (javaClass != other?.javaClass) return false
26+
27+
other as WorkspaceAgentModel
28+
29+
if (workspaceID != other.workspaceID) return false
30+
if (workspaceName != other.workspaceName) return false
31+
if (name != other.name) return false
32+
if (templateID != other.templateID) return false
33+
if (templateName != other.templateName) return false
34+
35+
return true
36+
}
37+
38+
override fun hashCode(): Int {
39+
var result = workspaceID.hashCode()
40+
result = 31 * result + workspaceName.hashCode()
41+
result = 31 * result + name.hashCode()
42+
result = 31 * result + templateID.hashCode()
43+
result = 31 * result + templateName.hashCode()
44+
return result
45+
}
46+
}

src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package com.coder.gateway.sdk
33
import com.coder.gateway.icons.CoderIcons
44
import com.intellij.openapi.components.Service
55
import com.intellij.openapi.components.service
6-
import com.intellij.ui.scale.ScaleContext
6+
import com.intellij.util.IconUtil
77
import com.intellij.util.ImageLoader
88
import com.intellij.util.ui.ImageUtil
9+
import org.imgscalr.Scalr
910
import java.net.URL
1011
import javax.swing.Icon
11-
import javax.swing.ImageIcon
1212

1313
@Service(Service.Level.APP)
1414
class TemplateIconDownloader {
@@ -25,10 +25,7 @@ class TemplateIconDownloader {
2525
if (url != null) {
2626
var img = ImageLoader.loadFromUrl(url)
2727
if (img != null) {
28-
if (ImageUtil.getRealHeight(img) > 32 && ImageUtil.getRealWidth(img) > 32) {
29-
img = ImageUtil.resize(img, 32, ScaleContext.create())
30-
}
31-
return ImageIcon(img)
28+
return IconUtil.toRetinaAwareIcon(Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.ULTRA_QUALITY, 32))
3229
}
3330
}
3431

src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 112 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,16 @@ import kotlinx.coroutines.cancel
6464
import kotlinx.coroutines.delay
6565
import kotlinx.coroutines.isActive
6666
import kotlinx.coroutines.launch
67+
import kotlinx.coroutines.runBlocking
6768
import kotlinx.coroutines.withContext
6869
import org.zeroturnaround.exec.ProcessExecutor
6970
import java.awt.Component
7071
import java.awt.Dimension
72+
import java.awt.event.MouseEvent
73+
import java.awt.event.MouseListener
74+
import java.awt.event.MouseMotionListener
75+
import java.awt.font.TextAttribute
76+
import java.awt.font.TextAttribute.UNDERLINE_ON
7177
import javax.swing.Icon
7278
import javax.swing.JTable
7379
import javax.swing.JTextField
@@ -80,6 +86,8 @@ private const val CODER_URL_KEY = "coder-url"
8086

8187
private const val SESSION_TOKEN = "session-token"
8288

89+
private const val MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW = "MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW"
90+
8391
class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : CoderWorkspacesWizardStep, Disposable {
8492
private val cs = CoroutineScope(Dispatchers.Main)
8593
private var localWizardModel = CoderWorkspacesWizardModel()
@@ -123,6 +131,49 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
123131
}
124132
updateWorkspaceActions()
125133
}
134+
135+
addMouseListener(object : MouseListener {
136+
override fun mouseClicked(e: MouseEvent?) {
137+
if (e?.source is TableView<*>) {
138+
val tblView = e.source as TableView<WorkspaceAgentModel>
139+
val col = tblView.selectedColumn
140+
val workspace = tblView.selectedObject
141+
142+
if (col == 2 && workspace != null) {
143+
BrowserUtil.browse(coderClient.coderURL.toURI().resolve("/templates/${workspace.templateName}"))
144+
}
145+
}
146+
}
147+
148+
override fun mousePressed(e: MouseEvent?) {
149+
}
150+
151+
override fun mouseReleased(e: MouseEvent?) {
152+
}
153+
154+
override fun mouseEntered(e: MouseEvent?) {
155+
}
156+
157+
override fun mouseExited(e: MouseEvent?) {
158+
}
159+
})
160+
addMouseMotionListener(object : MouseMotionListener {
161+
override fun mouseMoved(e: MouseEvent?) {
162+
if (e?.source is TableView<*>) {
163+
val tblView = e.source as TableView<WorkspaceAgentModel>
164+
val row = tblView.rowAtPoint(e.point)
165+
if (tblView.columnAtPoint(e.point) == 2 && row in 0 until tblView.listTableModel.rowCount) {
166+
tblView.putClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW, row)
167+
} else {
168+
tblView.putClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW, -1)
169+
}
170+
}
171+
172+
}
173+
174+
override fun mouseDragged(e: MouseEvent?) {
175+
}
176+
})
126177
}
127178

128179
private val goToDashboardAction = GoToDashboardAction()
@@ -349,12 +400,20 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
349400

350401
val authTask = object : Task.Modal(null, CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), false) {
351402
override fun run(pi: ProgressIndicator) {
352-
353403
pi.apply {
354404
isIndeterminate = false
355-
text = "Downloading coder cli..."
405+
text = "Retrieving Workspaces..."
356406
fraction = 0.1
357407
}
408+
runBlocking {
409+
loadWorkspaces()
410+
}
411+
412+
pi.apply {
413+
isIndeterminate = false
414+
text = "Downloading Coder CLI..."
415+
fraction = 0.3
416+
}
358417

359418
cliManager.downloadCLI()
360419
if (getOS() != OS.WINDOWS) {
@@ -363,7 +422,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
363422
logger.info("chmod +x ${cliManager.localCli.toAbsolutePath()} $chmodOutput")
364423
}
365424
pi.apply {
366-
text = "Configuring coder cli..."
425+
text = "Configuring Coder CLI..."
367426
fraction = 0.5
368427
}
369428

@@ -374,7 +433,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
374433
logger.info("Result of `${localWizardModel.localCliPath} config-ssh --yes --use-previous-options`: $sshConfigOutput")
375434

376435
pi.apply {
377-
text = "Remove old coder cli versions..."
436+
text = "Remove old Coder CLI versions..."
378437
fraction = 0.9
379438
}
380439
cliManager.removeOldCli()
@@ -417,35 +476,50 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
417476

418477
poller = cs.launch {
419478
while (isActive) {
420-
loadWorkspaces()
421479
delay(5000)
480+
loadWorkspaces()
422481
}
423482
}
424483
}
425484

426485
private suspend fun loadWorkspaces() {
427-
val workspaceList = withContext(Dispatchers.IO) {
486+
withContext(Dispatchers.IO) {
487+
val timeBeforeRequestingWorkspaces = System.currentTimeMillis()
428488
try {
429-
return@withContext coderClient.workspaces().collectAgents()
489+
val ws = coderClient.workspaces()
490+
val timeAfterRequestingWorkspaces = System.currentTimeMillis()
491+
logger.info("Retrieving the workspaces took: ${timeAfterRequestingWorkspaces - timeBeforeRequestingWorkspaces} millis")
492+
ws.resolveAndDisplayAgents()
430493
} catch (e: Exception) {
431494
logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e")
432-
emptyList()
433495
}
434496
}
497+
}
435498

436-
withContext(Dispatchers.Main) {
437-
val selectedWorkspace = tableOfWorkspaces.selectedObject?.name
438-
listTableModelOfWorkspaces.items = workspaceList
439-
if (selectedWorkspace != null) {
440-
tableOfWorkspaces.selectItem(selectedWorkspace)
499+
private fun List<Workspace>.resolveAndDisplayAgents() {
500+
this.forEach { workspace ->
501+
cs.launch(Dispatchers.IO) {
502+
val timeBeforeRequestingAgents = System.currentTimeMillis()
503+
workspace.agentModels().forEach { am ->
504+
withContext(Dispatchers.Main) {
505+
val selectedWorkspace = tableOfWorkspaces.selectedObject?.name
506+
if (listTableModelOfWorkspaces.indexOf(am) >= 0) {
507+
val index = listTableModelOfWorkspaces.indexOf(am)
508+
listTableModelOfWorkspaces.setItem(index, am)
509+
} else {
510+
listTableModelOfWorkspaces.addRow(am)
511+
}
512+
if (selectedWorkspace != null) {
513+
tableOfWorkspaces.selectItem(selectedWorkspace)
514+
}
515+
}
516+
}
517+
val timeAfterRequestingAgents = System.currentTimeMillis()
518+
logger.info("Retrieving the agents for ${workspace.name} took: ${timeAfterRequestingAgents - timeBeforeRequestingAgents} millis")
441519
}
442520
}
443521
}
444522

445-
private fun List<Workspace>.collectAgents(): List<WorkspaceAgentModel> {
446-
return this.flatMap { it.agentModels() }.toList()
447-
}
448-
449523
private fun Workspace.agentModels(): List<WorkspaceAgentModel> {
450524
return try {
451525
val agents = coderClient.workspaceAgentsByTemplate(this)
@@ -569,9 +643,10 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
569643
override fun isCenterAlignment() = true
570644

571645
override fun getTableCellRendererComponent(table: JTable?, value: Any?, selected: Boolean, focus: Boolean, row: Int, column: Int): Component {
572-
return super.getTableCellRendererComponent(table, value, selected, focus, row, column).apply {
573-
border = JBUI.Borders.empty(10, 10)
646+
super.getTableCellRendererComponent(table, value, selected, focus, row, column).apply {
647+
border = JBUI.Borders.empty(10)
574648
}
649+
return this
575650
}
576651
}
577652
}
@@ -590,6 +665,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
590665
text = value
591666
}
592667
font = JBFont.h3().asBold()
668+
border = JBUI.Borders.empty()
593669
return this
594670
}
595671
}
@@ -602,13 +678,27 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
602678
}
603679

604680
override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer {
681+
val simpleH3 = JBFont.h3()
682+
683+
val h3AttributesWithUnderlining = simpleH3.attributes as MutableMap<TextAttribute, Any>
684+
h3AttributesWithUnderlining[TextAttribute.UNDERLINE] = UNDERLINE_ON
685+
val underlinedH3 = JBFont.h3().deriveFont(h3AttributesWithUnderlining)
605686
return object : DefaultTableCellRenderer() {
606687
override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component {
607688
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
608689
if (value is String) {
609690
text = value
610691
}
611-
font = JBFont.h3()
692+
border = JBUI.Borders.empty()
693+
694+
if (table.getClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW) != null) {
695+
val mouseOverRow = table.getClientProperty(MOUSE_OVER_TEMPLATE_NAME_COLUMN_ON_ROW) as Int
696+
if (mouseOverRow >= 0 && mouseOverRow == row) {
697+
font = underlinedH3
698+
return this
699+
}
700+
}
701+
font = simpleH3
612702
return this
613703
}
614704
}
@@ -628,6 +718,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
628718
text = value
629719
}
630720
font = JBFont.h3()
721+
border = JBUI.Borders.empty()
631722
return this
632723
}
633724
}
@@ -647,6 +738,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
647738
text = value
648739
}
649740
font = JBFont.h3()
741+
border = JBUI.Borders.empty()
650742
foreground = (table.model as ListTableModel<WorkspaceAgentModel>).getRowValue(row).statusColor()
651743
return this
652744
}

src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
22
<idea-plugin>
33
<id>com.coder.gateway</id>
4-
<name>Coder Gateway</name>
4+
<name>Coder</name>
55
<vendor>Coder</vendor>
66

77
<!-- Product and plugin compatibility requirements -->
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
minCompatibleCoderVersion=0.12.9
2-
maxCompatibleCoderVersion=0.13.1
2+
maxCompatibleCoderVersion=0.13.5

0 commit comments

Comments
 (0)