Skip to content

Commit d2f50f1

Browse files
authored
revise recent projects flow to be less confusing (#464)
1 parent cae0cb4 commit d2f50f1

File tree

2 files changed

+48
-89
lines changed

2 files changed

+48
-89
lines changed

CHANGELOG.md

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

55
## Unreleased
66

7+
- The "Recents" view has been updated to have a new flow.
8+
Before, there were separate controls for managing the workspace and then you
9+
could click a link to launch a project (clicking a link would also start a stopped workspace automatically).
10+
Now, there are no workspace controls, just links which start the workspace automatically when needed.
11+
The links are enabled when the workspace is STOPPED, CANCELED, FAILED, STARTING, RUNNING. These states represent
12+
valid times to start a workspace and connect, or to simply connect to a running one or one that's already starting.
13+
We also use a spinner icon when workspaces are in a transition state (STARTING, CANCELING, DELETING, STOPPING)
14+
to give context for why a link might be disabled or a connection might take longer than usual to establish.
15+
716
## 2.13.1 - 2024-07-19
817

918
### Changed

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

Lines changed: 39 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ import com.coder.gateway.services.CoderRestClientService
1717
import com.coder.gateway.services.CoderSettingsService
1818
import com.coder.gateway.util.humanizeConnectionError
1919
import com.coder.gateway.util.toURL
20-
import com.coder.gateway.util.withPath
2120
import com.coder.gateway.util.withoutNull
2221
import com.intellij.icons.AllIcons
23-
import com.intellij.ide.BrowserUtil
2422
import com.intellij.openapi.Disposable
2523
import com.intellij.openapi.actionSystem.AnActionEvent
2624
import com.intellij.openapi.application.ModalityState
@@ -56,6 +54,7 @@ import kotlinx.coroutines.delay
5654
import kotlinx.coroutines.isActive
5755
import kotlinx.coroutines.launch
5856
import kotlinx.coroutines.withContext
57+
import java.awt.Color
5958
import java.awt.Component
6059
import java.awt.Dimension
6160
import java.util.Locale
@@ -175,15 +174,21 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
175174
val workspaceWithAgent = deployment?.items?.firstOrNull { it.workspace.name == workspaceName }
176175
val status =
177176
if (deploymentError != null) {
178-
Triple(UIUtil.getBalloonErrorIcon(), UIUtil.getErrorForeground(), deploymentError)
177+
Triple(UIUtil.getErrorForeground(), deploymentError, UIUtil.getBalloonErrorIcon())
179178
} else if (workspaceWithAgent != null) {
179+
val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status)
180+
180181
Triple(
181-
workspaceWithAgent.status.icon,
182182
workspaceWithAgent.status.statusColor(),
183183
workspaceWithAgent.status.description,
184+
if (inLoadingState) {
185+
AnimatedIcon.Default()
186+
} else {
187+
null
188+
},
184189
)
185190
} else {
186-
Triple(AnimatedIcon.Default.INSTANCE, UIUtil.getContextHelpForeground(), "Querying workspace status...")
191+
Triple(UIUtil.getContextHelpForeground(), "Querying workspace status...", AnimatedIcon.Default())
187192
}
188193
val gap =
189194
if (top) {
@@ -193,11 +198,6 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
193198
TopGap.MEDIUM
194199
}
195200
row {
196-
icon(status.first).applyToComponent {
197-
foreground = status.second
198-
}.align(AlignX.LEFT).gap(RightGap.SMALL).applyToComponent {
199-
size = Dimension(JBUI.scale(16), JBUI.scale(16))
200-
}
201201
label(workspaceName).applyToComponent {
202202
font = JBFont.h3().asBold()
203203
}.align(AlignX.LEFT).gap(RightGap.SMALL)
@@ -206,94 +206,44 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
206206
font = ComponentPanelBuilder.getCommentFont(font)
207207
}
208208
label("").resizableColumn().align(AlignX.FILL)
209-
actionButton(
210-
object : DumbAwareAction(
211-
CoderGatewayBundle.message("gateway.connector.recent-connections.start.button.tooltip"),
212-
"",
213-
CoderIcons.RUN,
214-
) {
215-
override fun actionPerformed(e: AnActionEvent) {
216-
withoutNull(workspaceWithAgent?.workspace, deployment?.client) { workspace, client ->
217-
jobs[workspace.id]?.cancel()
218-
jobs[workspace.id] =
219-
cs.launch(ModalityState.current().asContextElement()) {
220-
withContext(Dispatchers.IO) {
221-
try {
222-
client.startWorkspace(workspace)
223-
fetchWorkspaces()
224-
} catch (e: Exception) {
225-
logger.error("Could not start workspace ${workspace.name}", e)
226-
}
227-
}
228-
}
229-
}
230-
}
231-
},
232-
).applyToComponent {
233-
isEnabled =
234-
listOf(
235-
WorkspaceStatus.STOPPED,
236-
WorkspaceStatus.FAILED,
237-
).contains(workspaceWithAgent?.workspace?.latestBuild?.status)
238-
}
239-
.gap(RightGap.SMALL)
240-
actionButton(
241-
object : DumbAwareAction(
242-
CoderGatewayBundle.message("gateway.connector.recent-connections.stop.button.tooltip"),
243-
"",
244-
CoderIcons.STOP,
245-
) {
246-
override fun actionPerformed(e: AnActionEvent) {
247-
withoutNull(workspaceWithAgent?.workspace, deployment?.client) { workspace, client ->
248-
jobs[workspace.id]?.cancel()
249-
jobs[workspace.id] =
250-
cs.launch(ModalityState.current().asContextElement()) {
251-
withContext(Dispatchers.IO) {
252-
try {
253-
client.stopWorkspace(workspace)
254-
fetchWorkspaces()
255-
} catch (e: Exception) {
256-
logger.error("Could not stop workspace ${workspace.name}", e)
257-
}
258-
}
259-
}
260-
}
261-
}
262-
},
263-
).applyToComponent { isEnabled = workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING }
264-
.gap(RightGap.SMALL)
265-
actionButton(
266-
object : DumbAwareAction(
267-
CoderGatewayBundle.message("gateway.connector.recent-connections.terminal.button.tooltip"),
268-
"",
269-
CoderIcons.OPEN_TERMINAL,
270-
) {
271-
override fun actionPerformed(e: AnActionEvent) {
272-
withoutNull(workspaceWithAgent, deployment?.client) { ws, client ->
273-
val link = client.url.withPath("/me/${ws.name}/terminal")
274-
BrowserUtil.browse(link.toString())
275-
}
276-
}
277-
},
278-
)
279209
}.topGap(gap)
210+
211+
val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status)
212+
213+
// We only display an API error on the first workspace rather than duplicating it on each workspace.
280214
if (deploymentError == null || showError) {
281215
row {
282-
// There must be a way to make this properly wrap?
283-
label("<html><body style='width:350px;'>" + status.third + "</html>").applyToComponent {
284-
foreground = status.second
216+
status.third?.let {
217+
icon(it)
218+
}
219+
label("<html><body style='width:350px;'>" + status.second + "</html>").applyToComponent {
220+
foreground = status.first
285221
}
286222
}
287223
}
224+
288225
connections.forEach { workspaceProjectIDE ->
289226
row {
290227
icon(workspaceProjectIDE.ideProduct.icon)
291-
cell(
292-
ActionLink(workspaceProjectIDE.projectPathDisplay) {
293-
CoderRemoteConnectionHandle().connect { workspaceProjectIDE }
294-
GatewayUI.getInstance().reset()
295-
},
296-
)
228+
if (enableLinks) {
229+
cell(
230+
ActionLink(workspaceProjectIDE.projectPathDisplay) {
231+
withoutNull(deployment?.client, workspaceWithAgent?.workspace) { client, workspace ->
232+
CoderRemoteConnectionHandle().connect {
233+
if (listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED).contains(workspace.latestBuild.status)) {
234+
client.startWorkspace(workspace)
235+
}
236+
workspaceProjectIDE
237+
}
238+
GatewayUI.getInstance().reset()
239+
}
240+
},
241+
)
242+
} else {
243+
label(workspaceProjectIDE.projectPathDisplay).applyToComponent {
244+
foreground = Color.GRAY
245+
}
246+
}
297247
label("").resizableColumn().align(AlignX.FILL)
298248
label(workspaceProjectIDE.ideName).applyToComponent {
299249
foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy