Skip to content

[toolbox] Add workspace actions #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import com.coder.gateway.models.WorkspaceAndAgentStatus
import com.coder.gateway.sdk.CoderRestClient
import com.coder.gateway.sdk.v2.models.Workspace
import com.coder.gateway.sdk.v2.models.WorkspaceAgent
import com.coder.gateway.util.withPath
import com.coder.gateway.views.Action
import com.coder.gateway.views.EnvironmentView
import com.jetbrains.toolbox.gateway.AbstractRemoteProviderEnvironment
import com.jetbrains.toolbox.gateway.EnvironmentVisibilityState
import com.jetbrains.toolbox.gateway.environments.EnvironmentContentsView
import com.jetbrains.toolbox.gateway.states.EnvironmentStateConsumer
import com.jetbrains.toolbox.gateway.ui.ObservablePropertiesFactory
import com.jetbrains.toolbox.gateway.ui.ToolboxUi
import java.util.concurrent.CompletableFuture

/**
Expand All @@ -19,18 +22,60 @@ import java.util.concurrent.CompletableFuture
*/
class CoderRemoteEnvironment(
private val client: CoderRestClient,
private val workspace: Workspace,
private val agent: WorkspaceAgent,
private var workspace: Workspace,
private var agent: WorkspaceAgent,
private val ui: ToolboxUi,
observablePropertiesFactory: ObservablePropertiesFactory,
) : AbstractRemoteProviderEnvironment(observablePropertiesFactory) {
override fun getId(): String = "${workspace.name}.${agent.name}"
override fun getName(): String = "${workspace.name}.${agent.name}"
private var status = WorkspaceAndAgentStatus.from(workspace, agent)

init {
actionsList.add(
Action("Open web terminal") {
ui.openUrl(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString())
},
)
actionsList.add(
Action("Open in dashboard") {
ui.openUrl(client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString())
},
)
actionsList.add(
Action("View template") {
ui.openUrl(client.url.withPath("/templates/${workspace.templateName}").toString())
},
)
actionsList.add(
Action("Start", enabled = { status.canStart() }) {
val build = client.startWorkspace(workspace)
workspace = workspace.copy(latestBuild = build)
update(workspace, agent)
},
)
actionsList.add(
Action("Stop", enabled = { status.ready() || status.pending() }) {
val build = client.stopWorkspace(workspace)
workspace = workspace.copy(latestBuild = build)
update(workspace, agent)
},
)
actionsList.add(
Action("Update", enabled = { workspace.outdated }) {
val build = client.updateWorkspace(workspace)
workspace = workspace.copy(latestBuild = build)
update(workspace, agent)
},
)
}

/**
* Update the workspace/agent status to the listeners, if it has changed.
*/
fun update(workspace: Workspace, agent: WorkspaceAgent) {
this.workspace = workspace
this.agent = agent
val newStatus = WorkspaceAndAgentStatus.from(workspace, agent)
if (newStatus != status) {
status = newStatus
Expand Down Expand Up @@ -58,6 +103,11 @@ class CoderRemoteEnvironment(
* Immediately send the state to the listener and store for updates.
*/
override fun addStateListener(consumer: EnvironmentStateConsumer): Boolean {
// TODO@JB: It would be ideal if we could have the workspace state and
// the connected state listed separately, since right now the
// connected state can mask the workspace state.
// TODO@JB: You can still press connect if the environment is
// unreachable. Is that expected?
consumer.consume(status.toRemoteEnvironmentState())
return super.addStateListener(consumer)
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class CoderRemoteProvider(
it.name
}?.map { agent ->
// If we have an environment already, update that.
val env = CoderRemoteEnvironment(client, ws, agent, observablePropertiesFactory)
val env = CoderRemoteEnvironment(client, ws, agent, ui, observablePropertiesFactory)
lastEnvironments?.firstOrNull { it == env }?.let {
it.update(ws, agent)
it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* WorkspaceAndAgentStatus represents the combined status of a single agent and
* its workspace (or just the workspace if there are no agents).
*/
enum class WorkspaceAndAgentStatus(val label: String, val description: String) {

Check notice on line 15 in src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Class member can have 'private' visibility

Property 'label' could be private

Check warning on line 15 in src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "description" is never used
// Workspace states.
QUEUED("Queued", "The workspace is queueing to start."),
STARTING("Starting", "The workspace is starting."),
Expand Down Expand Up @@ -52,11 +52,8 @@
* Return the environment state for Toolbox, which tells it the label, color
* and whether the environment is reachable.
*
* We mark all ready and pending states as reachable since if the workspace
* is pending the cli will wait for it anyway.
*
* Additionally, terminal states like stopped are also marked as reachable,
* since the cli will start them.
* Note that a reachable environment will always display "connected" or
* "disconnected" regardless of the label we give that status.
*/
fun toRemoteEnvironmentState(): CustomRemoteEnvironmentState {
// Use comments; no named arguments for non-Kotlin functions.
Expand All @@ -67,7 +64,7 @@
Color(104, 112, 128, 255), // lightThemeColor
Color(224, 224, 240, 26), // darkThemeBackgroundColor
Color(224, 224, 245, 250), // lightThemeBackgroundColor
ready() || pending() || canStart(), // reachable
ready(), // reachable
// TODO@JB: How does this work? Would like a spinner for pending states.
null, // iconId
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ data class Workspace(
@Json(name = "latest_build") val latestBuild: WorkspaceBuild,
@Json(name = "outdated") val outdated: Boolean,
@Json(name = "name") val name: String,
@Json(name = "owner_name") val ownerName: String,
)
4 changes: 3 additions & 1 deletion src/main/kotlin/com/coder/gateway/views/CoderPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
private var getter: Function<UiField, *>? = null

/** Let Toolbox know the fields should be updated. */
protected var listener: Consumer<UiField?>? = null

Check notice on line 41 in src/main/kotlin/com/coder/gateway/views/CoderPage.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Class member can have 'private' visibility

Property 'listener' could be private

/** Stores errors until the notifier is attached. */
private var errorBuffer: MutableList<Throwable> = mutableListOf()
Expand Down Expand Up @@ -116,11 +116,13 @@
*/
class Action(
private val label: String,
private val closesPage: Boolean,
private val closesPage: Boolean = false,
private val enabled: () -> Boolean = { true },
private val cb: () -> Unit,
) : RunnableActionDescription {
override fun getLabel(): String = label
override fun getShouldClosePage(): Boolean = closesPage
override fun isEnabled(): Boolean = enabled()
override fun run() {
cb()
}
Expand Down
1 change: 1 addition & 0 deletions src/test/kotlin/com/coder/gateway/sdk/DataGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class DataGen {
),
outdated = false,
name = name,
ownerName = "owner",
)
}

Expand Down
Loading