From 92daa03ba6eead99357c7ca13c65e2ca36496360 Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Fri, 19 Apr 2024 10:57:10 -0700 Subject: [PATCH 1/7] 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 --- cmd/config/config.go | 1 + cmd/get/execution.go | 9 +- cmd/root.go | 1 + go.mod | 24 ++- go.sum | 42 +++++ pkg/bubbletea/bubbletea_pagination.go | 85 ++++++++++ pkg/bubbletea/bubbletea_pagination_util.go | 186 +++++++++++++++++++++ 7 files changed, 340 insertions(+), 8 deletions(-) create mode 100644 pkg/bubbletea/bubbletea_pagination.go create mode 100644 pkg/bubbletea/bubbletea_pagination_util.go diff --git a/cmd/config/config.go b/cmd/config/config.go index 8bf5cc98..b99c79f0 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -22,6 +22,7 @@ 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 } // OutputFormat will return output formate diff --git a/cmd/get/execution.go b/cmd/get/execution.go index abb89631..08f557c5 100644 --- a/cmd/get/execution.go +++ b/cmd/get/execution.go @@ -9,6 +9,8 @@ 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/printer" "github.com/golang/protobuf/proto" @@ -146,6 +148,9 @@ func getExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.Command return err } logger.Infof(ctx, "Retrieved %v executions", len(executionList.Executions)) - return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns, - ExecutionToProtoMessages(executionList.Executions)...) + + bubbletea.BubbleteaPaginator(executionColumns, ExecutionToProtoMessages(executionList.Executions)...) + return nil + // return adminPrinter.Print(config.GetConfig().MustOutputFormat(), executionColumns, + // ExecutionToProtoMessages(executionList.Executions)...) } diff --git a/cmd/root.go b/cmd/root.go index 418406c0..db7d51e8 100644 --- a/cmd/root.go +++ b/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().StringVarP(&(config.GetConfig().Format), "format", "f", "", "Set this flag to 'bubbletea' to use a more interactive CLI") rootCmd.AddCommand(get.CreateGetCommand()) compileCmd := compile.CreateCompileCommand() diff --git a/go.mod b/go.mod index 700445c9..9e492aa0 100644 --- a/go.mod +++ b/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/go.sum b/go.sum index 88b64583..0d631841 100644 --- a/go.sum +++ b/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/pkg/bubbletea/bubbletea_pagination.go b/pkg/bubbletea/bubbletea_pagination.go new file mode 100644 index 00000000..d8696773 --- /dev/null +++ b/pkg/bubbletea/bubbletea_pagination.go @@ -0,0 +1,85 @@ +package bubbletea + +// A simple program demonstrating the paginator component from the Bubbles +// component library. + +import ( + "log" + "strings" + + "github.com/charmbracelet/bubbles/paginator" + "github.com/charmbracelet/lipgloss" + "github.com/golang/protobuf/proto" + + 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) + // } + + p := paginator.New() + p.Type = paginator.Dots + p.PerPage = 10 + 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)) + + return pageModel{ + paginator: p, + items: messages, + } +} + +type pageModel struct { + items []proto.Message + paginator paginator.Model +} + +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) + 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) + 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()) + if _, err := p.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go new file mode 100644 index 00000000..010e9283 --- /dev/null +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -0,0 +1,186 @@ +package bubbletea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "strings" + + "github.com/flyteorg/flytectl/cmd/config" + "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/yalp/jsonpath" +) + +var ( + messages []proto.Message + columns []printer.Column +) + +const ( + tab = "\t" +) + +type PrintableProto struct { + proto.Message +} + +var marshaller = jsonpb.Marshaler{ + Indent: tab, +} + +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 extractRow(data interface{}, columns []printer.Column) []string { + if columns == nil || data == nil { + return nil + } + tableData := make([]string, 0, len(columns)) + + for _, c := range columns { + out, err := jsonpath.Read(data, c.JSONPath) + if err != nil || out == nil { + out = "" + } + s := fmt.Sprintf("%s", out) + if c.TruncateTo != nil { + t := *c.TruncateTo + if len(s) > t { + s = s[:t] + } + } + tableData = append(tableData, s) + } + return tableData +} + +func projectColumns(rows []interface{}, column []printer.Column) [][]string { + responses := make([][]string, 0, len(rows)) + for _, row := range rows { + responses = append(responses, extractRow(row, column)) + } + 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] + 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 rawRows []interface{} + if err := json.Unmarshal(jsonRows, &rawRows); err != nil { + return "", fmt.Errorf("failed to unmarshal into []interface{} from json") + } + if rawRows == nil { + return "", fmt.Errorf("expected one row or empty rows, received nil") + } + rows := projectColumns(rawRows, columns) + + printer := tableprinter.New(os.Stdout) + // TODO make this configurable + printer.AutoWrapText = false + printer.BorderLeft = true + printer.BorderRight = true + printer.BorderBottom = true + printer.BorderTop = true + 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 = 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 + + // out, err := done() + if err != nil { + return "", err + } + + return buf.String(), nil +} From 2bb1095b8850e33b98acac8abc0184483c59cc02 Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Mon, 22 Apr 2024 22:31:25 -0700 Subject: [PATCH 2/7] feat: use -i to trigger bubbletea, add pagination for get execution Signed-off-by: zychen5186 --- cmd/config/config.go | 8 +- cmd/get/execution.go | 22 ++- cmd/root.go | 2 +- pkg/bubbletea/bubbletea_pagination.go | 37 ++--- pkg/bubbletea/bubbletea_pagination_util.go | 165 ++++++++++----------- 5 files changed, 111 insertions(+), 123 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index b99c79f0..76b4b7c8 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -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 diff --git a/cmd/get/execution.go b/cmd/get/execution.go index 08f557c5..a1d4e408 100644 --- a/cmd/get/execution.go +++ b/cmd/get/execution.go @@ -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" @@ -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 @@ -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)...) } diff --git a/cmd/root.go b/cmd/root.go index db7d51e8..b1a97bbb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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() diff --git a/pkg/bubbletea/bubbletea_pagination.go b/pkg/bubbletea/bubbletea_pagination.go index d8696773..a15402f0 100644 --- a/pkg/bubbletea/bubbletea_pagination.go +++ b/pkg/bubbletea/bubbletea_pagination.go @@ -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 } @@ -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) } diff --git a/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go index 010e9283..11ede5f1 100644 --- a/pkg/bubbletea/bubbletea_pagination_util.go +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -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) @@ -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 { @@ -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}) @@ -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 @@ -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)) } From 44e26766ec3f7e188e73f8fc2c8151e501c37f07 Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Mon, 22 Apr 2024 23:10:16 -0700 Subject: [PATCH 3/7] 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 --- cmd/get/execution.go | 2 +- pkg/bubbletea/bubbletea_pagination.go | 11 +++------ pkg/bubbletea/bubbletea_pagination_util.go | 27 +++++++++++----------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/cmd/get/execution.go b/cmd/get/execution.go index a1d4e408..da597c51 100644 --- a/cmd/get/execution.go +++ b/cmd/get/execution.go @@ -161,7 +161,7 @@ func getExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.Command logger.Infof(ctx, "Retrieved %v executions", len(executionList.Executions)) if config.GetConfig().Interactive { - bubbletea.BubbleteaPaginator(executionColumns, getCallBack(ctx, cmdCtx)) + bubbletea.Paginator(executionColumns, getCallBack(ctx, cmdCtx)) return nil } diff --git a/pkg/bubbletea/bubbletea_pagination.go b/pkg/bubbletea/bubbletea_pagination.go index a15402f0..0eeeac9c 100644 --- a/pkg/bubbletea/bubbletea_pagination.go +++ b/pkg/bubbletea/bubbletea_pagination.go @@ -1,14 +1,11 @@ package bubbletea -// A simple program demonstrating the paginator component from the Bubbles -// component library. - import ( + "fmt" "log" "strings" "github.com/charmbracelet/bubbles/paginator" - "github.com/charmbracelet/lipgloss" "github.com/golang/protobuf/proto" tea "github.com/charmbracelet/bubbletea" @@ -21,10 +18,7 @@ type pageModel struct { func newModel(initMsg []proto.Message) pageModel { p := paginator.New() - p.Type = paginator.Dots 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(initMsg)) return pageModel{ @@ -59,7 +53,8 @@ func (m pageModel) View() string { return "" } b.WriteString(table) - b.WriteString(" " + m.paginator.View()) + currentPage := int(firstBatchIndex-1)*pagePerBatch + m.paginator.Page + 1 + b.WriteString(fmt.Sprintf(" PAGE - %d\n", currentPage)) b.WriteString("\n\n h/l ←/→ page • q: quit\n") return b.String() } diff --git a/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go index 11ede5f1..a904caa7 100644 --- a/pkg/bubbletea/bubbletea_pagination_util.go +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -20,8 +20,9 @@ type DataCallback func(filter filters.Filters) []proto.Message type PrintableProto struct{ proto.Message } const ( - defaultLimit = 100 - defaultMsgPerPage = 10 + defaultMsgPerBatch = 100 + defaultMsgPerPage = 10 + pagePerBatch = defaultMsgPerBatch / defaultMsgPerPage ) var ( @@ -29,7 +30,7 @@ var ( lastBatchIndex int32 = 10 batchLen = make(map[int32]int) - // Callback function from the module that called bubbleteapagination, which is used to fetch data + // Callback function used to fetch data from the module that called bubbletea pagination. callback DataCallback // The header of the table listHeader []printer.Column @@ -124,25 +125,25 @@ func printTable(m *pageModel, start int, end int) (string, error) { return buf.String(), nil } -func getMessageList(batchPage int32) []proto.Message { +func getMessageList(batchIndex int32) []proto.Message { msg := callback(filters.Filters{ - Limit: defaultLimit, - Page: batchPage, + Limit: defaultMsgPerBatch, + Page: batchIndex, SortBy: "created_at", Asc: false, }) - batchLen[batchPage] = len(msg) + batchLen[batchIndex] = len(msg) return msg } -func BubbleteaPaginator(_listHeader []printer.Column, _callback DataCallback) { +func Paginator(_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))...) + msg = append(msg, getMessageList(i)...) } showPagination(msg) @@ -153,21 +154,21 @@ func preFetchPage(m *pageModel) { if len(m.items)/defaultMsgPerPage == m.paginator.Page+1 { newMessages := getMessageList(lastBatchIndex + 1) if len(newMessages) != 0 { - lastBatchIndex += 1 + lastBatchIndex++ 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 + firstBatchIndex++ } } // Triggers when user is at the first page if m.paginator.Page == 0 && firstBatchIndex > 1 { newMessages := getMessageList(firstBatchIndex - 1) - firstBatchIndex -= 1 + firstBatchIndex-- 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 + lastBatchIndex-- } m.paginator.SetTotalPages(len(m.items)) } From b2dee428f4241c5d257689121d71b7d0706b6a5e Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Tue, 23 Apr 2024 15:10:21 -0700 Subject: [PATCH 4/7] reuse JSONToTable Signed-off-by: zychen5186 --- cmd/get/matchable_task_resource_attribute.go | 1 + cmd/register/files.go | 2 +- pkg/bubbletea/bubbletea_pagination_util.go | 65 ++------------------ pkg/printer/printer.go | 10 +-- pkg/printer/printer_test.go | 3 +- 5 files changed, 13 insertions(+), 68 deletions(-) diff --git a/cmd/get/matchable_task_resource_attribute.go b/cmd/get/matchable_task_resource_attribute.go index cfc5f4c6..c27a0b66 100644 --- a/cmd/get/matchable_task_resource_attribute.go +++ b/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/cmd/register/files.go b/cmd/register/files.go index f9b70696..bc204990 100644 --- a/cmd/register/files.go +++ b/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/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go index a904caa7..50520860 100644 --- a/pkg/bubbletea/bubbletea_pagination_util.go +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -8,11 +8,9 @@ import ( "github.com/flyteorg/flytectl/pkg/filters" "github.com/flyteorg/flytectl/pkg/printer" + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" - "github.com/kataras/tablewriter" - "github.com/landoop/tableprinter" - "github.com/yalp/jsonpath" ) type DataCallback func(filter filters.Filters) []proto.Message @@ -49,37 +47,6 @@ func (p PrintableProto) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } -func extractRow(data interface{}, columns []printer.Column) []string { - if columns == nil || data == nil { - return nil - } - - tableData := make([]string, 0, len(columns)) - for _, c := range columns { - out, err := jsonpath.Read(data, c.JSONPath) - if err != nil || out == nil { - out = "" - } - s := fmt.Sprintf("%s", out) - if c.TruncateTo != nil { - t := *c.TruncateTo - if len(s) > t { - s = s[:t] - } - } - tableData = append(tableData, s) - } - return tableData -} - -func projectColumns(rows []interface{}, column []printer.Column) [][]string { - responses := make([][]string, 0, len(rows)) - for _, row := range rows { - responses = append(responses, extractRow(row, column)) - } - return responses -} - func printTable(m *pageModel, start int, end int) (string, error) { curShowMessage := m.items[start:end] printableMessages := make([]*PrintableProto, 0, len(curShowMessage)) @@ -92,34 +59,10 @@ func printTable(m *pageModel, start int, end int) (string, error) { return "", fmt.Errorf("failed to marshal proto messages") } - var rawRows []interface{} - if err := json.Unmarshal(jsonRows, &rawRows); err != nil { - return "", fmt.Errorf("failed to unmarshal into []interface{} from json") - } - if rawRows == nil { - return "", fmt.Errorf("expected one row or empty rows, received nil") - } - rows := projectColumns(rawRows, listHeader) - var buf strings.Builder - printer := tableprinter.New(&buf) - printer.AutoWrapText = false - printer.BorderLeft = true - printer.BorderRight = true - printer.BorderBottom = true - printer.BorderTop = true - printer.RowLine = true - printer.ColumnSeparator = "|" - printer.HeaderBgColor = tablewriter.BgHiWhiteColor - 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) - } - - if r := printer.Render(headers, rows, positions, true); r == -1 { - return "", fmt.Errorf("failed to render table") + p := printer.Printer{} + if err := p.JSONToTable(&buf, jsonRows, listHeader); err != nil { + return "", err } return buf.String(), nil diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index e3b406b9..f98b76bf 100644 --- a/pkg/printer/printer.go +++ b/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,6 @@ 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)) return nil } @@ -155,7 +155,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 +285,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/pkg/printer/printer_test.go b/pkg/printer/printer_test.go index 06d4c2c3..eb4960a8 100644 --- a/pkg/printer/printer_test.go +++ b/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}, })) From e60c8803dc2f54d2ab863572287735888a74376b Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Tue, 23 Apr 2024 15:10:21 -0700 Subject: [PATCH 5/7] reuse JSONToTable Signed-off-by: zychen5186 change := to var Signed-off-by: zychen5186 --- pkg/bubbletea/bubbletea_pagination_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go index 50520860..adb5f3de 100644 --- a/pkg/bubbletea/bubbletea_pagination_util.go +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -84,7 +84,7 @@ func Paginator(_listHeader []printer.Column, _callback DataCallback) { listHeader = _listHeader callback = _callback - msg := []proto.Message{} + var msg []proto.Message for i := firstBatchIndex; i < lastBatchIndex+1; i++ { msg = append(msg, getMessageList(i)...) } From d633ea31609a4bfb10bba4c771b9f8347a9fb85b Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Tue, 23 Apr 2024 16:40:07 -0700 Subject: [PATCH 6/7] keep original format when not using bubbletea Signed-off-by: zychen5186 --- pkg/printer/printer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index f98b76bf..6dcf98d7 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -142,6 +142,9 @@ func (p Printer) JSONToTable(w io.Writer, jsonRows []byte, columns []Column) err if r := printer.Render(headers, rows, positions, true); r == -1 { return fmt.Errorf("failed to render table") } + if w == os.Stdout { + fmt.Printf("%d rows\n", len(rows)) + } return nil } From d9d646858a3be8da9c37a01418974129e5a3101a Mon Sep 17 00:00:00 2001 From: zychen5186 Date: Wed, 24 Apr 2024 19:56:05 -0700 Subject: [PATCH 7/7] improve readability and no functionality is changed Signed-off-by: zychen5186 --- pkg/bubbletea/bubbletea_pagination.go | 23 +++++--- pkg/bubbletea/bubbletea_pagination_util.go | 67 ++++++++++++++-------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/pkg/bubbletea/bubbletea_pagination.go b/pkg/bubbletea/bubbletea_pagination.go index 0eeeac9c..b79ff4da 100644 --- a/pkg/bubbletea/bubbletea_pagination.go +++ b/pkg/bubbletea/bubbletea_pagination.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/charmbracelet/bubbles/paginator" + "github.com/flyteorg/flytectl/pkg/printer" "github.com/golang/protobuf/proto" tea "github.com/charmbracelet/bubbletea" @@ -18,7 +19,7 @@ type pageModel struct { func newModel(initMsg []proto.Message) pageModel { p := paginator.New() - p.PerPage = defaultMsgPerPage + p.PerPage = msgPerPage p.SetTotalPages(len(initMsg)) return pageModel{ @@ -41,26 +42,32 @@ func (m pageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } m.paginator, cmd = m.paginator.Update(msg) - preFetchPage(&m) + preFetchBatch(&m) return m, cmd } func (m pageModel) View() string { var b strings.Builder - start, end := m.paginator.GetSliceBounds(len(m.items)) - table, err := printTable(&m, start, end) + table, err := getTable(&m) if err != nil { return "" } b.WriteString(table) - currentPage := int(firstBatchIndex-1)*pagePerBatch + m.paginator.Page + 1 - b.WriteString(fmt.Sprintf(" PAGE - %d\n", currentPage)) + 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 showPagination(initMsg []proto.Message) { - p := tea.NewProgram(newModel(initMsg)) +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/pkg/bubbletea/bubbletea_pagination_util.go b/pkg/bubbletea/bubbletea_pagination_util.go index adb5f3de..68d52d85 100644 --- a/pkg/bubbletea/bubbletea_pagination_util.go +++ b/pkg/bubbletea/bubbletea_pagination_util.go @@ -18,16 +18,18 @@ type DataCallback func(filter filters.Filters) []proto.Message type PrintableProto struct{ proto.Message } const ( - defaultMsgPerBatch = 100 - defaultMsgPerPage = 10 - pagePerBatch = defaultMsgPerBatch / defaultMsgPerPage + 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 @@ -47,7 +49,21 @@ func (p PrintableProto) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } -func printTable(m *pageModel, start int, end int) (string, error) { +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 { @@ -70,7 +86,7 @@ func printTable(m *pageModel, start int, end int) (string, error) { func getMessageList(batchIndex int32) []proto.Message { msg := callback(filters.Filters{ - Limit: defaultMsgPerBatch, + Limit: msgPerBatch, Page: batchIndex, SortBy: "created_at", Asc: false, @@ -80,38 +96,41 @@ func getMessageList(batchIndex int32) []proto.Message { return msg } -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)...) +func countTotalPages() int { + sum := 0 + for _, l := range batchLen { + sum += l } - - showPagination(msg) + return sum } -func preFetchPage(m *pageModel) { - // Triggers when user is at the last page - if len(m.items)/defaultMsgPerPage == m.paginator.Page+1 { +// 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 - m.paginator.Page -= batchLen[firstBatchIndex] / defaultMsgPerPage + localPageIndex -= batchLen[firstBatchIndex] / msgPerPage firstBatchIndex++ } + return } - // Triggers when user is at the first page - if m.paginator.Page == 0 && firstBatchIndex > 1 { + // 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(m.items, newMessages...) + m.items = append(newMessages, m.items...) m.items = m.items[:len(m.items)-batchLen[lastBatchIndex]] // delete the msgs in the "lastBatchIndex" batch - m.paginator.Page += batchLen[firstBatchIndex] / defaultMsgPerPage + localPageIndex += batchLen[firstBatchIndex] / msgPerPage lastBatchIndex-- + return } - m.paginator.SetTotalPages(len(m.items)) }