From d5d39fbe742f1bf3da6d4889973a0893b3f2b0b1 Mon Sep 17 00:00:00 2001 From: AgentK20 Date: Wed, 3 Jun 2026 04:04:16 +0000 Subject: [PATCH] Expose each DevWorkspace project to Toolbox instead of /projects The data source hardcoded `projectPaths = listOf("/projects")`, so Toolbox advertised every workspace as a single project rooted at /projects, ignoring the projects the workspace actually clones. This implements the existing TODO by deriving the paths from the workspace itself. DevWorkspace now parses `spec.template.projects[]` and resolves each entry to `/` (absolute clonePath honored verbatim), exposing them via a new `projectPaths` field. The data source uses that list, falling back to the projects root when a workspace declares no projects, so existing behavior is preserved. Reading the list from the workspace spec is more complete than the TODO's suggested PROJECT_SOURCE env var, which only names the first project. Downstream rendering already maps each path to a selectable CachedProject, so no view changes are needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../datasource/DevWorkspacesDataSource.kt | 9 ++++- .../toolbox/openshift/DevWorkspace.kt | 38 ++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt index af7f455..46c706f 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt @@ -14,6 +14,7 @@ package com.redhat.devtools.toolbox.datasource import com.jetbrains.toolbox.api.core.diagnostics.Logger import com.redhat.devtools.toolbox.environment.EnvironmentConfig import com.redhat.devtools.toolbox.openshift.OpenShiftClientFactory +import com.redhat.devtools.toolbox.openshift.DevWorkspace import com.redhat.devtools.toolbox.openshift.DevWorkspaces import com.redhat.devtools.toolbox.openshift.Projects import kotlinx.coroutines.flow.MutableStateFlow @@ -47,8 +48,12 @@ class DevWorkspacesDataSource( description = workspace.phase, port = 2022, // the port of in-container running sshd // availableIdeProductCodes = listOf("IU"), - // TODO: implement fetching the PROJECT_SOURCES env. var. value - projectPaths = listOf("/projects"), + // Expose every project the workspace clones so the user can open + // any of them directly from Toolbox. Fall back to the projects + // root for workspaces that declare no projects. + projectPaths = workspace.projectPaths.ifEmpty { + listOf(DevWorkspace.PROJECTS_ROOT) + }, tags = mapOf("namespace" to workspace.namespace) ) } diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt index 1d7f5b1..3da1f8e 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt @@ -20,7 +20,13 @@ data class DevWorkspace( val uid: String, val started: Boolean, val phase: String, - val cheEditor: String? + val cheEditor: String?, + /** + * On-disk path of every project the workspace clones, e.g. + * `["/projects/frontend", "/projects/backend"]`. Empty when the + * workspace declares no projects. + */ + val projectPaths: List = emptyList() ) { val running: Boolean get() = phase == PHASE_RUNNING @@ -32,6 +38,12 @@ data class DevWorkspace( const val PHASE_STOPPING = "Stopping" const val PHASE_FAILED = "Failed" + /** + * Default root under which the DevWorkspace engine clones projects. + * Matches the DevWorkspace Operator's `PROJECTS_ROOT` default. + */ + const val PROJECTS_ROOT = "/projects" + fun from(resource: GenericKubernetesResource): DevWorkspace { val metadata = resource.metadata val spec = resource.additionalProperties["spec"] as? Map<*, *> ?: emptyMap() @@ -45,8 +57,30 @@ data class DevWorkspace( uid = metadata.uid ?: "", started = spec["started"] as? Boolean ?: false, phase = status["phase"] as? String ?: "", - cheEditor = cheEditor + cheEditor = cheEditor, + projectPaths = parseProjectPaths(spec) ) } + + /** + * Resolves the clone path of every project declared by the workspace. + * + * Projects live in `spec.template.projects[]`. The DevWorkspace engine + * clones each one into `/`; an absolute + * `clonePath` is honored verbatim. A project without a usable name or + * clonePath is skipped. Returns an empty list when none are declared. + */ + private fun parseProjectPaths(spec: Map<*, *>): List { + val template = spec["template"] as? Map<*, *> ?: return emptyList() + val projects = template["projects"] as? List<*> ?: return emptyList() + return projects + .filterIsInstance>() + .mapNotNull { project -> + val subPath = (project["clonePath"] as? String) + ?: (project["name"] as? String) + ?: return@mapNotNull null + if (subPath.startsWith("/")) subPath else "$PROJECTS_ROOT/$subPath" + } + } } } \ No newline at end of file