@@ -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