Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
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
10 changes: 10 additions & 0 deletions api/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ type SearchContainer struct {
DisplayURL string `json:"displayUrl"`
}

// SpaceKey extracts the space key from the DisplayURL.
// DisplayURL is typically in the format "/display/SPACEKEY" or "/spaces/SPACEKEY/...".
func (c SearchContainer) SpaceKey() string {
parts := strings.Split(strings.TrimPrefix(c.DisplayURL, "/"), "/")
if len(parts) >= 2 && (parts[0] == "display" || parts[0] == "spaces") {
return parts[1]
}
return ""
}

// SearchResponse represents the v1 search API response.
type SearchResponse struct {
Results []SearchResult `json:"results"`
Expand Down
21 changes: 21 additions & 0 deletions api/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,27 @@ func TestBuildCQL_Empty(t *testing.T) {
assert.Empty(t, cql)
}

func TestSearchContainer_SpaceKey(t *testing.T) {
tests := []struct {
name string
displayURL string
expected string
}{
{"display format", "/display/DEV", "DEV"},
{"spaces format", "/spaces/TEAM/overview", "TEAM"},
{"empty", "", ""},
{"no matching prefix", "/other/path", ""},
{"only prefix", "/display/", ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := SearchContainer{DisplayURL: tt.displayURL}
assert.Equal(t, tt.expected, c.SpaceKey())
})
}
}

func TestBuildCQL_QuotesInValue(t *testing.T) {
opts := &SearchOptions{Text: `search "quoted" term`}
cql := buildCQL(opts)
Expand Down
20 changes: 20 additions & 0 deletions internal/cmd/page/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,33 @@ func runView(pageID string, opts *viewOptions, client *api.Client) error {
renderer := view.NewRenderer(view.Format(opts.output), opts.noColor)

if opts.output == "json" {
// Enrich JSON output with spaceKey if we can resolve it
if page.SpaceID != "" {
space, err := client.GetSpace(context.Background(), page.SpaceID)
if err == nil {
// Create enriched response with spaceKey
type enrichedPage struct {
*api.Page
SpaceKey string `json:"spaceKey"`
}
return renderer.RenderJSON(enrichedPage{Page: page, SpaceKey: space.Key})
}
}
return renderer.RenderJSON(page)
}

// Show page info (unless content-only mode)
if !opts.contentOnly {
renderer.RenderKeyValue("Title", page.Title)
renderer.RenderKeyValue("ID", page.ID)
if page.SpaceID != "" {
space, err := client.GetSpace(context.Background(), page.SpaceID)
if err == nil {
renderer.RenderKeyValue("Space", fmt.Sprintf("%s (ID: %s)", space.Key, page.SpaceID))
} else {
renderer.RenderKeyValue("Space ID", page.SpaceID)
}
}
if page.Version != nil {
renderer.RenderKeyValue("Version", fmt.Sprintf("%d", page.Version.Number))
}
Expand Down
19 changes: 15 additions & 4 deletions internal/cmd/page/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ import (

func TestRunView_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Contains(t, r.URL.Path, "/pages/12345")
assert.Equal(t, "GET", r.Method)

if strings.Contains(r.URL.Path, "/spaces/") {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": "9999", "key": "DEV", "name": "Development"}`))
return
}

assert.Contains(t, r.URL.Path, "/pages/12345")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"id": "12345",
"title": "Test Page",
"spaceId": "9999",
"version": {"number": 3},
"body": {"storage": {"value": "<p>Hello <strong>World</strong></p>"}},
"_links": {"webui": "/pages/12345"}
Expand Down Expand Up @@ -62,10 +69,17 @@ func TestRunView_RawFormat(t *testing.T) {

func TestRunView_JSONOutput(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/spaces/") {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": "9999", "key": "DEV", "name": "Development"}`))
return
}

w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"id": "12345",
"title": "Test Page",
"spaceId": "9999",
"version": {"number": 1},
"body": {"storage": {"value": "<p>Content</p>"}},
"_links": {"webui": "/pages/12345"}
Expand Down Expand Up @@ -278,6 +292,3 @@ func TestRunView_ContentOnly_EmptyBody(t *testing.T) {
require.NoError(t, err)
// Output should be "(No content)" without metadata headers
}

// Ensure strings is used
var _ = strings.NewReader("")
4 changes: 3 additions & 1 deletion internal/cmd/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,16 @@ func runSearch(opts *searchOptions, client *api.Client) error {
}

// Render results
headers := []string{"ID", "TYPE", "SPACE", "TITLE"}
headers := []string{"ID", "TYPE", "SPACE KEY", "SPACE", "TITLE"}
var rows [][]string

for _, r := range result.Results {
space := r.ResultGlobalContainer.Title
spaceKey := r.ResultGlobalContainer.SpaceKey()
rows = append(rows, []string{
r.Content.ID,
r.Content.Type,
spaceKey,
view.Truncate(space, 15),
view.Truncate(r.Content.Title, 50),
})
Expand Down
Loading