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

+9
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

+39-89
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)