Skip to content
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
6 changes: 3 additions & 3 deletions internal/cli/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func newAlertListCmd() *cobra.Command {
if err != nil {
return err
}
return ctx.PrintList(proj, nil, len(result.Items), page, limit, int(result.Total))
return ctx.PrintCappedList(proj, nil, len(result.Items), page, limit, int(result.Total), result.HasNextPage)
}

cols := []output.Column{
Expand All @@ -109,7 +109,7 @@ func newAlertListCmd() *cobra.Command {
{Header: "STARTED", Field: func(v any) string { return output.FormatTime(v.(flashduty.AlertItem).StartTime) }},
}

return ctx.PrintList(result.Items, cols, len(result.Items), page, limit, int(result.Total))
return ctx.PrintCappedList(result.Items, cols, len(result.Items), page, limit, int(result.Total), result.HasNextPage)
})
},
}
Expand Down Expand Up @@ -236,7 +236,7 @@ func newAlertEventsCmd() *cobra.Command {
defaults := listDefaultsFor("Alerts", "ReadEventList")
win := requestWindow(ctx.Cmd, nil, defaults)
return ctx.PrintPage(result.Items, cols, listPage{
Count: len(result.Items), Page: win.page, Base: win.base, Limit: win.limit, Total: int(result.Total), More: moreOf(result.HasNextPage),
Count: len(result.Items), Page: win.page, Base: win.base, Limit: win.limit, Total: int(result.Total), More: moreOf(result.HasNextPage), TotalCapped: true,
})
})
},
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/alert_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ func newAlertEventListCmd() *cobra.Command {
if len(proj) < len(result.Items) {
effectiveLimit = len(proj)
}
return ctx.PrintList(proj, nil, len(proj), page, effectiveLimit, int(result.Total))
return ctx.PrintCappedList(proj, nil, len(proj), page, effectiveLimit, int(result.Total), result.HasNextPage)
}

return ctx.PrintList(result.Items, cols, len(result.Items), page, limit, int(result.Total))
return ctx.PrintCappedList(result.Items, cols, len(result.Items), page, limit, int(result.Total), result.HasNextPage)
})
},
}
Expand Down
42 changes: 35 additions & 7 deletions internal/cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"
"io"
"strconv"

"github.com/flashcatcloud/go-flashduty"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -98,12 +99,24 @@ type listPage struct {
Limit int // effective page size: requested, else the schema default; 0 when unknown
Total int // total matching rows; negative when the API returned none
More moreState // has_next_page / next cursor, when the API returned one
// TotalCapped marks an endpoint that stops counting at serverCountCap; its
// callers must also set More, the only "rows remain" signal past the cap.
TotalCapped bool
}

// totalKnown reports whether Total is a usable server total. A total below the
// shown count is an absent field, not a real total.
func (p listPage) totalKnown() bool { return p.Total >= 0 && p.Total >= p.Count }

// serverCountCap is where the incident, alert and alert-event list endpoints
// stop counting: past it they report this value as the total while rows keep
// coming (the alert and alert-event API reference says "capped at 1000").
const serverCountCap = 1000

// totalIsFloor reports whether Total is a capped count, which says only that at
// least that many rows match.
func (p listPage) totalIsFloor() bool { return p.TotalCapped && p.Total >= serverCountCap }

// offset returns how many rows precede this page, when that is knowable: always
// for the first page, and for later pages only with a known page size and base.
func (p listPage) offset() (int, bool) {
Expand Down Expand Up @@ -143,6 +156,8 @@ func results(n int) string {
// footer and the structured-mode stderr note ("" when no warning applies):
//
// a. total known: "showing N of T", plus a next-page hint when rows remain;
// a capped total reads "at least T", and has_next_page alone decides
// whether rows remain;
// b. else has_next_page/cursor known: true warns, false is complete;
// c. else a full page (N == limit) cannot tell: warn that more may exist;
// d. else only the plain count.
Expand Down Expand Up @@ -179,13 +194,18 @@ func (p listPage) statement(cmd *cobra.Command) (footer, note string) {
}
switch {
case p.totalKnown():
footer = fmt.Sprintf("Showing %s%s.", results(p.Count), paren(fmt.Sprintf("total %d", p.Total)))
if off, ok := p.offset(); ok && off+p.Count < p.Total {
body := fmt.Sprintf("showing %d of %d total results%s", p.Count, p.Total, paren(""))
if hint != "" {
body += ": " + hint
}
note = "note: " + body
total := strconv.Itoa(p.Total)
if p.totalIsFloor() {
total = "at least " + total
}
footer = fmt.Sprintf("Showing %s%s.", results(p.Count), paren("total "+total))
off, offKnown := p.offset()
remain := offKnown && off+p.Count < p.Total
if p.totalIsFloor() {
remain = p.More == moreYes
}
if remain {
_, note = warn(fmt.Sprintf("%d of %s total results%s", p.Count, total, paren("")), "")
}
return footer, note
case p.More == moreYes:
Expand Down Expand Up @@ -257,6 +277,14 @@ func (ctx *RunContext) PrintList(items any, cols []output.Column, count, page, l
return ctx.PrintPage(items, cols, listPage{Count: count, Page: page, Base: pageOne, Limit: limit, Total: total})
}

// PrintCappedList is PrintList for an endpoint whose total stops at
// serverCountCap; hasNext is the response's has_next_page.
func (ctx *RunContext) PrintCappedList(items any, cols []output.Column, count, page, limit, total int, hasNext bool) error {
return ctx.PrintPage(items, cols, listPage{
Count: count, Page: page, Base: pageOne, Limit: limit, Total: total, More: moreOf(hasNext), TotalCapped: true,
})
}

// WriteResult prints a success message as plain text or JSON.
func (ctx *RunContext) WriteResult(message string) {
writeResult(ctx.Writer, message)
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/incident.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ func newIncidentListCmd() *cobra.Command {
if len(proj) < len(result.Items) {
effectiveLimit = len(proj)
}
return ctx.PrintList(proj, nil, len(proj), page, effectiveLimit, int(result.Total))
return ctx.PrintCappedList(proj, nil, len(proj), page, effectiveLimit, int(result.Total), result.HasNextPage)
}

return ctx.PrintList(result.Items, incidentColumns(), len(result.Items), page, limit, int(result.Total))
return ctx.PrintCappedList(result.Items, incidentColumns(), len(result.Items), page, limit, int(result.Total), result.HasNextPage)
})
},
}
Expand Down
39 changes: 39 additions & 0 deletions internal/cli/list_completeness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,45 @@ func TestListPageStatement(t *testing.T) {
wantFooter: "Showing 5 results (page 1, total 45).",
wantNote: "note: showing 5 of 45 total results (page 1): raise --limit or use --page 2",
},
{
name: "a: capped total on page 1 is a floor",
cmd: pagedCmd(),
pg: listPage{Count: 100, Page: 1, Base: pageOne, Limit: 100, Total: 1000, More: moreYes, TotalCapped: true},
wantFooter: "Showing 100 results (page 1, total at least 1000).",
wantNote: "note: showing 100 of at least 1000 total results (page 1): raise --limit or use --page 2",
},
{
name: "a: capped total past the cap, has-more true",
cmd: pagedCmd(),
pg: listPage{Count: 39, Page: 11, Base: pageOne, Limit: 100, Total: 1000, More: moreYes, TotalCapped: true},
wantFooter: "Showing 39 results (page 11, total at least 1000).",
wantNote: "note: showing 39 of at least 1000 total results (page 11): raise --limit or use --page 12",
},
{
name: "a: capped total past the cap, has-more false is the end",
cmd: pagedCmd(),
pg: listPage{Count: 100, Page: 11, Base: pageOne, Limit: 100, Total: 1000, More: moreNo, TotalCapped: true},
wantFooter: "Showing 100 results (page 11, total at least 1000).",
},
{
name: "a: capped total, has-more false beats the page offset",
cmd: pagedCmd(),
pg: listPage{Count: 100, Page: 1, Base: pageOne, Limit: 100, Total: 1000, More: moreNo, TotalCapped: true},
wantFooter: "Showing 100 results (page 1, total at least 1000).",
},
{
name: "a: capped endpoint, a total below the cap is exact",
cmd: pagedCmd(),
pg: listPage{Count: 100, Page: 1, Base: pageOne, Limit: 100, Total: 999, More: moreYes, TotalCapped: true},
wantFooter: "Showing 100 results (page 1, total 999).",
wantNote: "note: showing 100 of 999 total results (page 1): raise --limit or use --page 2",
},
{
name: "a: uncapped endpoint, a total of 1000 is exact",
cmd: pagedCmd(),
pg: listPage{Count: 100, Page: 10, Base: pageOne, Limit: 100, Total: 1000},
wantFooter: "Showing 100 results (page 10, total 1000).",
},
{
name: "b: has-more true, singular noun",
cmd: pagedCmd(),
Expand Down
Loading