Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefetch functionality for paginator #5310

Merged
merged 3 commits into from
May 9, 2024
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
16 changes: 7 additions & 9 deletions flytectl/cmd/get/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@
}

func getCallBack(ctx context.Context, cmdCtx cmdCore.CommandContext) bubbletea.DataCallback {
return func(filter filters.Filters) []proto.Message {
return func(filter filters.Filters) ([]proto.Message, error) {

Check warning on line 118 in flytectl/cmd/get/execution.go

View check run for this annotation

Codecov / codecov/patch

flytectl/cmd/get/execution.go#L118

Added line #L118 was not covered by tests
executionList, err := cmdCtx.AdminFetcherExt().ListExecution(ctx, config.GetConfig().Project, config.GetConfig().Domain, filter)
if err != nil {
return nil
return nil, err

Check warning on line 121 in flytectl/cmd/get/execution.go

View check run for this annotation

Codecov / codecov/patch

flytectl/cmd/get/execution.go#L121

Added line #L121 was not covered by tests
}
return ExecutionToProtoMessages(executionList.Executions)
return ExecutionToProtoMessages(executionList.Executions), nil

Check warning on line 123 in flytectl/cmd/get/execution.go

View check run for this annotation

Codecov / codecov/patch

flytectl/cmd/get/execution.go#L123

Added line #L123 was not covered by tests
}
}

Expand Down Expand Up @@ -154,17 +154,15 @@
return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns,
ExecutionToProtoMessages(executions)...)
}
if config.GetConfig().Interactive {
err := bubbletea.Paginator(executionColumns, getCallBack(ctx, cmdCtx), execution.DefaultConfig.Filter)
return err

Check warning on line 159 in flytectl/cmd/get/execution.go

View check run for this annotation

Codecov / codecov/patch

flytectl/cmd/get/execution.go#L158-L159

Added lines #L158 - L159 were not covered by tests
}
executionList, err := cmdCtx.AdminFetcherExt().ListExecution(ctx, config.GetConfig().Project, config.GetConfig().Domain, execution.DefaultConfig.Filter)
if err != nil {
return err
}
logger.Infof(ctx, "Retrieved %v executions", len(executionList.Executions))

if config.GetConfig().Interactive {
bubbletea.Paginator(executionColumns, getCallBack(ctx, cmdCtx))
return nil
}

return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns,
ExecutionToProtoMessages(executionList.Executions)...)
}
1 change: 1 addition & 0 deletions flytectl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charmbracelet/lipgloss v0.10.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/containerd/containerd v1.6.26 // indirect
github.com/containerd/log v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions flytectl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
Expand Down
123 changes: 113 additions & 10 deletions flytectl/pkg/bubbletea/bubbletea_pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,175 @@ package bubbletea
import (
"fmt"
"log"
"math"
"strings"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/paginator"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/lipgloss"
"github.com/flyteorg/flyte/flytectl/pkg/filters"
"github.com/flyteorg/flyte/flytectl/pkg/printer"
"github.com/golang/protobuf/proto"

tea "github.com/charmbracelet/bubbletea"
)

var (
spin = false
// Avoid fetching multiple times while still fetching
fetchingBackward = false
fetchingForward = false
)
zychen5186 marked this conversation as resolved.
Show resolved Hide resolved

type pageModel struct {
items []proto.Message
items *[]proto.Message
paginator paginator.Model
spinner spinner.Model
}

func newModel(initMsg []proto.Message) pageModel {
p := paginator.New()
p.PerPage = msgPerPage
p.SetTotalPages(len(initMsg))
p.Page = int(filter.Page) - 1
// Set the upper bound of the page number
p.SetTotalPages(getLastMsgIdx())

s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("56"))
s.Spinner = spinner.Points

return pageModel{
paginator: p,
items: initMsg,
spinner: s,
items: &initMsg,
}
}

func (m pageModel) Init() tea.Cmd {
return nil
return m.spinner.Tick
}

func (m pageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case error:
return m, tea.Quit
case tea.KeyMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
return m, tea.Quit
}
switch {
case key.Matches(msg, m.paginator.KeyMap.PrevPage):
// If previous page will be out of the range of the first batch, don't update
if m.paginator.Page == firstBatchIndex*pagePerBatch {
return m, nil
}
}
case spinner.TickMsg:
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
case newDataMsg:
if msg.fetchDirection == forward {
// Update if current page is in the range of the last batch
// i.e. if user not in last batch when finished fetching, don't update
if m.paginator.Page/pagePerBatch >= lastBatchIndex {
*m.items = append(*m.items, msg.newItems...)
lastBatchIndex++
if lastBatchIndex-firstBatchIndex >= localBatchLimit {
*m.items = (*m.items)[batchLen[firstBatchIndex]:]
firstBatchIndex++
}
}
fetchingForward = false
} else {
// Update if current page is in the range of the first batch
// i.e. if user not in first batch when finished fetching, don't update
if m.paginator.Page/pagePerBatch <= firstBatchIndex {
*m.items = append(msg.newItems, *m.items...)
firstBatchIndex--
if lastBatchIndex-firstBatchIndex >= localBatchLimit {
*m.items = (*m.items)[:len(*m.items)-batchLen[lastBatchIndex]]
lastBatchIndex--
}
}
fetchingBackward = false
}
// Set the upper bound of the page number
m.paginator.SetTotalPages(getLastMsgIdx())
return m, nil
}
m.paginator, cmd = m.paginator.Update(msg)
preFetchBatch(&m)

m.paginator, _ = m.paginator.Update(msg)

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.paginator.KeyMap.NextPage):
if (m.paginator.Page >= (lastBatchIndex+1)*pagePerBatch-prefetchThreshold) && !fetchingForward {
// If no more data, don't fetch again (won't show spinner)
value, ok := batchLen[lastBatchIndex+1]
if !ok || value != 0 {
fetchingForward = true
cmd = fetchDataCmd(lastBatchIndex+1, forward)
}
}
case key.Matches(msg, m.paginator.KeyMap.PrevPage):
if (m.paginator.Page <= firstBatchIndex*pagePerBatch+prefetchThreshold) && (firstBatchIndex > 0) && !fetchingBackward {
fetchingBackward = true
cmd = fetchDataCmd(firstBatchIndex-1, backward)
}
}
}

return m, cmd
}

func (m pageModel) View() string {
var b strings.Builder
table, err := getTable(&m)
if err != nil {
return ""
return "Error rendering table"
}
b.WriteString(table)
b.WriteString(fmt.Sprintf(" PAGE - %d\n", m.paginator.Page+1))
b.WriteString(fmt.Sprintf(" PAGE - %d ", m.paginator.Page+1))
if spin {
b.WriteString(fmt.Sprintf("%s%s", m.spinner.View(), " Loading new pages..."))
}
b.WriteString("\n\n h/l ←/→ page • q: quit\n")

return b.String()
}

func Paginator(_listHeader []printer.Column, _callback DataCallback) {
func Paginator(_listHeader []printer.Column, _callback DataCallback, _filter filters.Filters) error {
listHeader = _listHeader
callback = _callback
filter = _filter
filter.Page = int32(_max(int(filter.Page), 1))
firstBatchIndex = (int(filter.Page) - 1) / pagePerBatch
lastBatchIndex = firstBatchIndex

var msg []proto.Message
for i := firstBatchIndex; i < lastBatchIndex+1; i++ {
msg = append(msg, getMessageList(i)...)
newMessages, err := getMessageList(i)
if err != nil {
return err
}
if int(filter.Page)-(firstBatchIndex*pagePerBatch) > int(math.Ceil(float64(len(newMessages))/msgPerPage)) {
return fmt.Errorf("the specified page has no data, please enter a valid page number")
}
msg = append(msg, newMessages...)
}

p := tea.NewProgram(newModel(msg))
if _, err := p.Run(); err != nil {
log.Fatal(err)
}

if errMsg != nil {
return errMsg
}

return nil
}
Loading
Loading