Skip to content
This repository has been archived by the owner on May 31, 2024. It is now read-only.

Commit

Permalink
feat: use -i to trigger bubbletea, add pagination for get execution
Browse files Browse the repository at this point in the history
Signed-off-by: zychen5186 <brianchen5197@gmail.com>
  • Loading branch information
zychen5186 committed Apr 23, 2024
1 parent 92daa03 commit 2bb1095
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 123 deletions.
8 changes: 4 additions & 4 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ var (

// Config hold configration for flytectl flag
type Config struct {
Project string `json:"project" pflag:",Specifies the project to work on."`
Domain string `json:"domain" pflag:",Specifies the domain to work on."`
Output string `json:"output" pflag:",Specifies the output type."`
Format string `json:"format" pflag:",Specifies the CLI format"` // Form
Project string `json:"project" pflag:",Specifies the project to work on."`
Domain string `json:"domain" pflag:",Specifies the domain to work on."`
Output string `json:"output" pflag:",Specifies the output type."`
Interactive bool `json:"interactive" pflag:",Set this to trigger bubbletea interface."`
}

// OutputFormat will return output formate
Expand Down
22 changes: 18 additions & 4 deletions cmd/get/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
cmdCore "github.com/flyteorg/flytectl/cmd/core"

"github.com/flyteorg/flytectl/pkg/bubbletea"
"github.com/flyteorg/flytectl/pkg/filters"
"github.com/flyteorg/flytectl/pkg/printer"

"github.com/golang/protobuf/proto"
Expand Down Expand Up @@ -113,6 +114,16 @@ func ExecutionToProtoMessages(l []*admin.Execution) []proto.Message {
return messages
}

func getCallBack(ctx context.Context, cmdCtx cmdCore.CommandContext) bubbletea.DataCallback {
return func(filter filters.Filters) []proto.Message {
executionList, err := cmdCtx.AdminFetcherExt().ListExecution(ctx, config.GetConfig().Project, config.GetConfig().Domain, filter)
if err != nil {
return nil
}
return ExecutionToProtoMessages(executionList.Executions)
}
}

func getExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.CommandContext) error {
adminPrinter := printer.Printer{}
var executions []*admin.Execution
Expand Down Expand Up @@ -149,8 +160,11 @@ func getExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.Command
}
logger.Infof(ctx, "Retrieved %v executions", len(executionList.Executions))

bubbletea.BubbleteaPaginator(executionColumns, ExecutionToProtoMessages(executionList.Executions)...)
return nil
// return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns,
// ExecutionToProtoMessages(executionList.Executions)...)
if config.GetConfig().Interactive {
bubbletea.BubbleteaPaginator(executionColumns, getCallBack(ctx, cmdCtx))
return nil
}

return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns,
ExecutionToProtoMessages(executionList.Executions)...)
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func newRootCmd() *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&(config.GetConfig().Project), "project", "p", "", "Specifies the Flyte project.")
rootCmd.PersistentFlags().StringVarP(&(config.GetConfig().Domain), "domain", "d", "", "Specifies the Flyte project's domain.")
rootCmd.PersistentFlags().StringVarP(&(config.GetConfig().Output), "output", "o", printer.OutputFormatTABLE.String(), fmt.Sprintf("Specifies the output type - supported formats %s. NOTE: dot, doturl are only supported for Workflow", printer.OutputFormats()))
rootCmd.PersistentFlags().StringVarP(&(config.GetConfig().Format), "format", "f", "", "Set this flag to 'bubbletea' to use a more interactive CLI")
rootCmd.PersistentFlags().BoolVarP(&(config.GetConfig().Interactive), "interactive", "i", false, "Set this flag to use an interactive CLI")

rootCmd.AddCommand(get.CreateGetCommand())
compileCmd := compile.CreateCompileCommand()
Expand Down
37 changes: 12 additions & 25 deletions pkg/bubbletea/bubbletea_pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,25 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

func newModel() pageModel {
// var items []string
// for _, row := range content {
// items = append(items, row[0])
// }

// for i := 1; i < 101; i++ {
// text := fmt.Sprintf("Item %d", i)
// items = append(items, text)
// }
type pageModel struct {
items []proto.Message
paginator paginator.Model
}

func newModel(initMsg []proto.Message) pageModel {
p := paginator.New()
p.Type = paginator.Dots
p.PerPage = 10
p.PerPage = defaultMsgPerPage
p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•")
p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•")
p.SetTotalPages(len(messages))
p.SetTotalPages(len(initMsg))

return pageModel{
paginator: p,
items: messages,
items: initMsg,
}
}

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

func (m pageModel) Init() tea.Cmd {
return nil
}
Expand All @@ -57,28 +47,25 @@ func (m pageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
m.paginator, cmd = m.paginator.Update(msg)
preFetchPage(&m)
return m, cmd
}

func (m pageModel) View() string {
var b strings.Builder
// b.WriteString("\n Paginator Example\n\n")
start, end := m.paginator.GetSliceBounds(len(m.items))
table, err := printTable(start, end)
table, err := printTable(&m, start, end)
if err != nil {
return ""
}
b.WriteString(table)
// for _, item := range m.items[start:end] {
// b.WriteString(" • " + item + "\n\n")
// }
b.WriteString(" " + m.paginator.View())
b.WriteString("\n\n h/l ←/→ page • q: quit\n")
return b.String()
}

func showPagination() {
p := tea.NewProgram(newModel())
func showPagination(initMsg []proto.Message) {
p := tea.NewProgram(newModel(initMsg))
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
Expand Down
165 changes: 76 additions & 89 deletions pkg/bubbletea/bubbletea_pagination_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,40 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"

"github.com/flyteorg/flytectl/cmd/config"
"github.com/flyteorg/flytectl/pkg/filters"
"github.com/flyteorg/flytectl/pkg/printer"
"github.com/kataras/tablewriter"
"github.com/landoop/tableprinter"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/kataras/tablewriter"
"github.com/landoop/tableprinter"
"github.com/yalp/jsonpath"
)

var (
messages []proto.Message
columns []printer.Column
)
type DataCallback func(filter filters.Filters) []proto.Message

type PrintableProto struct{ proto.Message }

const (
tab = "\t"
defaultLimit = 100
defaultMsgPerPage = 10
)

type PrintableProto struct {
proto.Message
}
var (
firstBatchIndex int32 = 1
lastBatchIndex int32 = 10
batchLen = make(map[int32]int)

var marshaller = jsonpb.Marshaler{
Indent: tab,
}
// Callback function from the module that called bubbleteapagination, which is used to fetch data
callback DataCallback
// The header of the table
listHeader []printer.Column

marshaller = jsonpb.Marshaler{
Indent: "\t",
}
)

func (p PrintableProto) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
Expand All @@ -48,8 +52,8 @@ func extractRow(data interface{}, columns []printer.Column) []string {
if columns == nil || data == nil {
return nil
}
tableData := make([]string, 0, len(columns))

tableData := make([]string, 0, len(columns))
for _, c := range columns {
out, err := jsonpath.Read(data, c.JSONPath)
if err != nil || out == nil {
Expand All @@ -75,63 +79,8 @@ func projectColumns(rows []interface{}, column []printer.Column) [][]string {
return responses
}

func BubbleteaPaginator(_columns []printer.Column, _messages ...proto.Message) {
if config.GetConfig().Format != "bubbletea" {
return
}
columns = _columns
messages = _messages

showPagination()
}

// func capture() func() (string, error) {
// r, w, err := os.Pipe()
// if err != nil {
// panic(err)
// }

// done := make(chan error, 1)

// save := os.Stdout
// os.Stdout = w

// var buf strings.Builder

// go func() {
// _, err := io.Copy(&buf, r)
// r.Close()
// done <- err
// }()

// return func() (string, error) {
// os.Stdout = save
// w.Close()
// err := <-done
// return buf.String(), err
// }
// }

func printTable(start int, end int) (string, error) {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}

done := make(chan error, 1)

save := os.Stdout
os.Stdout = w

var buf strings.Builder

go func() {
_, err := io.Copy(&buf, r)
r.Close()
done <- err
}()

curShowMessage := messages[start:end]
func printTable(m *pageModel, start int, end int) (string, error) {
curShowMessage := m.items[start:end]
printableMessages := make([]*PrintableProto, 0, len(curShowMessage))
for _, m := range curShowMessage {
printableMessages = append(printableMessages, &PrintableProto{Message: m})
Expand All @@ -149,10 +98,10 @@ func printTable(start int, end int) (string, error) {
if rawRows == nil {
return "", fmt.Errorf("expected one row or empty rows, received nil")
}
rows := projectColumns(rawRows, columns)
rows := projectColumns(rawRows, listHeader)

printer := tableprinter.New(os.Stdout)
// TODO make this configurable
var buf strings.Builder
printer := tableprinter.New(&buf)
printer.AutoWrapText = false
printer.BorderLeft = true
printer.BorderRight = true
Expand All @@ -161,26 +110,64 @@ func printTable(start int, end int) (string, error) {
printer.RowLine = true
printer.ColumnSeparator = "|"
printer.HeaderBgColor = tablewriter.BgHiWhiteColor
headers := make([]string, 0, len(columns))
positions := make([]int, 0, len(columns))
for _, c := range columns {
headers := make([]string, 0, len(listHeader))
positions := make([]int, 0, len(listHeader))
for _, c := range listHeader {
headers = append(headers, c.Header)
positions = append(positions, 30)
}

// done := capture()
if r := printer.Render(headers, rows, positions, true); r == -1 {
return "", fmt.Errorf("failed to render table")
}

os.Stdout = save
w.Close()
err = <-done
return buf.String(), nil
}

func getMessageList(batchPage int32) []proto.Message {
msg := callback(filters.Filters{
Limit: defaultLimit,
Page: batchPage,
SortBy: "created_at",
Asc: false,
})
batchLen[batchPage] = len(msg)

// out, err := done()
if err != nil {
return "", err
return msg
}

func BubbleteaPaginator(_listHeader []printer.Column, _callback DataCallback) {
listHeader = _listHeader
callback = _callback

msg := []proto.Message{}
for i := firstBatchIndex; i < lastBatchIndex+1; i++ {
msg = append(msg, getMessageList(int32(i))...)
}

return buf.String(), nil
showPagination(msg)
}

func preFetchPage(m *pageModel) {
// Triggers when user is at the last page
if len(m.items)/defaultMsgPerPage == m.paginator.Page+1 {
newMessages := getMessageList(lastBatchIndex + 1)
if len(newMessages) != 0 {
lastBatchIndex += 1
m.items = append(m.items, newMessages...)
m.items = m.items[batchLen[firstBatchIndex]:] // delete the msgs in the "firstBatchIndex" batch
m.paginator.Page -= batchLen[firstBatchIndex] / defaultMsgPerPage
firstBatchIndex += 1
}
}
// Triggers when user is at the first page
if m.paginator.Page == 0 && firstBatchIndex > 1 {
newMessages := getMessageList(firstBatchIndex - 1)
firstBatchIndex -= 1
m.items = append(m.items, newMessages...)
m.items = m.items[:len(m.items)-batchLen[lastBatchIndex]] // delete the msgs in the "lastBatchIndex" batch
m.paginator.Page += batchLen[firstBatchIndex] / defaultMsgPerPage
lastBatchIndex -= 1
}
m.paginator.SetTotalPages(len(m.items))
}

0 comments on commit 2bb1095

Please sign in to comment.