Have got the table view working again

This commit is contained in:
Leon Mika 2022-03-27 21:21:52 +00:00 committed by GitHub
parent 507226f571
commit 2638597f42
9 changed files with 238 additions and 43 deletions

View file

@ -4,7 +4,12 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
@ -16,15 +21,13 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/lmika/awstools/internal/dynamo-browse/ui" "github.com/lmika/awstools/internal/dynamo-browse/ui"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamotableview"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/tableselect" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/tableselect"
"github.com/lmika/gopkgs/cli" "github.com/lmika/gopkgs/cli"
"log"
"os"
"time"
) )
func main() { func main() {
@ -33,7 +36,13 @@ func main() {
flag.Parse() flag.Parse()
ctx := context.Background() ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
// TEMP
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("ap-southeast-2"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("abc", "123", "")))
// END TEMP
if err != nil { if err != nil {
cli.Fatalf("cannot load AWS config: %v", err) cli.Fatalf("cannot load AWS config: %v", err)
} }
@ -57,7 +66,7 @@ func main() {
tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable) tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable)
commandController := commandctrl.NewCommandController(map[string]uimodels.Operation{ commandController := commandctrl.NewCommandController(map[string]uimodels.Operation{
"scan": tableReadController.Scan(), // "scan": tableReadController.Scan(),
"rw": tableWriteController.ToggleReadWrite(), "rw": tableWriteController.ToggleReadWrite(),
"dup": tableWriteController.Duplicate(), "dup": tableWriteController.Duplicate(),
}) })
@ -70,7 +79,8 @@ func main() {
var model tea.Model = statusandprompt.New( var model tea.Model = statusandprompt.New(
layout.NewVBox( layout.NewVBox(
frame.NewFrame("This is the header", true, layout.Model(newTestModel("this is the top"))), layout.LastChildFixedAt(11),
frame.NewFrame("This is the header", true, dynamotableview.New(tableReadController)),
frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))), frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))),
), ),
"Hello world", "Hello world",

View file

@ -2,8 +2,9 @@ package controllers
import ( import (
"context" "context"
"log"
"github.com/lmika/awstools/internal/common/ui/uimodels" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -21,6 +22,30 @@ func NewTableReadController(tableService *tables.Service, tableName string) *Tab
} }
} }
func (c *TableReadController) Scan() tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
log.Println("Fetching table info")
tableInfo, err := c.tableInfo(ctx)
if err != nil {
log.Println("error: ", err)
return err
}
log.Println("Scanning")
resultSet, err := c.tableService.Scan(ctx, tableInfo)
if err != nil {
log.Println("error: ", err)
return err
}
log.Println("Scan done")
return NewResultSet{resultSet}
}
}
/*
func (c *TableReadController) Scan() uimodels.Operation { func (c *TableReadController) Scan() uimodels.Operation {
return uimodels.OperationFn(func(ctx context.Context) error { return uimodels.OperationFn(func(ctx context.Context) error {
return c.doScan(ctx, false) return c.doScan(ctx, false)
@ -50,13 +75,16 @@ func (c *TableReadController) doScan(ctx context.Context, quiet bool) (err error
uiCtx.Send(NewResultSet{resultSet}) uiCtx.Send(NewResultSet{resultSet})
return nil return nil
} }
*/
// tableInfo returns the table info from the state if a result set exists. If not, it fetches the // tableInfo returns the table info from the state if a result set exists. If not, it fetches the
// table information from the service. // table information from the service.
func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) { func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) {
/*
if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil { if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil {
return existingResultSet.TableInfo, nil return existingResultSet.TableInfo, nil
} }
*/
tableInfo, err := c.tableService.Describe(ctx, c.tableName) tableInfo, err := c.tableService.Describe(ctx, c.tableName)
if err != nil { if err != nil {

View file

@ -81,9 +81,9 @@ func (c *TableWriteController) Duplicate() uimodels.Operation {
} }
// Rescan to get updated items // Rescan to get updated items
if err := c.tableReadControllers.doScan(ctx, true); err != nil { // if err := c.tableReadControllers.doScan(ctx, true); err != nil {
return err // return err
} // }
return nil return nil
})) }))
@ -122,9 +122,9 @@ func (c *TableWriteController) Delete() uimodels.Operation {
} }
// Rescan to get updated items // Rescan to get updated items
if err := c.tableReadControllers.doScan(ctx, true); err != nil { // if err := c.tableReadControllers.doScan(ctx, true); err != nil {
return err // return err
} // }
uiCtx.Message("Item deleted") uiCtx.Message("Item deleted")
return nil return nil

View file

@ -2,11 +2,8 @@ package ui
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"text/tabwriter"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
table "github.com/calyptia/go-bubble-table" table "github.com/calyptia/go-bubble-table"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
@ -83,6 +80,7 @@ func (m uiModel) Init() tea.Cmd {
return nil return nil
} }
/*
func (m *uiModel) updateTable() { func (m *uiModel) updateTable() {
if !m.ready { if !m.ready {
return return
@ -99,6 +97,8 @@ func (m *uiModel) updateTable() {
m.table = newTbl m.table = newTbl
} }
func (m *uiModel) selectedItem() (itemTableRow, bool) { func (m *uiModel) selectedItem() (itemTableRow, bool) {
resultSet := m.state.ResultSet resultSet := m.state.ResultSet
if m.ready && resultSet != nil && len(resultSet.Items) > 0 { if m.ready && resultSet != nil && len(resultSet.Items) > 0 {
@ -136,6 +136,7 @@ func (m *uiModel) updateViewportToSelectedMessage() {
tabWriter.Flush() tabWriter.Flush()
m.viewport.SetContent(viewportContent.String()) m.viewport.SetContent(viewportContent.String())
} }
*/
func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var textInputCommands tea.Cmd var textInputCommands tea.Cmd
@ -145,8 +146,8 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Local events // Local events
case controllers.NewResultSet: case controllers.NewResultSet:
m.state.ResultSet = msg.ResultSet m.state.ResultSet = msg.ResultSet
m.updateTable() // m.updateTable()
m.updateViewportToSelectedMessage() // m.updateViewportToSelectedMessage()
case controllers.SetReadWrite: case controllers.SetReadWrite:
m.state.InReadWriteMode = msg.NewValue m.state.InReadWriteMode = msg.NewValue
@ -203,16 +204,16 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit return m, tea.Quit
case "up", "i": case "up", "i":
m.table.GoUp() m.table.GoUp()
m.updateViewportToSelectedMessage() // m.updateViewportToSelectedMessage()
case "down", "k": case "down", "k":
m.table.GoDown() m.table.GoDown()
m.updateViewportToSelectedMessage() // m.updateViewportToSelectedMessage()
// TODO: these should be moved somewhere else // TODO: these should be moved somewhere else
case ":": case ":":
m.invokeOperation(context.Background(), m.commandController.Prompt()) m.invokeOperation(context.Background(), m.commandController.Prompt())
case "s": // case "s":
m.invokeOperation(context.Background(), m.tableReadController.Scan()) // m.invokeOperation(context.Background(), m.tableReadController.Scan())
case "D": case "D":
m.invokeOperation(context.Background(), m.tableWriteController.Delete()) m.invokeOperation(context.Background(), m.tableWriteController.Delete())
} }
@ -233,9 +234,9 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m uiModel) invokeOperation(ctx context.Context, op uimodels.Operation) { func (m uiModel) invokeOperation(ctx context.Context, op uimodels.Operation) {
state := m.state state := m.state
if selectedItem, ok := m.selectedItem(); ok { // if selectedItem, ok := m.selectedItem(); ok {
state.SelectedItem = selectedItem.item // state.SelectedItem = selectedItem.item
} // }
ctx = controllers.ContextWithState(ctx, state) ctx = controllers.ContextWithState(ctx, state)
m.dispatcher.Start(ctx, op) m.dispatcher.Start(ctx, op)

View file

@ -0,0 +1,120 @@
package dynamotableview
import (
table "github.com/calyptia/go-bubble-table"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
"github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
)
type Model struct {
tableReadControllers *controllers.TableReadController
table table.Model
w, h int
// model state
resultSet *models.ResultSet
}
func New(tableReadControllers *controllers.TableReadController) Model {
tbl := table.New([]string{"pk", "sk"}, 100, 100)
rows := make([]table.Row, 0)
tbl.SetRows(rows)
return Model{tableReadControllers: tableReadControllers, table: tbl}
}
func (m Model) Init() tea.Cmd {
return nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case controllers.NewResultSet:
m.resultSet = msg.ResultSet
m.updateTable()
return m, nil
case tea.KeyMsg:
switch msg.String() {
// Table nav
case "i", "up":
m.table.GoUp()
return m, nil
case "k", "down":
m.table.GoDown()
return m, nil
// TEMP
case "s":
return m, m.tableReadControllers.Scan()
case "ctrl+c", "esc":
return m, tea.Quit
}
}
return m, nil
}
func (m Model) View() string {
return m.table.View()
}
func (m Model) Resize(w, h int) layout.ResizingModel {
m.w, m.h = w, h
m.table.SetSize(w, h)
return m
}
func (m *Model) updateTable() {
resultSet := m.resultSet
newTbl := table.New(resultSet.Columns, m.w, m.h)
newRows := make([]table.Row, len(resultSet.Items))
for i, r := range resultSet.Items {
newRows[i] = itemTableRow{resultSet, r}
}
newTbl.SetRows(newRows)
m.table = newTbl
}
func (m *Model) selectedItem() (itemTableRow, bool) {
resultSet := m.resultSet
if resultSet != nil && len(resultSet.Items) > 0 {
selectedItem, ok := m.table.SelectedRow().(itemTableRow)
if ok {
return selectedItem, true
}
}
return itemTableRow{}, false
}
/*
func (m *Model) updateViewportToSelectedMessage() {
selectedItem, ok := m.selectedItem()
if !ok {
m.viewport.SetContent("(no row selected)")
return
}
viewportContent := &strings.Builder{}
tabWriter := tabwriter.NewWriter(viewportContent, 0, 1, 1, ' ', 0)
for _, colName := range selectedItem.resultSet.Columns {
switch colVal := selectedItem.item[colName].(type) {
case nil:
break
case *types.AttributeValueMemberS:
fmt.Fprintf(tabWriter, "%v\tS\t%s\n", colName, colVal.Value)
case *types.AttributeValueMemberN:
fmt.Fprintf(tabWriter, "%v\tN\t%s\n", colName, colVal.Value)
default:
fmt.Fprintf(tabWriter, "%v\t?\t%s\n", colName, "(other)")
}
}
tabWriter.Flush()
m.viewport.SetContent(viewportContent.String())
}
*/

View file

@ -1,4 +1,4 @@
package ui package dynamotableview
import ( import (
"fmt" "fmt"

View file

@ -0,0 +1,36 @@
package layout
type BoxSize interface {
childSize(idx, cnt, available int) int
}
func EqualSize() BoxSize {
return equalSize{}
}
type equalSize struct {
}
func (l equalSize) childSize(idx, cnt, available int) int {
childrenHeight := available / cnt
lastChildRem := available % cnt
if idx == cnt-1 {
return childrenHeight + lastChildRem
}
return childrenHeight
}
func LastChildFixedAt(size int) BoxSize {
return lastChildFixedAt{size}
}
type lastChildFixedAt struct {
lastChildSize int
}
func (l lastChildFixedAt) childSize(idx, cnt, available int) int {
if idx == cnt-1 {
return l.lastChildSize
}
return (equalSize{}).childSize(idx, cnt-1, available-l.lastChildSize)
}

View file

@ -1,18 +1,20 @@
package layout package layout
import ( import (
"strings"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
"strings"
) )
// VBox is a model which will display its children vertically. // VBox is a model which will display its children vertically.
type VBox struct { type VBox struct {
boxSize BoxSize
children []ResizingModel children []ResizingModel
} }
func NewVBox(children ...ResizingModel) VBox { func NewVBox(boxSize BoxSize, children ...ResizingModel) VBox {
return VBox{children: children} return VBox{boxSize: boxSize, children: children}
} }
func (vb VBox) Init() tea.Cmd { func (vb VBox) Init() tea.Cmd {
@ -43,14 +45,9 @@ func (vb VBox) View() string {
} }
func (vb VBox) Resize(w, h int) ResizingModel { func (vb VBox) Resize(w, h int) ResizingModel {
childrenHeight := h / len(vb.children)
lastChildRem := h % len(vb.children)
for i, c := range vb.children { for i, c := range vb.children {
if i == len(vb.children)-1 { childHeight := vb.boxSize.childSize(i, len(vb.children), h)
vb.children[i] = c.Resize(w, childrenHeight+lastChildRem) vb.children[i] = c.Resize(w, childHeight)
} else {
vb.children[i] = c.Resize(w, childrenHeight)
}
} }
return vb return vb
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/brianvoe/gofakeit/v6" "github.com/brianvoe/gofakeit/v6"
@ -21,7 +22,9 @@ func main() {
tableName := "awstools-test" tableName := "awstools-test"
totalItems := 300 totalItems := 300
cfg, err := config.LoadDefaultConfig(ctx) cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("ap-southeast-2"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("abc", "123", "")))
if err != nil { if err != nil {
cli.Fatalf("cannot load AWS config: %v", err) cli.Fatalf("cannot load AWS config: %v", err)
} }
@ -32,7 +35,7 @@ func main() {
if _, err = dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{ if _, err = dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
TableName: aws.String(tableName), TableName: aws.String(tableName),
}); err != nil { }); err != nil {
log.Printf("warn: cannot delete table: %v", tableName) log.Printf("warn: cannot delete table: %v: %v", tableName, err)
} }
if _, err = dynamoClient.CreateTable(ctx, &dynamodb.CreateTableInput{ if _, err = dynamoClient.CreateTable(ctx, &dynamodb.CreateTableInput{