Skip to content

Commit 92c84a8

Browse files
committed
Fix prematurely deleting recent connection
If you view the recent window, it populates with workspaces. Then if you create a new workspace, connect to it, then go back to the recent window it can get removed because it had not yet done another fetch that includes the new workspace. To fix, move the deletion from the render phase to the fetch phase.
1 parent 52a91b1 commit 92c84a8

File tree

1 file changed

+66
-68
lines changed

1 file changed

+66
-68
lines changed

src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt

+66-68
Original file line numberDiff line numberDiff line change
@@ -147,56 +147,24 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
147147
override fun getRecentsTitle() = CoderGatewayBundle.message("gateway.connector.title")
148148

149149
override fun updateRecentView() {
150-
triggerWorkspacePolling()
150+
// Render immediately so we can display spinners for each connection
151+
// that we have not fetched a workspace for yet.
151152
updateContentView()
153+
// After each poll, the content view will be updated again.
154+
triggerWorkspacePolling()
152155
}
153156

157+
/**
158+
* Render the most recent connections, matching with fetched workspaces.
159+
*/
154160
private fun updateContentView() {
155-
val connectionsByDeployment = recentConnectionsService.getAllRecentConnections()
156-
// Validate and parse connections.
157-
.mapNotNull {
158-
try {
159-
it.toWorkspaceProjectIDE()
160-
} catch (e: Exception) {
161-
logger.warn("Removing invalid recent connection $it", e)
162-
recentConnectionsService.removeConnection(it)
163-
null
164-
}
165-
}
166-
// Filter by the search.
167-
.filter { matchesFilter(it) }
168-
// Group by the deployment.
169-
.groupBy { it.deploymentURL }
170-
// Group the connections in each deployment by workspace.
171-
.mapValues { (deploymentURL, deploymentConnections) ->
172-
deploymentConnections
173-
.groupBy { it.name.split(".", limit = 2).first() }
174-
// Find the matching workspace in the query response.
175-
.mapValues { (workspaceName, connections) ->
176-
val deployment = deployments[deploymentURL]
177-
val workspaceWithAgent = deployment?.items?.firstOrNull { it.workspace.name == workspaceName }
178-
Pair(workspaceWithAgent, connections)
179-
}
180-
// Remove connections to workspaces that no longer exist.
181-
.filter {
182-
val (workspaceWithAgent, workspaceConnections) = it.value
183-
if (workspaceWithAgent == null && deployments[deploymentURL]?.didFetch() == true) {
184-
logger.info("Removing recent connections for deleted workspace ${it.key} (found ${workspaceConnections.size})")
185-
workspaceConnections.forEach { conn ->
186-
recentConnectionsService.removeConnection(conn.toRecentWorkspaceConnection())
187-
}
188-
false
189-
} else {
190-
true
191-
}
192-
}
193-
}
161+
val connections = getConnectionsByDeployment(true)
194162
recentWorkspacesContentPanel.viewport.view = panel {
195-
connectionsByDeployment.forEach { (deploymentURL, connectionsByWorkspace) ->
163+
connections.forEach { (deploymentURL, connectionsByWorkspace) ->
196164
val deployment = deployments[deploymentURL]
197165
val deploymentError = deployment?.error
198-
connectionsByWorkspace.forEach { (workspaceName, value) ->
199-
val (workspaceWithAgent, connections) = value
166+
connectionsByWorkspace.forEach { (workspaceName, connections) ->
167+
val workspaceWithAgent = deployment?.items?.firstOrNull { it.workspace.name == workspaceName }
200168
val status = if (deploymentError != null) {
201169
Triple(UIUtil.getBalloonErrorIcon(), UIUtil.getErrorForeground(), deploymentError)
202170
} else if (workspaceWithAgent != null) {
@@ -280,6 +248,31 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
280248
}
281249
}
282250

251+
/**
252+
* Get valid connections grouped by deployment and workspace.
253+
*/
254+
private fun getConnectionsByDeployment(filter: Boolean): Map<String, Map<String, List<WorkspaceProjectIDE>>> {
255+
return recentConnectionsService.getAllRecentConnections()
256+
// Validate and parse connections.
257+
.mapNotNull {
258+
try {
259+
it.toWorkspaceProjectIDE()
260+
} catch (e: Exception) {
261+
logger.warn("Removing invalid recent connection $it", e)
262+
recentConnectionsService.removeConnection(it)
263+
null
264+
}
265+
}
266+
.filter { !filter || matchesFilter(it) }
267+
// Group by the deployment.
268+
.groupBy { it.deploymentURL }
269+
// Group the connections in each deployment by workspace.
270+
.mapValues { (_, connections) ->
271+
connections
272+
.groupBy { it.name.split(".", limit = 2).first() }
273+
}
274+
}
275+
283276
/**
284277
* Return true if the connection matches the current filter.
285278
*/
@@ -319,35 +312,40 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
319312
*/
320313
private suspend fun fetchWorkspaces() {
321314
withContext(Dispatchers.IO) {
322-
recentConnectionsService.getAllRecentConnections()
323-
.mapNotNull { it.deploymentURL }
324-
.toSet()
325-
.map { Pair(it, deployments.getOrPut(it) { DeploymentInfo() }) }
326-
.forEach { (deploymentURL, deployment) ->
327-
val client = deployment.client ?: try {
328-
val cli = CoderCLIManager(deploymentURL.toURL())
329-
val (_, token) = settings.readConfig(cli.coderConfigPath)
330-
deployment.client = CoderRestClientService(deploymentURL.toURL(), token)
315+
val connections = getConnectionsByDeployment(false)
316+
.map { Triple(it.key, deployments.getOrPut(it.key) { DeploymentInfo() }, it.value) }
317+
connections.forEach { (deploymentURL, deployment, workspaces) ->
318+
val client = deployment.client ?: try {
319+
val cli = CoderCLIManager(deploymentURL.toURL())
320+
val (_, token) = settings.readConfig(cli.coderConfigPath)
321+
deployment.client = CoderRestClientService(deploymentURL.toURL(), token)
322+
deployment.error = null
323+
deployment.client
324+
} catch (e: Exception) {
325+
logger.error("Unable to create client for $deploymentURL", e)
326+
deployment.error = "Error connecting to $deploymentURL: ${e.message}"
327+
null
328+
}
329+
if (client != null) {
330+
try {
331+
val items = client.workspaces().flatMap { it.toAgentList() }
332+
deployment.items = items
331333
deployment.error = null
332-
deployment.client
333-
} catch (e: Exception) {
334-
logger.error("Unable to create client for $deploymentURL", e)
335-
deployment.error = "Error connecting to $deploymentURL: ${e.message}"
336-
null
337-
}
338-
if (client != null) {
339-
try {
340-
deployment.items = client.workspaces()
341-
.flatMap { it.toAgentList() }
342-
deployment.error = null
343-
} catch (e: Exception) {
344-
// TODO: If this is an auth error, ask for a new token.
345-
logger.error("Failed to fetch workspaces from ${client.url}", e)
346-
deployment.items = null
347-
deployment.error = e.message ?: "Request failed without further details"
334+
// Delete connections that have no workspace.
335+
workspaces.forEach { name, connections ->
336+
if (items.firstOrNull { it.workspace.name == name } == null) {
337+
logger.info("Removing recent connections for deleted workspace ${name} (found ${connections.size})")
338+
connections.forEach { recentConnectionsService.removeConnection(it.toRecentWorkspaceConnection()) }
339+
}
348340
}
341+
} catch (e: Exception) {
342+
// TODO: If this is an auth error, ask for a new token.
343+
logger.error("Failed to fetch workspaces from ${client.url}", e)
344+
deployment.items = null
345+
deployment.error = e.message ?: "Request failed without further details"
349346
}
350347
}
348+
}
351349
}
352350
withContext(Dispatchers.Main) {
353351
updateContentView()

0 commit comments

Comments
 (0)