put-items: started adding some basic commands for putting items
This commit is contained in:
parent
3319a9d4aa
commit
174bab36c3
|
@ -43,8 +43,9 @@ func main() {
|
|||
|
||||
tableService := tables.NewService(dynamoProvider)
|
||||
|
||||
tableReadController := controllers.NewTableReadController(tableService, *flagTable)
|
||||
tableWriteController := controllers.NewTableWriteController(tableService, tableReadController)
|
||||
state := controllers.NewState()
|
||||
tableReadController := controllers.NewTableReadController(state, tableService, *flagTable)
|
||||
tableWriteController := controllers.NewTableWriteController(state, tableService, tableReadController)
|
||||
|
||||
commandController := commandctrl.NewCommandController()
|
||||
model := ui.NewModel(tableReadController, tableWriteController, commandController)
|
||||
|
|
|
@ -1,28 +1,46 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
)
|
||||
|
||||
type State struct {
|
||||
ResultSet *models.ResultSet
|
||||
SelectedItem models.Item
|
||||
|
||||
// InReadWriteMode indicates whether modifications can be made to the table
|
||||
InReadWriteMode bool
|
||||
mutex *sync.Mutex
|
||||
resultSet *models.ResultSet
|
||||
filter string
|
||||
}
|
||||
|
||||
type stateContextKeyType struct{}
|
||||
|
||||
var stateContextKey = stateContextKeyType{}
|
||||
|
||||
func CurrentState(ctx context.Context) State {
|
||||
state, _ := ctx.Value(stateContextKey).(State)
|
||||
return state
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
mutex: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func ContextWithState(ctx context.Context, state State) context.Context {
|
||||
return context.WithValue(ctx, stateContextKey, state)
|
||||
func (s *State) ResultSet() *models.ResultSet {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return s.resultSet
|
||||
}
|
||||
|
||||
func (s *State) Filter() string {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return s.filter
|
||||
}
|
||||
|
||||
func (s *State) withResultSet(rs func(*models.ResultSet)) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
rs(s.resultSet)
|
||||
}
|
||||
|
||||
func (s *State) setResultSetAndFilter(resultSet *models.ResultSet, filter string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.resultSet = resultSet
|
||||
s.filter = filter
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@ type TableReadController struct {
|
|||
tableName string
|
||||
|
||||
// state
|
||||
mutex *sync.Mutex
|
||||
resultSet *models.ResultSet
|
||||
filter string
|
||||
mutex *sync.Mutex
|
||||
state *State
|
||||
//resultSet *models.ResultSet
|
||||
//filter string
|
||||
}
|
||||
|
||||
func NewTableReadController(tableService TableReadService, tableName string) *TableReadController {
|
||||
func NewTableReadController(state *State, tableService TableReadService, tableName string) *TableReadController {
|
||||
return &TableReadController{
|
||||
state: state,
|
||||
tableService: tableService,
|
||||
tableName: tableName,
|
||||
mutex: new(sync.Mutex),
|
||||
|
@ -68,19 +70,19 @@ func (c *TableReadController) ScanTable(name string) tea.Cmd {
|
|||
return events.Error(err)
|
||||
}
|
||||
|
||||
return c.setResultSetAndFilter(resultSet, c.filter)
|
||||
return c.setResultSetAndFilter(resultSet, c.state.Filter())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TableReadController) Rescan() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return c.doScan(context.Background(), c.resultSet)
|
||||
return c.doScan(context.Background(), c.state.ResultSet())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TableReadController) ExportCSV(filename string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resultSet := c.resultSet
|
||||
resultSet := c.state.ResultSet()
|
||||
if resultSet == nil {
|
||||
return events.Error(errors.New("no result set"))
|
||||
}
|
||||
|
@ -119,39 +121,30 @@ func (c *TableReadController) doScan(ctx context.Context, resultSet *models.Resu
|
|||
return events.Error(err)
|
||||
}
|
||||
|
||||
newResultSet = c.tableService.Filter(newResultSet, c.filter)
|
||||
newResultSet = c.tableService.Filter(newResultSet, c.state.Filter())
|
||||
|
||||
return c.setResultSetAndFilter(newResultSet, c.filter)
|
||||
return c.setResultSetAndFilter(newResultSet, c.state.Filter())
|
||||
}
|
||||
|
||||
func (c *TableReadController) ResultSet() *models.ResultSet {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.resultSet
|
||||
}
|
||||
//func (c *TableReadController) ResultSet() *models.ResultSet {
|
||||
// c.mutex.Lock()
|
||||
// defer c.mutex.Unlock()
|
||||
//
|
||||
// return c.resultSet
|
||||
//}
|
||||
|
||||
func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string) tea.Msg {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.resultSet = resultSet
|
||||
c.filter = filter
|
||||
c.state.setResultSetAndFilter(resultSet, filter)
|
||||
return NewResultSet{resultSet}
|
||||
}
|
||||
|
||||
func (c *TableReadController) Unmark() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resultSet := c.ResultSet()
|
||||
|
||||
for i := range resultSet.Items() {
|
||||
resultSet.SetMark(i, false)
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.resultSet = resultSet
|
||||
c.state.withResultSet(func(resultSet *models.ResultSet) {
|
||||
for i := range resultSet.Items() {
|
||||
resultSet.SetMark(i, false)
|
||||
}
|
||||
})
|
||||
return ResultSetUpdated{}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +155,7 @@ func (c *TableReadController) Filter() tea.Cmd {
|
|||
Prompt: "filter: ",
|
||||
OnDone: func(value string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resultSet := c.ResultSet()
|
||||
resultSet := c.state.ResultSet()
|
||||
newResultSet := c.tableService.Filter(resultSet, value)
|
||||
|
||||
return c.setResultSetAndFilter(newResultSet, value)
|
||||
|
|
|
@ -22,7 +22,7 @@ func TestTableReadController_InitTable(t *testing.T) {
|
|||
service := tables.NewService(provider)
|
||||
|
||||
t.Run("should prompt for table if no table name provided", func(t *testing.T) {
|
||||
readController := controllers.NewTableReadController(service, "")
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
|
||||
|
||||
cmd := readController.Init()
|
||||
event := cmd()
|
||||
|
@ -31,7 +31,7 @@ func TestTableReadController_InitTable(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should scan table if table name provided", func(t *testing.T) {
|
||||
readController := controllers.NewTableReadController(service, "")
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
|
||||
|
||||
cmd := readController.Init()
|
||||
event := cmd()
|
||||
|
@ -46,7 +46,7 @@ func TestTableReadController_ListTables(t *testing.T) {
|
|||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
readController := controllers.NewTableReadController(service, "")
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
|
||||
|
||||
t.Run("returns a list of tables", func(t *testing.T) {
|
||||
cmd := readController.ListTables()
|
||||
|
@ -70,7 +70,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
|||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
readController := controllers.NewTableReadController(service, "alpha-table")
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "alpha-table")
|
||||
|
||||
t.Run("should export result set to CSV file", func(t *testing.T) {
|
||||
tempFile := tempFile(t)
|
||||
|
@ -91,7 +91,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
|||
|
||||
t.Run("should return error if result set is not set", func(t *testing.T) {
|
||||
tempFile := tempFile(t)
|
||||
readController := controllers.NewTableReadController(service, "non-existant-table")
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "non-existant-table")
|
||||
|
||||
invokeCommandExpectingError(t, readController.Init())
|
||||
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
|
||||
|
@ -123,6 +123,17 @@ func invokeCommand(t *testing.T, cmd tea.Cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
func invokeCommandWithPrompt(t *testing.T, cmd tea.Cmd, promptValue string) {
|
||||
msg := cmd()
|
||||
|
||||
pi, isPi := msg.(events.PromptForInputMsg)
|
||||
if !isPi {
|
||||
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
|
||||
}
|
||||
|
||||
invokeCommand(t, pi.OnDone(promptValue))
|
||||
}
|
||||
|
||||
func invokeCommandExpectingError(t *testing.T, cmd tea.Cmd) {
|
||||
msg := cmd()
|
||||
|
||||
|
|
|
@ -3,18 +3,22 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/lmika/awstools/internal/common/ui/events"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
|
||||
)
|
||||
|
||||
type TableWriteController struct {
|
||||
state *State
|
||||
tableService *tables.Service
|
||||
tableReadControllers *TableReadController
|
||||
}
|
||||
|
||||
func NewTableWriteController(tableService *tables.Service, tableReadControllers *TableReadController) *TableWriteController {
|
||||
func NewTableWriteController(state *State, tableService *tables.Service, tableReadControllers *TableReadController) *TableWriteController {
|
||||
return &TableWriteController{
|
||||
state: state,
|
||||
tableService: tableService,
|
||||
tableReadControllers: tableReadControllers,
|
||||
}
|
||||
|
@ -22,16 +26,46 @@ func NewTableWriteController(tableService *tables.Service, tableReadControllers
|
|||
|
||||
func (twc *TableWriteController) ToggleMark(idx int) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resultSet := twc.tableReadControllers.ResultSet()
|
||||
resultSet.SetMark(idx, !resultSet.Marked(idx))
|
||||
twc.state.withResultSet(func(resultSet *models.ResultSet) {
|
||||
resultSet.SetMark(idx, !resultSet.Marked(idx))
|
||||
})
|
||||
|
||||
return ResultSetUpdated{}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
})
|
||||
return NewResultSet{twc.state.ResultSet()}
|
||||
}
|
||||
}
|
||||
|
||||
func (twc *TableWriteController) SetItemValue(idx int, key string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return events.PromptForInputMsg{
|
||||
Prompt: "string value: ",
|
||||
OnDone: func(value string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
twc.state.withResultSet(func(set *models.ResultSet) {
|
||||
set.Items()[idx][key] = &types.AttributeValueMemberS{Value: value}
|
||||
set.SetDirty(idx, true)
|
||||
})
|
||||
return ResultSetUpdated{}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (twc *TableWriteController) DeleteMarked() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resultSet := twc.tableReadControllers.ResultSet()
|
||||
resultSet := twc.state.ResultSet()
|
||||
markedItems := resultSet.MarkedItems()
|
||||
|
||||
if len(markedItems) == 0 {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package controllers_test
|
||||
|
||||
import (
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
|
||||
"github.com/lmika/awstools/test/testdynamo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -173,24 +178,53 @@ func setupController(t *testing.T) (*controllers.TableWriteController, controlle
|
|||
tableService: tableService,
|
||||
}, cleanupFn
|
||||
}
|
||||
|
||||
var testData = testdynamo.TestData{
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "222",
|
||||
"alpha": "This is another some value",
|
||||
"beta": 1231,
|
||||
},
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "111",
|
||||
"alpha": "This is some value",
|
||||
},
|
||||
{
|
||||
"pk": "bbb",
|
||||
"sk": "131",
|
||||
"beta": 2468,
|
||||
"gamma": "foobar",
|
||||
},
|
||||
}
|
||||
*/
|
||||
|
||||
func TestTableWriteController_NewItem(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
|
||||
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)
|
||||
|
||||
invokeCommand(t, readController.Init())
|
||||
assert.Len(t, state.ResultSet().Items(), 3)
|
||||
|
||||
invokeCommand(t, writeController.NewItem())
|
||||
newResultSet := state.ResultSet()
|
||||
assert.Len(t, newResultSet.Items(), 4)
|
||||
assert.Len(t, newResultSet.Items()[3], 0)
|
||||
assert.True(t, newResultSet.IsNew(3))
|
||||
assert.True(t, newResultSet.IsDirty(3))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTableWriteController_SetItemValue(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
|
||||
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)
|
||||
|
||||
invokeCommand(t, readController.Init())
|
||||
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
|
||||
assert.Equal(t, "This is some value", before)
|
||||
assert.False(t, state.ResultSet().IsDirty(0))
|
||||
|
||||
invokeCommandWithPrompt(t, writeController.SetItemValue(0, "alpha"), "a new value")
|
||||
|
||||
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
|
||||
assert.Equal(t, "a new value", after)
|
||||
assert.True(t, state.ResultSet().IsDirty(0))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ type ResultSet struct {
|
|||
type ItemAttribute struct {
|
||||
Marked bool
|
||||
Hidden bool
|
||||
Dirty bool
|
||||
New bool
|
||||
}
|
||||
|
||||
func (rs *ResultSet) Items() []Item {
|
||||
|
@ -21,6 +23,11 @@ func (rs *ResultSet) SetItems(items []Item) {
|
|||
rs.attributes = make([]ItemAttribute, len(items))
|
||||
}
|
||||
|
||||
func (rs *ResultSet) AddNewItem(item Item, attrs ItemAttribute) {
|
||||
rs.items = append(rs.items, item)
|
||||
rs.attributes = append(rs.attributes, attrs)
|
||||
}
|
||||
|
||||
func (rs *ResultSet) SetMark(idx int, marked bool) {
|
||||
rs.attributes[idx].Marked = marked
|
||||
}
|
||||
|
@ -29,6 +36,14 @@ func (rs *ResultSet) SetHidden(idx int, hidden bool) {
|
|||
rs.attributes[idx].Hidden = hidden
|
||||
}
|
||||
|
||||
func (rs *ResultSet) SetDirty(idx int, dirty bool) {
|
||||
rs.attributes[idx].Dirty = dirty
|
||||
}
|
||||
|
||||
func (rs *ResultSet) SetNew(idx int, isNew bool) {
|
||||
rs.attributes[idx].New = isNew
|
||||
}
|
||||
|
||||
func (rs *ResultSet) Marked(idx int) bool {
|
||||
return rs.attributes[idx].Marked
|
||||
}
|
||||
|
@ -37,6 +52,14 @@ func (rs *ResultSet) Hidden(idx int) bool {
|
|||
return rs.attributes[idx].Hidden
|
||||
}
|
||||
|
||||
func (rs *ResultSet) IsDirty(idx int) bool {
|
||||
return rs.attributes[idx].Dirty
|
||||
}
|
||||
|
||||
func (rs *ResultSet) IsNew(idx int) bool {
|
||||
return rs.attributes[idx].New
|
||||
}
|
||||
|
||||
func (rs *ResultSet) MarkedItems() []Item {
|
||||
items := make([]Item, 0)
|
||||
for i, itemAttr := range rs.attributes {
|
||||
|
|
|
@ -48,6 +48,15 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
|||
},
|
||||
"unmark": commandctrl.NoArgCommand(rc.Unmark()),
|
||||
"delete": commandctrl.NoArgCommand(wc.DeleteMarked()),
|
||||
|
||||
// TEMP
|
||||
"new-item": commandctrl.NoArgCommand(wc.NewItem()),
|
||||
"set": func(args []string) tea.Cmd {
|
||||
if len(args) != 1 {
|
||||
return events.SetError(errors.New("expected attribute key"))
|
||||
}
|
||||
return wc.SetItemValue(dtv.SelectedItemIndex(), args[0])
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -135,5 +135,6 @@ func (m *Model) postSelectedItemChanged() tea.Msg {
|
|||
}
|
||||
|
||||
func (m *Model) Refresh() {
|
||||
|
||||
m.table.SetRows(m.rows)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
func main() {
|
||||
ctx := context.Background()
|
||||
tableName := "awstools-test"
|
||||
totalItems := 300
|
||||
totalItems := 10
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
|
@ -27,7 +27,7 @@ func main() {
|
|||
}
|
||||
|
||||
dynamoClient := dynamodb.NewFromConfig(cfg,
|
||||
dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL("http://localhost:8000")))
|
||||
dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL("http://localhost:18000")))
|
||||
|
||||
if _, err = dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
|
|
Loading…
Reference in a new issue