Skip to content

Commit 8e95ed3

Browse files
committed
fix: read pending fork items in hosted mode
1 parent 58fb74c commit 8e95ed3

10 files changed

Lines changed: 371 additions & 57 deletions

File tree

cmd/wl/cmd_browse.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,11 @@ func runBrowseLocal(stdout, stderr io.Writer, cfg *federation.Config, filter com
167167

168168
db := openDB(cfg.LocalDir)
169169
client := sdk.New(sdk.ClientConfig{
170-
DB: db,
171-
RigHandle: cfg.RigHandle,
172-
Mode: cfg.ResolveMode(),
173-
ListPendingItems: listPendingItemsFromPRs(cfg),
170+
DB: db,
171+
RigHandle: cfg.RigHandle,
172+
Mode: cfg.ResolveMode(),
173+
LoadPendingDetail: pendingDetailLoaderCallback(cfg),
174+
ListPendingItems: listPendingItemsFromPRs(cfg),
174175
})
175176

176177
result, err := client.Browse(filter)
@@ -190,10 +191,11 @@ func runBrowseRemote(stdout, _ io.Writer, cfg *federation.Config, filter commons
190191
return err
191192
}
192193
client := sdk.New(sdk.ClientConfig{
193-
DB: db,
194-
RigHandle: cfg.RigHandle,
195-
Mode: cfg.ResolveMode(),
196-
ListPendingItems: listPendingItemsFromPRs(cfg),
194+
DB: db,
195+
RigHandle: cfg.RigHandle,
196+
Mode: cfg.ResolveMode(),
197+
LoadPendingDetail: pendingDetailLoaderCallback(cfg),
198+
ListPendingItems: listPendingItemsFromPRs(cfg),
197199
})
198200

199201
result, err := client.Browse(filter)

cmd/wl/cmd_review.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ func dolthubListPendingItems(cfg *federation.Config) func() (map[string][]sdk.Pe
864864
Branch: p.Branch,
865865
BranchURL: p.BranchURL,
866866
PRURL: p.PRURL,
867+
ForkOwner: p.ForkOwner,
867868
CompletedBy: p.CompletedBy,
868869
Evidence: p.Evidence,
869870
}
@@ -944,6 +945,32 @@ func ghListPendingItems(ghPath, upstreamRepo string) func() (map[string][]sdk.Pe
944945
}
945946
}
946947

948+
// pendingDetailLoaderCallback returns a callback that can read branch-only
949+
// pending items from the correct DoltHub fork. Returns nil when the current
950+
// config does not support fork-aware remote reads.
951+
func pendingDetailLoaderCallback(cfg *federation.Config) func(string, sdk.PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
952+
if cfg.ResolveBackend() == federation.BackendLocal || cfg.ResolveProviderType() != "dolthub" {
953+
return nil
954+
}
955+
956+
upstreamOrg, db, err := federation.ParseUpstream(cfg.Upstream)
957+
if err != nil {
958+
return nil
959+
}
960+
961+
return pendingDetailLoader(upstreamOrg, db, cfg.ResolveMode(), commons.DoltHubToken())
962+
}
963+
964+
func pendingDetailLoader(upstreamOrg, db, mode, token string) func(string, sdk.PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
965+
return func(wantedID string, pending sdk.PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
966+
if pending.ForkOwner == "" || pending.Branch == "" {
967+
return nil, nil, nil, fmt.Errorf("pending item %q is missing fork owner or branch", wantedID)
968+
}
969+
forkDB := backend.NewRemoteDB(token, upstreamOrg, db, pending.ForkOwner, db, mode)
970+
return commons.QueryFullDetailAsOf(forkDB, wantedID, pending.Branch)
971+
}
972+
}
973+
947974
// branchURLCallback returns a callback that builds a DoltHub branch URL.
948975
// Returns nil if the provider is not DoltHub or fork info is missing.
949976
func branchURLCallback(cfg *federation.Config) func(string) string {

cmd/wl/cmd_serve.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,10 @@ func runServe(cmd *cobra.Command, stdout, stderr io.Writer) error {
204204
ClosePR: func(branch string) error {
205205
return closePRForBranch(cfg, branch)
206206
},
207-
ListPendingItems: listPendingItemsFromPRs(cfg),
208-
BranchURL: branchURLCallback(cfg),
209-
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
207+
LoadPendingDetail: pendingDetailLoaderCallback(cfg),
208+
ListPendingItems: listPendingItemsFromPRs(cfg),
209+
BranchURL: branchURLCallback(cfg),
210+
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
210211
})
211212

212213
server := api.New(client)
@@ -309,9 +310,10 @@ func runServeHosted(cmd *cobra.Command, stdout, _ io.Writer) error {
309310
pendingCache := newPendingItemsCache("hop", "wl-commons", 2*time.Minute)
310311
defer pendingCache.Stop()
311312
anonClient := sdk.New(sdk.ClientConfig{
312-
DB: publicDB,
313-
Mode: federation.ModePR,
314-
ListPendingItems: pendingCache.Get,
313+
DB: publicDB,
314+
Mode: federation.ModePR,
315+
LoadPendingDetail: pendingDetailLoader("hop", "wl-commons", federation.ModePR, ""),
316+
ListPendingItems: pendingCache.Get,
315317
})
316318
apiServer.SetPublicClient(anonClient)
317319

@@ -386,6 +388,7 @@ func newPendingItemsCache(upstreamOrg, db string, interval time.Duration) *pendi
386388
Branch: p.Branch,
387389
BranchURL: p.BranchURL,
388390
PRURL: p.PRURL,
391+
ForkOwner: p.ForkOwner,
389392
CompletedBy: p.CompletedBy,
390393
Evidence: p.Evidence,
391394
}

cmd/wl/cmd_tui.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ func runTUI(cmd *cobra.Command, _, stderr io.Writer) error {
121121
c.Signing = signing
122122
return store.Save(c)
123123
},
124-
ListPendingItems: listPendingItemsFromPRs(cfg),
125-
BranchURL: branchURLCallback(cfg),
126-
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
124+
LoadPendingDetail: pendingDetailLoaderCallback(cfg),
125+
ListPendingItems: listPendingItemsFromPRs(cfg),
126+
BranchURL: branchURLCallback(cfg),
127+
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
127128
})
128129

129130
m := tui.New(tui.Config{

cmd/wl/sdk_factory.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ var newSDKClient = func(cfg *federation.Config, noPush bool) (*sdk.Client, error
3131
ClosePR: func(branch string) error {
3232
return closePRForBranch(cfg, branch)
3333
},
34-
ListPendingItems: listPendingItemsFromPRs(cfg),
35-
BranchURL: branchURLCallback(cfg),
36-
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
34+
LoadPendingDetail: pendingDetailLoaderCallback(cfg),
35+
ListPendingItems: listPendingItemsFromPRs(cfg),
36+
BranchURL: branchURLCallback(cfg),
37+
CloseUpstreamPR: closeUpstreamPRCallback(cfg),
3738
}), nil
3839
}

internal/api/server_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io"
78
"net/http"
@@ -401,6 +402,77 @@ func TestDetail(t *testing.T) {
401402
}
402403
}
403404

405+
func TestHostedPublic_ReadsPendingOnlyForkItem(t *testing.T) {
406+
mainDB := newFakeDB()
407+
forkDB := newFakeDB()
408+
branch := "wl/charlie/w-new"
409+
forkDB.branches[branch] = true
410+
forkDB.branchItems[branch] = map[string]*fakeItem{
411+
"w-new": {
412+
id: "w-new",
413+
title: "New docs task",
414+
description: "Document binary install paths",
415+
project: "gascity",
416+
typ: "docs",
417+
priority: 1,
418+
postedBy: "charlie",
419+
status: "open",
420+
effortLevel: "small",
421+
},
422+
}
423+
424+
publicClient := sdk.New(sdk.ClientConfig{
425+
DB: mainDB,
426+
Mode: "pr",
427+
LoadPendingDetail: func(wantedID string, pending sdk.PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
428+
return commons.QueryFullDetailAsOf(forkDB, wantedID, pending.Branch)
429+
},
430+
ListPendingItems: func() (map[string][]sdk.PendingItem, error) {
431+
return map[string][]sdk.PendingItem{
432+
"w-new": {{
433+
RigHandle: "charlie",
434+
Status: "open",
435+
Branch: branch,
436+
BranchURL: "https://example.com/branch",
437+
PRURL: "https://example.com/pr/1",
438+
ForkOwner: "charlie",
439+
}},
440+
}, nil
441+
},
442+
})
443+
444+
srv := NewHosted(func(*http.Request) (*sdk.Client, error) {
445+
return nil, errors.New("not authenticated")
446+
})
447+
srv.SetPublicClient(publicClient)
448+
ts := httptest.NewServer(srv)
449+
defer ts.Close()
450+
451+
var browse BrowseResponse
452+
r := getJSON(t, ts, "/api/wanted?view=all", &browse)
453+
if r.StatusCode != http.StatusOK {
454+
t.Fatalf("expected 200 browse, got %d", r.StatusCode)
455+
}
456+
if len(browse.Items) != 1 || browse.Items[0].ID != "w-new" {
457+
t.Fatalf("expected pending fork item in browse, got %+v", browse.Items)
458+
}
459+
460+
var detail DetailResponse
461+
r = getJSON(t, ts, "/api/wanted/w-new", &detail)
462+
if r.StatusCode != http.StatusOK {
463+
t.Fatalf("expected 200 detail, got %d", r.StatusCode)
464+
}
465+
if detail.Item == nil || detail.Item.ID != "w-new" {
466+
t.Fatalf("expected detail for w-new, got %+v", detail.Item)
467+
}
468+
if detail.Branch != branch {
469+
t.Errorf("branch = %q, want %q", detail.Branch, branch)
470+
}
471+
if detail.PRURL != "https://example.com/pr/1" {
472+
t.Errorf("prURL = %q", detail.PRURL)
473+
}
474+
}
475+
404476
func TestDetailNotFound(t *testing.T) {
405477
db := newFakeDB()
406478
ts := newTestServer(db, "wild-west")

internal/hosted/resolver.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func newPendingUpstreamCache(provider *remote.DoltHubProvider, upOrg, upDB strin
6060
Branch: p.Branch,
6161
BranchURL: p.BranchURL,
6262
PRURL: p.PRURL,
63+
ForkOwner: p.ForkOwner,
6364
CompletedBy: p.CompletedBy,
6465
Evidence: p.Evidence,
6566
}
@@ -270,6 +271,13 @@ func (wr *WorkspaceResolver) buildClient(wl *WastelandConfig, rigHandle, connect
270271
entry.Signing = signing
271272
return wr.nango.SetMetadata(connectionID, currentMeta)
272273
},
274+
LoadPendingDetail: func(wantedID string, pending sdk.PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
275+
if pending.ForkOwner == "" || pending.Branch == "" {
276+
return nil, nil, nil, fmt.Errorf("pending item %q is missing fork owner or branch", wantedID)
277+
}
278+
forkDB := backend.NewRemoteDB(apiKey, upOrg, upDB, pending.ForkOwner, upDB, mode)
279+
return commons.QueryFullDetailAsOf(forkDB, wantedID, pending.Branch)
280+
},
273281
})
274282

275283
return client, nil

internal/sdk/reads.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type PendingItem struct {
1414
Branch string // e.g. "wl/alice/w-001"
1515
BranchURL string // web URL for the fork branch
1616
PRURL string // web URL for the upstream PR
17+
ForkOwner string // owner of the fork that hosts Branch
1718
CompletedBy string // from fork branch completions table
1819
Evidence string // from fork branch completions table
1920
}
@@ -91,7 +92,7 @@ func (c *Client) Browse(filter commons.BrowseFilter) (*BrowseResult, error) {
9192
if best.Branch == "" {
9293
continue
9394
}
94-
item, err := commons.QueryWantedDetailAsOf(c.db, id, best.Branch)
95+
item, _, _, err := c.loadPendingDetail(id, best)
9596
if err != nil {
9697
continue
9798
}
@@ -203,6 +204,14 @@ func (c *Client) detailPR(wantedID string) (*DetailResult, error) {
203204
}
204205
effective := state.Effective()
205206
if effective == nil {
207+
upstreamPRs := c.fetchUpstreamPRs(wantedID)
208+
if len(upstreamPRs) > 0 {
209+
result, err := c.detailFromPending(wantedID, upstreamPRs)
210+
if err == nil {
211+
return result, nil
212+
}
213+
return nil, err
214+
}
206215
// Fall back to main query if resolve found nothing.
207216
return c.detailWildWest(wantedID)
208217
}
@@ -229,6 +238,39 @@ func (c *Client) detailPR(wantedID string) (*DetailResult, error) {
229238
return result, nil
230239
}
231240

241+
func (c *Client) loadPendingDetail(wantedID string, pending PendingItem) (*commons.WantedItem, *commons.CompletionRecord, *commons.Stamp, error) {
242+
if c.LoadPendingDetail != nil {
243+
item, completion, stamp, err := c.LoadPendingDetail(wantedID, pending)
244+
if err == nil {
245+
return item, completion, stamp, nil
246+
}
247+
}
248+
return commons.QueryFullDetailAsOf(c.db, wantedID, pending.Branch)
249+
}
250+
251+
func (c *Client) detailFromPending(wantedID string, pending []PendingItem) (*DetailResult, error) {
252+
best := bestPendingState(pending)
253+
if best.Branch == "" {
254+
return c.detailWildWest(wantedID)
255+
}
256+
257+
item, completion, stamp, err := c.loadPendingDetail(wantedID, best)
258+
if err != nil {
259+
return nil, err
260+
}
261+
262+
return &DetailResult{
263+
Item: item,
264+
Completion: completion,
265+
Stamp: stamp,
266+
Branch: best.Branch,
267+
BranchURL: best.BranchURL,
268+
PRURL: best.PRURL,
269+
Delta: commons.ComputeDelta("", item.Status, true),
270+
UpstreamPRs: pending,
271+
}, nil
272+
}
273+
232274
func (c *Client) detailWildWest(wantedID string) (*DetailResult, error) {
233275
item, completion, stamp, err := commons.QueryFullDetail(c.db, wantedID)
234276
if err != nil {

0 commit comments

Comments
 (0)