Skip to content

Sandbox annotation from #304 is CLI-only — propagate to ConfigService for HTTP/REST parity #312

Description

@padak

Background

PR #311 (v0.42.0, closes #304) added a sandbox_annotation block to config detail --component-id keboola.sandboxes --config-id <ID> that resolves the misleading parameters.id to the real Storage workspace ID. The annotation lives in commands/config.py, not in ConfigService.get_config_detail().

This means:

Reproduction

kbagent serve --ui
# In another terminal:
kbagent http get "/configs/<alias>/keboola.sandboxes/<sandbox-config-id>" | jq 'has(\"sandbox_annotation\")'
# false  ← gap

kbagent --json config detail --project <alias> --component-id keboola.sandboxes --config-id <sandbox-config-id> | jq 'has(\"sandbox_annotation\")'
# true  ← CLI has it

Why CLI-only in #311

I chose the path of least risk in PR #311: changing ConfigService.get_config_detail() shape would touch every programmatic consumer, so I scoped the annotation to the CLI layer where I could prove the change end-to-end with the time available. The gap was acknowledged in PR #311 comments but not addressed.

Proposed fix

Add an opt-in parameter to the service layer:

# services/config_service.py
def get_config_detail(
    self,
    *,
    include_sandbox_annotation: bool = False,  # ← new, default off
    ...
) -> dict[str, Any]:
    result = ...  # existing fetch path
    if include_sandbox_annotation and component_id == \"keboola.sandboxes\" and config_id is not None:
        result[\"sandbox_annotation\"] = self._resolve_sandbox_annotation(...)
    return result

Then:

  • commands/config.py passes include_sandbox_annotation=True (and removes its current ad-hoc enrichment block).
  • server/routers/configs.py accepts include_sandbox_annotation: bool = False as a query parameter, opt-in for REST callers. The web UI workspace / config detail page can flip it on once it learns to render the annotation block.

Default False = zero regression risk for existing programmatic consumers; new callers opt in.

Refactor sketch

Pull the filtering logic from WorkspaceService.resolve_sandbox_workspace_id() into a module-level pure function in services/workspace_service.py:

def find_storage_workspace_for_sandbox_config(
    workspaces: list[dict[str, Any]],
    config_id: str,
) -> int | None:
    \"\"\"Pure-function workspace[].configurationId → workspace.id lookup.\"\"\"
    ...

Both WorkspaceService.resolve_sandbox_workspace_id (callers: CLI) AND ConfigService.get_config_detail (callers: CLI + HTTP) call this helper. Avoids the circular ConfigService → WorkspaceService dependency that would otherwise creep into the DI graph.

Tests to add

  • test_config_service.py::TestSandboxAnnotation -- service emits annotation iff flag is True + component is keboola.sandboxes + single-config mode.
  • test_config_service.py regression -- existing programmatic consumers (default include_sandbox_annotation=False) get the unchanged shape.
  • HTTP endpoint test -- GET /configs/.../keboola.sandboxes/<id>?include_sandbox_annotation=true returns the block; without the flag it does not.

Related

Out of scope for this issue

  • Web UI rendering of the annotation block (separate PR; this issue is service+HTTP parity only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions