Prompt user for primary and secondary key for new items
This commit is contained in:
parent
a4216b47f5
commit
e5a7b82a63
19 changed files with 191 additions and 53 deletions
25
internal/dynamo-browse/controllers/commands.go
Normal file
25
internal/dynamo-browse/controllers/commands.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/lmika/awstools/internal/common/ui/events"
|
||||
)
|
||||
|
||||
type promptSequence struct {
|
||||
prompts []string
|
||||
receivedValues []string
|
||||
onAllDone func(values []string) tea.Msg
|
||||
}
|
||||
|
||||
func (ps *promptSequence) next() tea.Msg {
|
||||
if len(ps.receivedValues) < len(ps.prompts) {
|
||||
return events.PromptForInputMsg{
|
||||
Prompt: ps.prompts[len(ps.receivedValues)],
|
||||
OnDone: func(value string) tea.Cmd {
|
||||
ps.receivedValues = append(ps.receivedValues, value)
|
||||
return ps.next
|
||||
},
|
||||
}
|
||||
}
|
||||
return ps.onAllDone(ps.receivedValues)
|
||||
}
|
||||
|
|
@ -114,13 +114,14 @@ func tempFile(t *testing.T) string {
|
|||
return tempFile.Name()
|
||||
}
|
||||
|
||||
func invokeCommand(t *testing.T, cmd tea.Cmd) {
|
||||
func invokeCommand(t *testing.T, cmd tea.Cmd) tea.Msg {
|
||||
msg := cmd()
|
||||
|
||||
err, isErr := msg.(events.ErrorMsg)
|
||||
if isErr {
|
||||
assert.Fail(t, fmt.Sprintf("expected no error but got one: %v", err))
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func invokeCommandWithPrompt(t *testing.T, cmd tea.Cmd, promptValue string) {
|
||||
|
|
@ -134,6 +135,35 @@ func invokeCommandWithPrompt(t *testing.T, cmd tea.Cmd, promptValue string) {
|
|||
invokeCommand(t, pi.OnDone(promptValue))
|
||||
}
|
||||
|
||||
func invokeCommandWithPrompts(t *testing.T, cmd tea.Cmd, promptValues ...string) {
|
||||
msg := cmd()
|
||||
|
||||
for _, promptValue := range promptValues {
|
||||
pi, isPi := msg.(events.PromptForInputMsg)
|
||||
if !isPi {
|
||||
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
|
||||
}
|
||||
|
||||
msg = invokeCommand(t, pi.OnDone(promptValue))
|
||||
}
|
||||
}
|
||||
|
||||
func invokeCommandWithPromptsExpectingError(t *testing.T, cmd tea.Cmd, promptValues ...string) {
|
||||
msg := cmd()
|
||||
|
||||
for _, promptValue := range promptValues {
|
||||
pi, isPi := msg.(events.PromptForInputMsg)
|
||||
if !isPi {
|
||||
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
|
||||
}
|
||||
|
||||
msg = invokeCommand(t, pi.OnDone(promptValue))
|
||||
}
|
||||
|
||||
_, isErr := msg.(events.ErrorMsg)
|
||||
assert.True(t, isErr)
|
||||
}
|
||||
|
||||
func invokeCommandExpectingError(t *testing.T, cmd tea.Cmd) {
|
||||
msg := cmd()
|
||||
|
||||
|
|
|
|||
|
|
@ -37,13 +37,34 @@ func (twc *TableWriteController) ToggleMark(idx int) tea.Cmd {
|
|||
|
||||
func (twc *TableWriteController) NewItem() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
twc.state.withResultSet(func(set *models.ResultSet) {
|
||||
set.AddNewItem(models.Item{}, models.ItemAttribute{
|
||||
New: true,
|
||||
Dirty: true,
|
||||
// Work out which keys we need to prompt for
|
||||
rs := twc.state.ResultSet()
|
||||
|
||||
keyPrompts := &promptSequence{
|
||||
prompts: []string{rs.TableInfo.Keys.PartitionKey + ": "},
|
||||
}
|
||||
if rs.TableInfo.Keys.SortKey != "" {
|
||||
keyPrompts.prompts = append(keyPrompts.prompts, rs.TableInfo.Keys.SortKey+": ")
|
||||
}
|
||||
keyPrompts.onAllDone = func(values []string) tea.Msg {
|
||||
twc.state.withResultSet(func(set *models.ResultSet) {
|
||||
newItem := models.Item{}
|
||||
|
||||
// TODO: deal with keys of different type
|
||||
newItem[rs.TableInfo.Keys.PartitionKey] = &types.AttributeValueMemberS{Value: values[0]}
|
||||
if len(values) == 2 {
|
||||
newItem[rs.TableInfo.Keys.SortKey] = &types.AttributeValueMemberS{Value: values[1]}
|
||||
}
|
||||
|
||||
set.AddNewItem(newItem, models.ItemAttribute{
|
||||
New: true,
|
||||
Dirty: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
return NewResultSet{twc.state.ResultSet()}
|
||||
return NewResultSet{twc.state.ResultSet()}
|
||||
}
|
||||
|
||||
return keyPrompts.next()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -181,13 +181,13 @@ func setupController(t *testing.T) (*controllers.TableWriteController, controlle
|
|||
*/
|
||||
|
||||
func TestTableWriteController_NewItem(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
||||
t.Run("should add a new empty item at the end of the result set", func(t *testing.T) {
|
||||
state := controllers.NewState()
|
||||
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||
|
|
@ -195,10 +195,17 @@ func TestTableWriteController_NewItem(t *testing.T) {
|
|||
invokeCommand(t, readController.Init())
|
||||
assert.Len(t, state.ResultSet().Items(), 3)
|
||||
|
||||
invokeCommand(t, writeController.NewItem())
|
||||
// Prompt for keys
|
||||
invokeCommandWithPrompts(t, writeController.NewItem(), "pk-value", "sk-value")
|
||||
|
||||
newResultSet := state.ResultSet()
|
||||
assert.Len(t, newResultSet.Items(), 4)
|
||||
assert.Len(t, newResultSet.Items()[3], 0)
|
||||
assert.Len(t, newResultSet.Items()[3], 2)
|
||||
|
||||
pk, _ := newResultSet.Items()[3].AttributeValueAsString("pk")
|
||||
sk, _ := newResultSet.Items()[3].AttributeValueAsString("sk")
|
||||
assert.Equal(t, "pk-value", pk)
|
||||
assert.Equal(t, "sk-value", sk)
|
||||
assert.True(t, newResultSet.IsNew(3))
|
||||
assert.True(t, newResultSet.IsDirty(3))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -64,17 +64,27 @@ func NewProvider(client *dynamodb.Client) *Provider {
|
|||
return &Provider{client: client}
|
||||
}
|
||||
|
||||
func (p *Provider) ScanItems(ctx context.Context, tableName string) ([]models.Item, error) {
|
||||
res, err := p.client.Scan(ctx, &dynamodb.ScanInput{
|
||||
func (p *Provider) ScanItems(ctx context.Context, tableName string, maxItems int) ([]models.Item, error) {
|
||||
paginator := dynamodb.NewScanPaginator(p.client, &dynamodb.ScanInput{
|
||||
TableName: aws.String(tableName),
|
||||
Limit: aws.Int32(int32(maxItems)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot execute scan on table %v", tableName)
|
||||
}
|
||||
|
||||
items := make([]models.Item, len(res.Items))
|
||||
for i, itm := range res.Items {
|
||||
items[i] = itm
|
||||
items := make([]models.Item, 0)
|
||||
|
||||
outer:
|
||||
for paginator.HasMorePages() {
|
||||
res, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot execute scan on table %v", tableName)
|
||||
}
|
||||
|
||||
for _, itm := range res.Items {
|
||||
items = append(items, itm)
|
||||
if len(items) >= maxItems {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
type TableProvider interface {
|
||||
ListTables(ctx context.Context) ([]string, error)
|
||||
DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error)
|
||||
ScanItems(ctx context.Context, tableName string) ([]models.Item, error)
|
||||
ScanItems(ctx context.Context, tableName string, maxItems int) ([]models.Item, error)
|
||||
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error
|
||||
PutItem(ctx context.Context, name string, item models.Item) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo
|
|||
}
|
||||
|
||||
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
|
||||
results, err := s.provider.ScanItems(ctx, tableInfo.Name)
|
||||
results, err := s.provider.ScanItems(ctx, tableInfo.Name, 1000)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
|||
|
||||
// TEMP
|
||||
"new-item": commandctrl.NoArgCommand(wc.NewItem()),
|
||||
"set-s": func(args []string) tea.Cmd {
|
||||
if len(args) == 0 {
|
||||
return events.SetError(errors.New("expected field"))
|
||||
}
|
||||
return wc.SetStringValue(dtv.SelectedItemIndex(), args[0])
|
||||
},
|
||||
|
||||
"put": func(args []string) tea.Cmd {
|
||||
return wc.PutItem(dtv.SelectedItemIndex())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package dynamotableview
|
||||
|
||||
import (
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
|
@ -10,6 +9,7 @@ import (
|
|||
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamoitemview"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -42,8 +42,20 @@ type Model struct {
|
|||
resultSet *models.ResultSet
|
||||
}
|
||||
|
||||
type columnModel struct {
|
||||
m *Model
|
||||
}
|
||||
|
||||
func (cm columnModel) Len() int {
|
||||
return len(cm.m.resultSet.Columns[cm.m.colOffset:])
|
||||
}
|
||||
|
||||
func (cm columnModel) Header(index int) string {
|
||||
return cm.m.resultSet.Columns[cm.m.colOffset+index]
|
||||
}
|
||||
|
||||
func New() *Model {
|
||||
tbl := table.New([]string{"pk", "sk"}, 100, 100)
|
||||
tbl := table.New(table.SimpleColumns([]string{"pk", "sk"}), 100, 100)
|
||||
rows := make([]table.Row, 0)
|
||||
tbl.SetRows(rows)
|
||||
|
||||
|
|
@ -116,7 +128,9 @@ func (m *Model) setLeftmostDisplayedColumn(newCol int) {
|
|||
} else {
|
||||
m.colOffset = newCol
|
||||
}
|
||||
m.rebuildTable()
|
||||
// TEMP
|
||||
m.table.GoDown()
|
||||
m.table.GoUp()
|
||||
}
|
||||
|
||||
func (m *Model) View() string {
|
||||
|
|
@ -141,7 +155,7 @@ func (m *Model) updateTable() {
|
|||
func (m *Model) rebuildTable() {
|
||||
resultSet := m.resultSet
|
||||
|
||||
newTbl := table.New(resultSet.Columns[m.colOffset:], m.w, m.h-m.frameTitle.HeaderHeight())
|
||||
newTbl := table.New(columnModel{m}, m.w, m.h-m.frameTitle.HeaderHeight())
|
||||
newRows := make([]table.Row, 0)
|
||||
for i, r := range resultSet.Items() {
|
||||
if resultSet.Hidden(i) {
|
||||
|
|
@ -149,22 +163,24 @@ func (m *Model) rebuildTable() {
|
|||
}
|
||||
|
||||
newRows = append(newRows, itemTableRow{
|
||||
model: m,
|
||||
resultSet: resultSet,
|
||||
itemIndex: i,
|
||||
colOffset: m.colOffset,
|
||||
item: r,
|
||||
})
|
||||
}
|
||||
|
||||
m.rows = newRows
|
||||
newTbl.SetRows(newRows)
|
||||
for newTbl.Cursor() != m.table.Cursor() {
|
||||
if newTbl.Cursor() < m.table.Cursor() {
|
||||
newTbl.GoDown()
|
||||
} else if newTbl.Cursor() > m.table.Cursor() {
|
||||
newTbl.GoUp()
|
||||
/*
|
||||
for newTbl.Cursor() != m.table.Cursor() {
|
||||
if newTbl.Cursor() < m.table.Cursor() {
|
||||
newTbl.GoDown()
|
||||
} else if newTbl.Cursor() > m.table.Cursor() {
|
||||
newTbl.GoUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
m.table = newTbl
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import (
|
|||
"io"
|
||||
"strings"
|
||||
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
)
|
||||
|
||||
var (
|
||||
markedRowStyle = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#e1e1e1"))
|
||||
Background(lipgloss.AdaptiveColor{Dark: "#e1e1e1", Light: "#414141"})
|
||||
dirtyRowStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#e13131"))
|
||||
newRowStyle = lipgloss.NewStyle().
|
||||
|
|
@ -23,9 +23,9 @@ var (
|
|||
)
|
||||
|
||||
type itemTableRow struct {
|
||||
model *Model
|
||||
resultSet *models.ResultSet
|
||||
itemIndex int
|
||||
colOffset int
|
||||
item models.Item
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ func (mtr itemTableRow) Render(w io.Writer, model table.Model, index int) {
|
|||
metaInfoStyle := style.Copy().Inherit(metaInfoStyle)
|
||||
|
||||
sb := strings.Builder{}
|
||||
for i, colName := range mtr.resultSet.Columns[mtr.colOffset:] {
|
||||
for i, colName := range mtr.resultSet.Columns[mtr.model.colOffset:] {
|
||||
if i > 0 {
|
||||
sb.WriteString(style.Render("\t"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package loglines
|
||||
|
||||
import (
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package loglines
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
"github.com/lmika/awstools/internal/slog-view/models"
|
||||
"io"
|
||||
"strings"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"log"
|
||||
"strings"
|
||||
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"io"
|
||||
"strings"
|
||||
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
"github.com/lmika/awstools/internal/sqs-browse/models"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package ssmlist
|
||||
|
||||
import (
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package ssmlist
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
table "github.com/calyptia/go-bubble-table"
|
||||
table "github.com/lmika/go-bubble-table"
|
||||
"github.com/lmika/awstools/internal/ssm-browse/models"
|
||||
"io"
|
||||
"strings"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue