Skip to content

Commit 52a91b1

Browse files
committed
Ensure no nulls in recent window actions
1 parent fd1acde commit 52a91b1

File tree

5 files changed

+110
-116
lines changed

5 files changed

+110
-116
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.coder.gateway.util
2+
3+
/**
4+
* Run block with provided arguments after checking they are all non-null. This
5+
* is to enforce non-null values and should be used to signify developer error.
6+
*/
7+
fun <A, Z> withoutNull(a: A?, block: (a: A) -> Z): Z {
8+
if (a == null) {
9+
throw Exception("Unexpected null value")
10+
}
11+
return block(a)
12+
}
13+
14+
/**
15+
* Run block with provided arguments after checking they are all non-null. This
16+
* is to enforce non-null values and should be used to signify developer error.
17+
*/
18+
fun <A, B, Z> withoutNull(a: A?, b: B?, block: (a: A, b: B) -> Z): Z {
19+
if (a == null || b == null) {
20+
throw Exception("Unexpected null value")
21+
}
22+
return block(a, b)
23+
}
24+
25+
/**
26+
* Run block with provided arguments after checking they are all non-null. This
27+
* is to enforce non-null values and should be used to signify developer error.
28+
*/
29+
fun <A, B, C, D, Z> withoutNull(a: A?, b: B?, c: C?, d: D?, block: (a: A, b: B, c: C, d: D) -> Z): Z {
30+
if (a == null || b == null || c == null || d == null) {
31+
throw Exception("Unexpected null value")
32+
}
33+
return block(a, b, c, d)
34+
}

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

+8-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
1717
import com.coder.gateway.services.CoderRestClientService
1818
import com.coder.gateway.services.CoderSettingsService
1919
import com.coder.gateway.util.toURL
20+
import com.coder.gateway.util.withoutNull
2021
import com.coder.gateway.util.withPath
2122
import com.intellij.icons.AllIcons
2223
import com.intellij.ide.BrowserUtil
@@ -219,27 +220,27 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
219220
label("").resizableColumn().align(AlignX.FILL)
220221
actionButton(object : DumbAwareAction(CoderGatewayBundle.message("gateway.connector.recent-connections.start.button.tooltip"), "", CoderIcons.RUN) {
221222
override fun actionPerformed(e: AnActionEvent) {
222-
if (workspaceWithAgent != null) {
223-
deployment?.client?.startWorkspace(workspaceWithAgent.workspace)
223+
withoutNull(workspaceWithAgent, deployment?.client) { ws, client ->
224+
client.startWorkspace(ws.workspace)
224225
cs.launch { fetchWorkspaces() }
225226
}
226227
}
227228
}).applyToComponent { isEnabled = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.FAILED).contains(workspaceWithAgent?.workspace?.latestBuild?.status) }
228229
.gap(RightGap.SMALL)
229230
actionButton(object : DumbAwareAction(CoderGatewayBundle.message("gateway.connector.recent-connections.stop.button.tooltip"), "", CoderIcons.STOP) {
230231
override fun actionPerformed(e: AnActionEvent) {
231-
if (workspaceWithAgent != null) {
232-
deployment?.client?.stopWorkspace(workspaceWithAgent.workspace)
232+
withoutNull(workspaceWithAgent, deployment?.client) { ws, client ->
233+
client.stopWorkspace(ws.workspace)
233234
cs.launch { fetchWorkspaces() }
234235
}
235236
}
236237
}).applyToComponent { isEnabled = workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING }
237238
.gap(RightGap.SMALL)
238239
actionButton(object : DumbAwareAction(CoderGatewayBundle.message("gateway.connector.recent-connections.terminal.button.tooltip"), "", CoderIcons.OPEN_TERMINAL) {
239240
override fun actionPerformed(e: AnActionEvent) {
240-
if (workspaceWithAgent != null) {
241-
val link = deployment?.client?.url?.withPath("/me/${workspaceWithAgent.name}/terminal")
242-
BrowserUtil.browse(link?.toString() ?: "")
241+
withoutNull(workspaceWithAgent, deployment?.client) { ws, client ->
242+
val link = client.url.withPath("/me/${ws.name}/terminal")
243+
BrowserUtil.browse(link.toString())
243244
}
244245
}
245246
})

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

+1-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.coder.gateway.views.steps
22

3+
import com.coder.gateway.util.withoutNull
34
import com.intellij.ide.IdeBundle
45
import com.intellij.openapi.Disposable
56
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager
@@ -62,36 +63,3 @@ sealed class CoderWizardStep<T>(
6263
*/
6364
abstract fun stop()
6465
}
65-
66-
/**
67-
* Run block with provided arguments after checking they are all non-null. This
68-
* is to enforce non-null values and should be used to signify developer error.
69-
*/
70-
fun <A, Z> withoutNull(a: A?, block: (a: A) -> Z): Z {
71-
if (a == null) {
72-
throw Exception("Unexpected null value")
73-
}
74-
return block(a)
75-
}
76-
77-
/**
78-
* Run block with provided arguments after checking they are all non-null. This
79-
* is to enforce non-null values and should be used to signify developer error.
80-
*/
81-
fun <A, B, Z> withoutNull(a: A?, b: B?, block: (a: A, b: B) -> Z): Z {
82-
if (a == null || b == null) {
83-
throw Exception("Unexpected null value")
84-
}
85-
return block(a, b)
86-
}
87-
88-
/**
89-
* Run block with provided arguments after checking they are all non-null. This
90-
* is to enforce non-null values and should be used to signify developer error.
91-
*/
92-
fun <A, B, C, D, Z> withoutNull(a: A?, b: B?, c: C?, d: D?, block: (a: A, b: B, c: C, d: D) -> Z): Z {
93-
if (a == null || b == null || c == null || d == null) {
94-
throw Exception("Unexpected null value")
95-
}
96-
return block(a, b, c, d)
97-
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.coder.gateway.util.humanizeDuration
1313
import com.coder.gateway.util.isCancellation
1414
import com.coder.gateway.util.isWorkerTimeout
1515
import com.coder.gateway.util.suspendingRetryWithExponentialBackOff
16+
import com.coder.gateway.util.withoutNull
1617
import com.coder.gateway.util.withPath
1718
import com.coder.gateway.views.LazyBrowserLink
1819
import com.intellij.openapi.application.ApplicationManager

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

+66-76
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.coder.gateway.util.OS
2424
import com.coder.gateway.util.SemVer
2525
import com.coder.gateway.util.isCancellation
2626
import com.coder.gateway.util.toURL
27+
import com.coder.gateway.util.withoutNull
2728
import com.intellij.icons.AllIcons
2829
import com.intellij.ide.ActivityTracker
2930
import com.intellij.ide.BrowserUtil
@@ -262,30 +263,24 @@ class CoderWorkspacesStepView : CoderWizardStep<CoderWorkspacesStepSelection>(
262263
private inner class GoToTemplateAction :
263264
AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.template.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.template.text"), AllIcons.Nodes.Template) {
264265
override fun actionPerformed(p0: AnActionEvent) {
265-
withoutNull(client) {
266-
if (tableOfWorkspaces.selectedObject != null) {
267-
val workspace = (tableOfWorkspaces.selectedObject as WorkspaceAgentListModel).workspace
268-
BrowserUtil.browse(it.url.toURI().resolve("/templates/${workspace.templateName}"))
269-
}
266+
withoutNull(client, tableOfWorkspaces.selectedObject?.workspace) { c, workspace ->
267+
BrowserUtil.browse(c.url.toURI().resolve("/templates/${workspace.templateName}"))
270268
}
271269
}
272270
}
273271

274272
private inner class StartWorkspaceAction :
275273
AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderIcons.RUN) {
276274
override fun actionPerformed(p0: AnActionEvent) {
277-
withoutNull(client) {
278-
if (tableOfWorkspaces.selectedObject != null) {
279-
val workspace = (tableOfWorkspaces.selectedObject as WorkspaceAgentListModel).workspace
280-
jobs[workspace.id]?.cancel()
281-
jobs[workspace.id] = cs.launch {
282-
withContext(Dispatchers.IO) {
283-
try {
284-
it.startWorkspace(workspace)
285-
loadWorkspaces()
286-
} catch (e: WorkspaceResponseException) {
287-
logger.error("Could not start workspace ${workspace.name}, reason: $e")
288-
}
275+
withoutNull(client, tableOfWorkspaces.selectedObject?.workspace) { c, workspace ->
276+
jobs[workspace.id]?.cancel()
277+
jobs[workspace.id] = cs.launch {
278+
withContext(Dispatchers.IO) {
279+
try {
280+
c.startWorkspace(workspace)
281+
loadWorkspaces()
282+
} catch (e: WorkspaceResponseException) {
283+
logger.error("Could not start workspace ${workspace.name}, reason: $e")
289284
}
290285
}
291286
}
@@ -296,59 +291,56 @@ class CoderWorkspacesStepView : CoderWizardStep<CoderWorkspacesStepSelection>(
296291
private inner class UpdateWorkspaceTemplateAction :
297292
AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.update.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.update.text"), CoderIcons.UPDATE) {
298293
override fun actionPerformed(p0: AnActionEvent) {
299-
withoutNull(client) {
300-
if (tableOfWorkspaces.selectedObject != null) {
301-
val workspace = (tableOfWorkspaces.selectedObject as WorkspaceAgentListModel).workspace
302-
jobs[workspace.id]?.cancel()
303-
jobs[workspace.id] = cs.launch {
304-
withContext(Dispatchers.IO) {
305-
try {
306-
// Stop the workspace first if it is running.
307-
if (workspace.latestBuild.status == WorkspaceStatus.RUNNING) {
308-
logger.info("Waiting for ${workspace.name} to stop before updating")
309-
it.stopWorkspace(workspace)
310-
loadWorkspaces()
311-
var elapsed = Duration.ofSeconds(0)
312-
val timeout = Duration.ofSeconds(5)
313-
val maxWait = Duration.ofMinutes(10)
314-
while (isActive) { // Wait for the workspace to fully stop.
315-
delay(timeout.toMillis())
316-
val found = tableOfWorkspaces.items.firstOrNull{ it.workspace.id == workspace.id }
317-
when (val status = found?.workspace?.latestBuild?.status) {
318-
WorkspaceStatus.PENDING, WorkspaceStatus.STOPPING, WorkspaceStatus.RUNNING -> {
319-
logger.info("Still waiting for ${workspace.name} to stop before updating")
320-
}
321-
WorkspaceStatus.STARTING, WorkspaceStatus.FAILED,
322-
WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED,
323-
WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> {
324-
logger.warn("Canceled ${workspace.name} update due to status change to $status")
325-
break
326-
}
327-
null -> {
328-
logger.warn("Canceled ${workspace.name} update because it no longer exists")
329-
break
330-
}
331-
WorkspaceStatus.STOPPED -> {
332-
logger.info("${workspace.name} has stopped; updating now")
333-
it.updateWorkspace(workspace)
334-
break
335-
}
294+
withoutNull(client, tableOfWorkspaces.selectedObject?.workspace) { c, workspace ->
295+
jobs[workspace.id]?.cancel()
296+
jobs[workspace.id] = cs.launch {
297+
withContext(Dispatchers.IO) {
298+
try {
299+
// Stop the workspace first if it is running.
300+
if (workspace.latestBuild.status == WorkspaceStatus.RUNNING) {
301+
logger.info("Waiting for ${workspace.name} to stop before updating")
302+
c.stopWorkspace(workspace)
303+
loadWorkspaces()
304+
var elapsed = Duration.ofSeconds(0)
305+
val timeout = Duration.ofSeconds(5)
306+
val maxWait = Duration.ofMinutes(10)
307+
while (isActive) { // Wait for the workspace to fully stop.
308+
delay(timeout.toMillis())
309+
val found = tableOfWorkspaces.items.firstOrNull{ it.workspace.id == workspace.id }
310+
when (val status = found?.workspace?.latestBuild?.status) {
311+
WorkspaceStatus.PENDING, WorkspaceStatus.STOPPING, WorkspaceStatus.RUNNING -> {
312+
logger.info("Still waiting for ${workspace.name} to stop before updating")
313+
}
314+
WorkspaceStatus.STARTING, WorkspaceStatus.FAILED,
315+
WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED,
316+
WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> {
317+
logger.warn("Canceled ${workspace.name} update due to status change to $status")
318+
break
336319
}
337-
elapsed += timeout
338-
if (elapsed > maxWait) {
339-
logger.error("Canceled ${workspace.name} update because it took took longer than ${maxWait.toMinutes()} minutes to stop")
320+
null -> {
321+
logger.warn("Canceled ${workspace.name} update because it no longer exists")
340322
break
341323
}
324+
WorkspaceStatus.STOPPED -> {
325+
logger.info("${workspace.name} has stopped; updating now")
326+
c.updateWorkspace(workspace)
327+
break
328+
}
329+
}
330+
elapsed += timeout
331+
if (elapsed > maxWait) {
332+
logger.error("Canceled ${workspace.name} update because it took took longer than ${maxWait.toMinutes()} minutes to stop")
333+
break
342334
}
343-
} else {
344-
it.updateWorkspace(workspace)
345-
loadWorkspaces()
346335
}
347-
} catch (e: WorkspaceResponseException) {
348-
logger.error("Could not update workspace ${workspace.name}, reason: $e")
349-
} catch (e: TemplateResponseException) {
350-
logger.error("Could not update workspace ${workspace.name}, reason: $e")
336+
} else {
337+
c.updateWorkspace(workspace)
338+
loadWorkspaces()
351339
}
340+
} catch (e: WorkspaceResponseException) {
341+
logger.error("Could not update workspace ${workspace.name}, reason: $e")
342+
} catch (e: TemplateResponseException) {
343+
logger.error("Could not update workspace ${workspace.name}, reason: $e")
352344
}
353345
}
354346
}
@@ -359,18 +351,16 @@ class CoderWorkspacesStepView : CoderWizardStep<CoderWorkspacesStepSelection>(
359351
private inner class StopWorkspaceAction :
360352
AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderIcons.STOP) {
361353
override fun actionPerformed(p0: AnActionEvent) {
362-
withoutNull(client) {
363-
if (tableOfWorkspaces.selectedObject != null) {
364-
val workspace = (tableOfWorkspaces.selectedObject as WorkspaceAgentListModel).workspace
365-
jobs[workspace.id]?.cancel()
366-
jobs[workspace.id] = cs.launch {
367-
withContext(Dispatchers.IO) {
368-
try {
369-
it.stopWorkspace(workspace)
370-
loadWorkspaces()
371-
} catch (e: WorkspaceResponseException) {
372-
logger.error("Could not stop workspace ${workspace.name}, reason: $e")
373-
}
354+
withoutNull(client, tableOfWorkspaces.selectedObject) { c, workspace ->
355+
val workspace = (tableOfWorkspaces.selectedObject as WorkspaceAgentListModel).workspace
356+
jobs[workspace.id]?.cancel()
357+
jobs[workspace.id] = cs.launch {
358+
withContext(Dispatchers.IO) {
359+
try {
360+
c.stopWorkspace(workspace)
361+
loadWorkspaces()
362+
} catch (e: WorkspaceResponseException) {
363+
logger.error("Could not stop workspace ${workspace.name}, reason: $e")
374364
}
375365
}
376366
}

0 commit comments

Comments
 (0)