Skip to content

Commit 9200282

Browse files
authored
fix(browse): include subcollection-only document refs (#47)
Co-authored-by: Gustavo Hoirisch <355877+gugahoi@users.noreply.github.com>
1 parent 0fffe8b commit 9200282

1 file changed

Lines changed: 101 additions & 66 deletions

File tree

pkg/cmd/browse/firestore.go

Lines changed: 101 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -194,91 +194,126 @@ func fetchColumnData(client *firestore.Client, path string, isDoc bool, columnIn
194194
colPath := strings.TrimPrefix(path, "/")
195195
colRef := client.Collection(colPath)
196196

197-
// Apply sorting if specified
198-
query := colRef.Query
199-
if fo.query != nil {
200-
query = query.Where(fo.query.Field, fo.query.Operator, fo.query.Value)
201-
}
202-
if sortField != "" {
203-
query = query.OrderBy(sortField, sortDirection)
204-
}
205-
206-
// Apply pagination limit
207-
if fo.limit > 0 {
208-
query = query.Limit(fo.limit + 1) // Fetch one extra to detect hasMore
209-
if fo.offset > 0 {
210-
query = query.Offset(fo.offset)
211-
}
212-
}
213-
214-
iter := query.Documents(ctx)
215197
var items []ListItem
216198
firstDoc := true
217-
fetchCount := 0
218-
for {
219-
doc, err := iter.Next()
220-
if err == iterator.Done {
221-
break
222-
}
223-
if err != nil {
224-
return errorMsg{err: err}
199+
200+
if fo.query == nil && sortField == "" {
201+
// The default collection view should include both stored documents and
202+
// document refs that only exist because they have subcollections.
203+
refsIter := colRef.DocumentRefs(ctx)
204+
refs := []*firestore.DocumentRef{}
205+
skipped := 0
206+
for {
207+
ref, err := refsIter.Next()
208+
if err == iterator.Done {
209+
break
210+
}
211+
if err != nil {
212+
return errorMsg{err: err}
213+
}
214+
if skipped < fo.offset {
215+
skipped++
216+
continue
217+
}
218+
if fo.limit > 0 && len(refs) > fo.limit {
219+
hasMore = true
220+
break
221+
}
222+
refs = append(refs, ref)
225223
}
226224

227-
fetchCount++
228-
// If we got more than limit, we have more pages
229-
if fo.limit > 0 && fetchCount > fo.limit {
225+
if fo.limit > 0 && len(refs) > fo.limit {
230226
hasMore = true
231-
break
227+
refs = refs[:fo.limit]
232228
}
233229

234-
// Extract field names from first document
235-
if firstDoc {
236-
firstDoc = false
237-
data := doc.Data()
238-
for key := range data {
239-
availableFields = append(availableFields, key)
230+
if len(refs) > 0 {
231+
snaps, err := client.GetAll(ctx, refs)
232+
if err != nil {
233+
return errorMsg{err: err}
234+
}
235+
236+
for _, snap := range snaps {
237+
docPath := path + "/" + snap.Ref.ID
238+
item := ListItem{
239+
id: snap.Ref.ID,
240+
path: docPath,
241+
isDoc: true,
242+
}
243+
if snap.Exists() {
244+
if firstDoc {
245+
firstDoc = false
246+
data := snap.Data()
247+
for key := range data {
248+
availableFields = append(availableFields, key)
249+
}
250+
sort.Strings(availableFields)
251+
}
252+
253+
metadata := map[string]string{}
254+
if !snap.CreateTime.IsZero() {
255+
metadata["created"] = snap.CreateTime.Local().Format("2006-01-02 15:04:05")
256+
}
257+
item.metadata = metadata
258+
} else {
259+
item.isMissing = true
260+
}
261+
items = append(items, item)
240262
}
241-
sort.Strings(availableFields)
242263
}
243-
244-
metadata := map[string]string{}
245-
if !doc.CreateTime.IsZero() {
246-
metadata["created"] = doc.CreateTime.Local().Format("2006-01-02 15:04:05")
264+
} else {
265+
// Queries and sorts must use Firestore's server-side document query.
266+
query := colRef.Query
267+
if fo.query != nil {
268+
query = query.Where(fo.query.Field, fo.query.Operator, fo.query.Value)
269+
}
270+
if sortField != "" {
271+
query = query.OrderBy(sortField, sortDirection)
247272
}
248-
docPath := path + "/" + doc.Ref.ID
249-
items = append(items, ListItem{
250-
id: doc.Ref.ID,
251-
path: docPath,
252-
isDoc: true,
253-
metadata: metadata,
254-
})
255-
}
256273

257-
// Only fetch document refs for missing docs on first page
258-
if fo.offset == 0 {
259-
seen := make(map[string]bool, len(items))
260-
for _, item := range items {
261-
seen[item.id] = true
274+
if fo.limit > 0 {
275+
query = query.Limit(fo.limit + 1)
276+
if fo.offset > 0 {
277+
query = query.Offset(fo.offset)
278+
}
262279
}
263280

264-
refsIter := colRef.DocumentRefs(ctx)
281+
iter := query.Documents(ctx)
282+
fetchCount := 0
265283
for {
266-
ref, err := refsIter.Next()
284+
doc, err := iter.Next()
267285
if err == iterator.Done {
268286
break
269287
}
270288
if err != nil {
271289
return errorMsg{err: err}
272290
}
273-
if seen[ref.ID] {
274-
continue
291+
292+
fetchCount++
293+
if fo.limit > 0 && fetchCount > fo.limit {
294+
hasMore = true
295+
break
296+
}
297+
298+
if firstDoc {
299+
firstDoc = false
300+
data := doc.Data()
301+
for key := range data {
302+
availableFields = append(availableFields, key)
303+
}
304+
sort.Strings(availableFields)
305+
}
306+
307+
metadata := map[string]string{}
308+
if !doc.CreateTime.IsZero() {
309+
metadata["created"] = doc.CreateTime.Local().Format("2006-01-02 15:04:05")
275310
}
276-
docPath := path + "/" + ref.ID
311+
docPath := path + "/" + doc.Ref.ID
277312
items = append(items, ListItem{
278-
id: ref.ID,
279-
path: docPath,
280-
isDoc: true,
281-
isMissing: true,
313+
id: doc.Ref.ID,
314+
path: docPath,
315+
isDoc: true,
316+
metadata: metadata,
282317
})
283318
}
284319
}
@@ -391,13 +426,13 @@ func fetchColumnData(client *firestore.Client, path string, isDoc bool, columnIn
391426
}
392427
}
393428

394-
// Count queried documents (exclude missing refs and sentinel — docCount
395-
// is used as the Firestore query Offset for pagination)
429+
// Count displayed documents/refs (exclude sentinel — docCount is used as
430+
// the Offset for pagination in both query and DocumentRefs modes).
396431
totalDocs := 0
397432
for _, s := range sections {
398433
if s.title == "Documents" {
399434
for _, item := range s.items {
400-
if item.path != "__load_more__" && !item.isMissing {
435+
if item.path != "__load_more__" {
401436
totalDocs++
402437
}
403438
}

0 commit comments

Comments
 (0)