From 8a8bbc05be3e918e6ac32849a2b86dce1b7f5a2c Mon Sep 17 00:00:00 2001 From: Brian Chen <50983601+zychen5186@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:24:13 -0700 Subject: [PATCH] Integrate Bubbletea Pagination into get execution (#473) * feat: add pagination for get execution (draft) Signed-off-by: zychen5186 fix: catches existing commands in os.Args Signed-off-by: zychen5186 fix: restore neccessary codes Signed-off-by: zychen5186 * feat: use -i to trigger bubbletea, add pagination for get execution Signed-off-by: zychen5186 * change dot to arabic paging format Signed-off-by: zychen5186 change dot to arabic paging format Signed-off-by: zychen5186 change var names Signed-off-by: zychen5186 fix: lint Signed-off-by: zychen5186 * reuse JSONToTable Signed-off-by: zychen5186 * reuse JSONToTable Signed-off-by: zychen5186 change := to var Signed-off-by: zychen5186 * keep original format when not using bubbletea Signed-off-by: zychen5186 * improve readability and no functionality is changed Signed-off-by: zychen5186 --------- Signed-off-by: zychen5186 --- flytectl/cmd/config/config.go | 7 +- flytectl/cmd/get/execution.go | 19 +++ .../get/matchable_task_resource_attribute.go | 1 + flytectl/cmd/register/files.go | 2 +- flytectl/cmd/root.go | 1 + flytectl/go.mod | 24 +++- flytectl/go.sum | 42 ++++++ .../pkg/bubbletea/bubbletea_pagination.go | 74 ++++++++++ .../bubbletea/bubbletea_pagination_util.go | 136 ++++++++++++++++++ flytectl/pkg/printer/printer.go | 13 +- flytectl/pkg/printer/printer_test.go | 3 +- 11 files changed, 306 insertions(+), 16 deletions(-) create mode 100644 flytectl/pkg/bubbletea/bubbletea_pagination.go create mode 100644 flytectl/pkg/bubbletea/bubbletea_pagination_util.go diff --git a/flytectl/cmd/config/config.go b/flytectl/cmd/config/config.go index 8bf5cc986b..76b4b7c880 100644 --- a/flytectl/cmd/config/config.go +++ b/flytectl/cmd/config/config.go @@ -19,9 +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."` + 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 diff --git a/flytectl/cmd/get/execution.go b/flytectl/cmd/get/execution.go index abb89631f2..da597c5187 100644 --- a/flytectl/cmd/get/execution.go +++ b/flytectl/cmd/get/execution.go @@ -9,6 +9,9 @@ import ( "github.com/flyteorg/flytectl/cmd/config" "github.com/flyteorg/flytectl/cmd/config/subcommand/execution" 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" @@ -111,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 @@ -146,6 +159,12 @@ func getExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.Command 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)...) } diff --git a/flytectl/cmd/get/matchable_task_resource_attribute.go b/flytectl/cmd/get/matchable_task_resource_attribute.go index cfc5f4c6d4..c27a0b663d 100644 --- a/flytectl/cmd/get/matchable_task_resource_attribute.go +++ b/flytectl/cmd/get/matchable_task_resource_attribute.go @@ -2,6 +2,7 @@ package get import ( "context" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flytectl/cmd/config" sconfig "github.com/flyteorg/flytectl/cmd/config/subcommand" diff --git a/flytectl/cmd/register/files.go b/flytectl/cmd/register/files.go index f9b70696ab..bc2049902e 100644 --- a/flytectl/cmd/register/files.go +++ b/flytectl/cmd/register/files.go @@ -162,7 +162,7 @@ func Register(ctx context.Context, args []string, cfg *config.Config, cmdCtx cmd payload, _ := json.Marshal(registerResults) registerPrinter := printer.Printer{} - _ = registerPrinter.JSONToTable(payload, projectColumns) + _ = registerPrinter.JSONToTable(os.Stdout, payload, projectColumns) if tmpDir != "" { if _err := os.RemoveAll(tmpDir); _err != nil { logger.Errorf(ctx, "unable to delete temp dir %v due to %v", tmpDir, _err) diff --git a/flytectl/cmd/root.go b/flytectl/cmd/root.go index 418406c034..b1a97bbb5c 100644 --- a/flytectl/cmd/root.go +++ b/flytectl/cmd/root.go @@ -56,6 +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().BoolVarP(&(config.GetConfig().Interactive), "interactive", "i", false, "Set this flag to use an interactive CLI") rootCmd.AddCommand(get.CreateGetCommand()) compileCmd := compile.CreateCompileCommand() diff --git a/flytectl/go.mod b/flytectl/go.mod index 700445c958..9e492aa0ec 100644 --- a/flytectl/go.mod +++ b/flytectl/go.mod @@ -21,7 +21,7 @@ require ( github.com/hashicorp/go-version v1.3.0 github.com/hexops/gotextdiff v1.0.3 github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 - github.com/landoop/tableprinter v0.0.0-20180806200924-8bd8c2576d27 + github.com/landoop/tableprinter v0.0.0-20201125135848-89e81fc956e7 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 github.com/mouuff/go-rocket-update v1.5.1 @@ -66,9 +66,14 @@ require ( github.com/Microsoft/go-winio v0.5.0 // indirect github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/aws/aws-sdk-go v1.44.2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 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/bubbles v0.18.0 // indirect + github.com/charmbracelet/bubbletea v0.25.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.5.10 // indirect github.com/coocood/freecache v1.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect @@ -77,7 +82,7 @@ require ( github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/docker/distribution v2.8.0+incompatible // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect @@ -110,15 +115,21 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncw/swift v1.0.53 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -129,7 +140,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.4.1 // indirect @@ -141,7 +152,8 @@ require ( golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/time v0.1.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/flytectl/go.sum b/flytectl/go.sum index 88b64583da..0d6318416f 100644 --- a/flytectl/go.sum +++ b/flytectl/go.sum @@ -137,6 +137,8 @@ github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.2 h1:5VBk5r06bgxgRKVaUtm1/4NT/rtrnH2E4cnAYv5zgQc= github.com/aws/aws-sdk-go v1.44.2/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -161,6 +163,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +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= @@ -196,6 +204,8 @@ github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -327,6 +337,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -614,6 +626,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/landoop/tableprinter v0.0.0-20180806200924-8bd8c2576d27 h1:O664tckOIC4smyHDDJPXAh/YBYYc0Y1O8S5wmZDm3d8= github.com/landoop/tableprinter v0.0.0-20180806200924-8bd8c2576d27/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0= +github.com/landoop/tableprinter v0.0.0-20201125135848-89e81fc956e7 h1:J6LE/95ZXKZLdAG5xF+FF+h+CEKF78+UN5ZV8VJSCCk= +github.com/landoop/tableprinter v0.0.0-20201125135848-89e81fc956e7/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -632,9 +648,16 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -667,6 +690,14 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mouuff/go-rocket-update v1.5.1 h1:qGgUu/MP+aVQ63laEguRNimmNTPKs29xz0lZW6QRFaQ= github.com/mouuff/go-rocket-update v1.5.1/go.mod h1:CnOyUYCxAJyC1g1mebSGC7gJysLTlX+RpxKgD1B0zLs= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -780,8 +811,12 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1042,6 +1077,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1131,8 +1168,12 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= @@ -1429,6 +1470,7 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/flytectl/pkg/bubbletea/bubbletea_pagination.go b/flytectl/pkg/bubbletea/bubbletea_pagination.go new file mode 100644 index 0000000000..b79ff4da5a --- /dev/null +++ b/flytectl/pkg/bubbletea/bubbletea_pagination.go @@ -0,0 +1,74 @@ +package bubbletea + +import ( + "fmt" + "log" + "strings" + + "github.com/charmbracelet/bubbles/paginator" + "github.com/flyteorg/flytectl/pkg/printer" + "github.com/golang/protobuf/proto" + + tea "github.com/charmbracelet/bubbletea" +) + +type pageModel struct { + items []proto.Message + paginator paginator.Model +} + +func newModel(initMsg []proto.Message) pageModel { + p := paginator.New() + p.PerPage = msgPerPage + p.SetTotalPages(len(initMsg)) + + return pageModel{ + paginator: p, + items: initMsg, + } +} + +func (m pageModel) Init() tea.Cmd { + return nil +} + +func (m pageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + return m, tea.Quit + } + } + m.paginator, cmd = m.paginator.Update(msg) + preFetchBatch(&m) + return m, cmd +} + +func (m pageModel) View() string { + var b strings.Builder + table, err := getTable(&m) + if err != nil { + return "" + } + b.WriteString(table) + b.WriteString(fmt.Sprintf(" PAGE - %d\n", m.paginator.Page+1)) + b.WriteString("\n\n h/l ←/→ page • q: quit\n") + return b.String() +} + +func Paginator(_listHeader []printer.Column, _callback DataCallback) { + listHeader = _listHeader + callback = _callback + + var msg []proto.Message + for i := firstBatchIndex; i < lastBatchIndex+1; i++ { + msg = append(msg, getMessageList(i)...) + } + + p := tea.NewProgram(newModel(msg)) + if _, err := p.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/flytectl/pkg/bubbletea/bubbletea_pagination_util.go b/flytectl/pkg/bubbletea/bubbletea_pagination_util.go new file mode 100644 index 0000000000..68d52d858a --- /dev/null +++ b/flytectl/pkg/bubbletea/bubbletea_pagination_util.go @@ -0,0 +1,136 @@ +package bubbletea + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/flyteorg/flytectl/pkg/filters" + "github.com/flyteorg/flytectl/pkg/printer" + + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" +) + +type DataCallback func(filter filters.Filters) []proto.Message + +type PrintableProto struct{ proto.Message } + +const ( + msgPerBatch = 100 // Please set msgPerBatch as a multiple of msgPerPage + msgPerPage = 10 + pagePerBatch = msgPerBatch / msgPerPage +) + +var ( + // Used for indexing local stored rows + localPageIndex int + // Recording batch index fetched from admin + firstBatchIndex int32 = 1 + lastBatchIndex int32 = 10 + batchLen = make(map[int32]int) + // Callback function used to fetch data from the module that called bubbletea pagination. + 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) + err := marshaller.Marshal(buf, p.Message) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func getSliceBounds(idx int, length int) (start int, end int) { + start = idx * msgPerPage + end = min(idx*msgPerPage+msgPerPage, length) + return start, end +} + +func getTable(m *pageModel) (string, error) { + start, end := getSliceBounds(localPageIndex, len(m.items)) + curShowMessage := m.items[start:end] + printableMessages := make([]*PrintableProto, 0, len(curShowMessage)) + for _, m := range curShowMessage { + printableMessages = append(printableMessages, &PrintableProto{Message: m}) + } + + jsonRows, err := json.Marshal(printableMessages) + if err != nil { + return "", fmt.Errorf("failed to marshal proto messages") + } + + var buf strings.Builder + p := printer.Printer{} + if err := p.JSONToTable(&buf, jsonRows, listHeader); err != nil { + return "", err + } + + return buf.String(), nil +} + +func getMessageList(batchIndex int32) []proto.Message { + msg := callback(filters.Filters{ + Limit: msgPerBatch, + Page: batchIndex, + SortBy: "created_at", + Asc: false, + }) + batchLen[batchIndex] = len(msg) + + return msg +} + +func countTotalPages() int { + sum := 0 + for _, l := range batchLen { + sum += l + } + return sum +} + +// Only (lastBatchIndex-firstBatchIndex)*msgPerBatch of rows are stored in local memory. +// When user tries to get rows out of this range, this function will be triggered. +func preFetchBatch(m *pageModel) { + localPageIndex = m.paginator.Page - int(firstBatchIndex-1)*pagePerBatch + + // Triggers when user is at the last local page + if localPageIndex+1 == len(m.items)/msgPerPage { + newMessages := getMessageList(lastBatchIndex + 1) + m.paginator.SetTotalPages(countTotalPages()) + if len(newMessages) != 0 { + lastBatchIndex++ + m.items = append(m.items, newMessages...) + m.items = m.items[batchLen[firstBatchIndex]:] // delete the msgs in the "firstBatchIndex" batch + localPageIndex -= batchLen[firstBatchIndex] / msgPerPage + firstBatchIndex++ + } + return + } + // Triggers when user is at the first local page + if localPageIndex == 0 && firstBatchIndex > 1 { + newMessages := getMessageList(firstBatchIndex - 1) + m.paginator.SetTotalPages(countTotalPages()) + firstBatchIndex-- + m.items = append(newMessages, m.items...) + m.items = m.items[:len(m.items)-batchLen[lastBatchIndex]] // delete the msgs in the "lastBatchIndex" batch + localPageIndex += batchLen[firstBatchIndex] / msgPerPage + lastBatchIndex-- + return + } +} diff --git a/flytectl/pkg/printer/printer.go b/flytectl/pkg/printer/printer.go index e3b406b927..6dcf98d73e 100644 --- a/flytectl/pkg/printer/printer.go +++ b/flytectl/pkg/printer/printer.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "net/url" "os" "sort" @@ -112,7 +113,7 @@ func projectColumns(rows []interface{}, column []Column) [][]string { return responses } -func (p Printer) JSONToTable(jsonRows []byte, columns []Column) error { +func (p Printer) JSONToTable(w io.Writer, jsonRows []byte, columns []Column) error { var rawRows []interface{} if err := json.Unmarshal(jsonRows, &rawRows); err != nil { return errors.Wrapf("JSONUnmarshalFailure", err, "failed to unmarshal into []interface{} from json") @@ -122,7 +123,7 @@ func (p Printer) JSONToTable(jsonRows []byte, columns []Column) error { } rows := projectColumns(rawRows, columns) - printer := tableprinter.New(os.Stdout) + printer := tableprinter.New(w) // TODO make this configurable printer.AutoWrapText = false printer.BorderLeft = true @@ -141,7 +142,9 @@ func (p Printer) JSONToTable(jsonRows []byte, columns []Column) error { if r := printer.Render(headers, rows, positions, true); r == -1 { return fmt.Errorf("failed to render table") } - fmt.Printf("%d rows\n", len(rows)) + if w == os.Stdout { + fmt.Printf("%d rows\n", len(rows)) + } return nil } @@ -155,7 +158,7 @@ func (p Printer) PrintInterface(format OutputFormat, columns []Column, v interfa case OutputFormatJSON, OutputFormatYAML: return printJSONYaml(format, v) default: // Print table - return p.JSONToTable(jsonRows, columns) + return p.JSONToTable(os.Stdout, jsonRows, columns) } } @@ -285,7 +288,7 @@ func (p Printer) Print(format OutputFormat, columns []Column, messages ...proto. if err != nil { return errors.Wrapf("ProtoToJSONFailure", err, "failed to marshal proto messages") } - return p.JSONToTable(rows, columns) + return p.JSONToTable(os.Stdout, rows, columns) } return nil } diff --git a/flytectl/pkg/printer/printer_test.go b/flytectl/pkg/printer/printer_test.go index 06d4c2c31d..eb4960a878 100644 --- a/flytectl/pkg/printer/printer_test.go +++ b/flytectl/pkg/printer/printer_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "testing" "time" @@ -53,7 +54,7 @@ func TestJSONToTable(t *testing.T) { b, err := json.Marshal(j) assert.NoError(t, err) p := Printer{} - assert.NoError(t, p.JSONToTable(b, []Column{ + assert.NoError(t, p.JSONToTable(os.Stdout, b, []Column{ {"A", "$.a", &trunc}, {"S", "$.s.y", nil}, }))