From 1157dbfd509bc96c87293181ca6a98755a07abb6 Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Mon, 11 May 2026 23:48:23 +0530 Subject: [PATCH] Show workspace group in workspace output Add a GROUP column to rad workspace list and rad workspace show. The new table output derives the group name from the workspace scope while preserving the stored scope value for structured output.\n\nFixes: #9805 Signed-off-by: Asish Kumar --- pkg/cli/cmd/workspace/common/objectformats.go | 7 +++- .../workspace/common/objectformats_test.go | 3 +- pkg/cli/cmd/workspace/list/list_test.go | 4 +++ pkg/cli/cmd/workspace/show/show_test.go | 2 ++ pkg/cli/objectformats/transformers.go | 20 ++++++++++++ pkg/cli/objectformats/transformers_test.go | 32 +++++++++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/pkg/cli/cmd/workspace/common/objectformats.go b/pkg/cli/cmd/workspace/common/objectformats.go index c00e9cd498..6d7c3cb2cf 100644 --- a/pkg/cli/cmd/workspace/common/objectformats.go +++ b/pkg/cli/cmd/workspace/common/objectformats.go @@ -22,7 +22,7 @@ import ( ) // WorkspaceFormat returns a FormatterOptions object which contains a list of columns to be used for displaying -// workspace information such as name, kind, kubecontext and environment." +// workspace information such as name, kind, kubecontext, group, and environment. func WorkspaceFormat() output.FormatterOptions { return output.FormatterOptions{ Columns: []output.Column{ @@ -38,6 +38,11 @@ func WorkspaceFormat() output.FormatterOptions { Heading: "KUBECONTEXT", JSONPath: "{ .Connection.context }", }, + { + Heading: "GROUP", + JSONPath: "{ .Scope }", + Transformer: &objectformats.ResourceScopeToResourceGroupNameTransformer{}, + }, { Heading: "ENVIRONMENT", JSONPath: "{ .Environment }", diff --git a/pkg/cli/cmd/workspace/common/objectformats_test.go b/pkg/cli/cmd/workspace/common/objectformats_test.go index 47dbbd7696..d3c9a5f3ad 100644 --- a/pkg/cli/cmd/workspace/common/objectformats_test.go +++ b/pkg/cli/cmd/workspace/common/objectformats_test.go @@ -32,6 +32,7 @@ func Test_WorkspaceFormat(t *testing.T) { "kind": workspaces.KindKubernetes, "context": "test-context", }, + Scope: "/planes/radius/local/resourceGroups/test-group", Environment: "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/test", } @@ -39,6 +40,6 @@ func Test_WorkspaceFormat(t *testing.T) { err := output.Write(output.FormatTable, obj, buffer, WorkspaceFormat()) require.NoError(t, err) - expected := "WORKSPACE KIND KUBECONTEXT ENVIRONMENT\ntest kubernetes test-context test\n" + expected := "WORKSPACE KIND KUBECONTEXT GROUP ENVIRONMENT\ntest kubernetes test-context test-group test\n" require.Equal(t, expected, buffer.String()) } diff --git a/pkg/cli/cmd/workspace/list/list_test.go b/pkg/cli/cmd/workspace/list/list_test.go index 6fd6f01d5c..1f57b9d528 100644 --- a/pkg/cli/cmd/workspace/list/list_test.go +++ b/pkg/cli/cmd/workspace/list/list_test.go @@ -74,11 +74,13 @@ func Test_Run(t *testing.T) { // Intentionally NOT in alphabetical order "workspace-b": { Environment: "b", + Scope: "/planes/radius/local/resourceGroups/group-b", Source: workspaces.SourceUserConfig, Connection: map[string]any{}, }, "workspace-a": { Environment: "a", + Scope: "/planes/radius/local/resourceGroups/group-a", Source: workspaces.SourceUserConfig, Connection: map[string]any{}, }, @@ -104,12 +106,14 @@ func Test_Run(t *testing.T) { { Name: "workspace-a", Environment: "a", + Scope: "/planes/radius/local/resourceGroups/group-a", Source: workspaces.SourceUserConfig, Connection: map[string]any{}, }, { Name: "workspace-b", Environment: "b", + Scope: "/planes/radius/local/resourceGroups/group-b", Source: workspaces.SourceUserConfig, Connection: map[string]any{}, }, diff --git a/pkg/cli/cmd/workspace/show/show_test.go b/pkg/cli/cmd/workspace/show/show_test.go index 2c2f81cf02..7d7d6c1d50 100644 --- a/pkg/cli/cmd/workspace/show/show_test.go +++ b/pkg/cli/cmd/workspace/show/show_test.go @@ -93,6 +93,7 @@ func Test_Run(t *testing.T) { Source: workspaces.SourceUserConfig, Name: "test-workspace", Environment: "test-environment", + Scope: "/planes/radius/local/resourceGroups/test-group", Connection: map[string]any{}, }, } @@ -107,6 +108,7 @@ func Test_Run(t *testing.T) { Source: workspaces.SourceUserConfig, Name: "test-workspace", Environment: "test-environment", + Scope: "/planes/radius/local/resourceGroups/test-group", Connection: map[string]any{}, }, Options: common.WorkspaceFormat(), diff --git a/pkg/cli/objectformats/transformers.go b/pkg/cli/objectformats/transformers.go index 3a71423ead..512547a8f6 100644 --- a/pkg/cli/objectformats/transformers.go +++ b/pkg/cli/objectformats/transformers.go @@ -41,6 +41,26 @@ func (t *ResourceIDToResourceGroupNameTransformer) Transform(input string) strin return id.FindScope(radius.ScopeResourceGroups) } +// ResourceScopeToResourceGroupNameTransformer is a transformer that takes a scope ID and returns the resource group name. +type ResourceScopeToResourceGroupNameTransformer struct { +} + +// Transform takes a scope ID and returns the resource group name. +func (t *ResourceScopeToResourceGroupNameTransformer) Transform(input string) string { + if input == "" { + return "" + } + + // NOTE: this is for display to human users in a table. It's not a great place + // for us to put a long explanation. + id, err := resources.ParseScope(input) + if err != nil { + return "" + } + + return id.FindScope(radius.ScopeResourceGroups) +} + // ResourceIDToResourceNameTransformer is a transformer that takes a resource ID and returns the resource name. type ResourceIDToResourceNameTransformer struct { } diff --git a/pkg/cli/objectformats/transformers_test.go b/pkg/cli/objectformats/transformers_test.go index bbc6fa258f..1e6f7b06cc 100644 --- a/pkg/cli/objectformats/transformers_test.go +++ b/pkg/cli/objectformats/transformers_test.go @@ -85,3 +85,35 @@ func Test_ResourceIDToResourceGroupNameTransformer(t *testing.T) { }) } } + +func Test_ResourceScopeToResourceGroupNameTransformer(t *testing.T) { + cases := []struct { + name string + input string + expected string + }{ + { + name: "empty input", + input: "", + expected: "", + }, + { + name: "invalid input", + input: "////", + expected: "", + }, + { + name: "valid input", + input: "/planes/radius/local/resourceGroups/test-group", + expected: "test-group", + }, + } + + for _, testcase := range cases { + t.Run(testcase.name, func(t *testing.T) { + transformer := &ResourceScopeToResourceGroupNameTransformer{} + actual := transformer.Transform(testcase.input) + require.Equal(t, testcase.expected, actual) + }) + } +}