Modified put to put all dirty or marked items
This commit is contained in:
parent
9fee17a6a6
commit
2dbd664dd2
9
internal/common/sliceutils/map.go
Normal file
9
internal/common/sliceutils/map.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package sliceutils
|
||||||
|
|
||||||
|
func Map[T, U any](ts []T, fn func(t T) U) []U {
|
||||||
|
us := make([]U, len(ts))
|
||||||
|
for i, t := range ts {
|
||||||
|
us[i] = fn(t)
|
||||||
|
}
|
||||||
|
return us
|
||||||
|
}
|
|
@ -23,22 +23,3 @@ func (ps *promptSequence) next() tea.Msg {
|
||||||
}
|
}
|
||||||
return ps.onAllDone(ps.receivedValues)
|
return ps.onAllDone(ps.receivedValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
//type SetAttributeArg struct {
|
|
||||||
// attrType models.ItemType
|
|
||||||
// attrName string
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func ParseSetAttributeArgs(args []string) (attrArgs []SetAttributeArg, err error) {
|
|
||||||
// var currArg SetAttributeArg
|
|
||||||
// for _, arg := range args {
|
|
||||||
// if arg[0] == '-' {
|
|
||||||
// currArg.attrType = models.ItemType(arg[1:])
|
|
||||||
// } else {
|
|
||||||
// currArg.attrName = arg
|
|
||||||
// attrArgs = append(attrArgs, currArg)
|
|
||||||
// currArg = SetAttributeArg{}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return attrArgs, nil
|
|
||||||
//}
|
|
||||||
|
|
|
@ -49,4 +49,10 @@ type PromptForTableMsg struct {
|
||||||
OnSelected func(tableName string) tea.Cmd
|
OnSelected func(tableName string) tea.Cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResultSetUpdated struct{}
|
type ResultSetUpdated struct {
|
||||||
|
statusMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs ResultSetUpdated) StatusMessage() string {
|
||||||
|
return rs.statusMessage
|
||||||
|
}
|
||||||
|
|
|
@ -15,8 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTableReadController_InitTable(t *testing.T) {
|
func TestTableReadController_InitTable(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -41,8 +40,7 @@ func TestTableReadController_InitTable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableReadController_ListTables(t *testing.T) {
|
func TestTableReadController_ListTables(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -65,8 +63,7 @@ func TestTableReadController_ListTables(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableReadController_ExportCSV(t *testing.T) {
|
func TestTableReadController_ExportCSV(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -101,8 +98,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableReadController_Query(t *testing.T) {
|
func TestTableReadController_Query(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
|
|
@ -257,6 +257,68 @@ func (twc *TableWriteController) PutItem(idx int) tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (twc *TableWriteController) PutItems() tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
var (
|
||||||
|
expectedPuts int
|
||||||
|
markedItems int
|
||||||
|
)
|
||||||
|
|
||||||
|
twc.state.withResultSet(func(rs *models.ResultSet) {
|
||||||
|
markedItems = len(rs.MarkedItems())
|
||||||
|
for i := range rs.Items() {
|
||||||
|
if rs.IsDirty(i) && (markedItems == 0 || rs.Marked(i)) {
|
||||||
|
expectedPuts++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if expectedPuts == 0 {
|
||||||
|
if markedItems > 0 {
|
||||||
|
return events.StatusMsg("no marked items are modified")
|
||||||
|
} else {
|
||||||
|
return events.StatusMsg("no items are modified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var promptMessage string
|
||||||
|
if markedItems > 0 {
|
||||||
|
promptMessage = applyToN("put ", expectedPuts, "marked item", "marked items", "? ")
|
||||||
|
} else {
|
||||||
|
promptMessage = applyToN("put ", expectedPuts, "item", "items", "? ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.PromptForInputMsg{
|
||||||
|
Prompt: promptMessage,
|
||||||
|
OnDone: func(value string) tea.Cmd {
|
||||||
|
if value != "y" {
|
||||||
|
return events.SetStatus("operation aborted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() tea.Msg {
|
||||||
|
if err := twc.state.withResultSetReturningError(func(rs *models.ResultSet) error {
|
||||||
|
updated, err := twc.tableService.PutSelectedItems(context.Background(), rs, func(idx int) bool {
|
||||||
|
return rs.IsDirty(idx) && (markedItems == 0 || rs.Marked(idx))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updated != expectedPuts {
|
||||||
|
return errors.Errorf("expected %d updates but only %d were applied", expectedPuts, updated)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return events.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSetUpdated{
|
||||||
|
statusMessage: applyToN("", expectedPuts, "item", "item", " put to table"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (twc *TableWriteController) TouchItem(idx int) tea.Cmd {
|
func (twc *TableWriteController) TouchItem(idx int) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
resultSet := twc.state.ResultSet()
|
resultSet := twc.state.ResultSet()
|
||||||
|
@ -325,7 +387,7 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
return events.PromptForInputMsg{
|
return events.PromptForInputMsg{
|
||||||
Prompt: fmt.Sprintf("delete %d items? ", len(markedItems)),
|
Prompt: applyToN("delete ", len(markedItems), "item", "items", "? "),
|
||||||
OnDone: func(value string) tea.Cmd {
|
OnDone: func(value string) tea.Cmd {
|
||||||
if value != "y" {
|
if value != "y" {
|
||||||
return events.SetStatus("operation aborted")
|
return events.SetStatus("operation aborted")
|
||||||
|
@ -343,3 +405,10 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyToN(prefix string, n int, singular, plural, suffix string) string {
|
||||||
|
if n == 1 {
|
||||||
|
return fmt.Sprintf("%v%v %v%v", prefix, n, singular, suffix)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v%v %v%v", prefix, n, plural, suffix)
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,7 @@ import (
|
||||||
|
|
||||||
func TestTableWriteController_NewItem(t *testing.T) {
|
func TestTableWriteController_NewItem(t *testing.T) {
|
||||||
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
|
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)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -45,8 +44,7 @@ func TestTableWriteController_NewItem(t *testing.T) {
|
||||||
|
|
||||||
func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
||||||
t.Run("should preserve the type of the field if unspecified", func(t *testing.T) {
|
t.Run("should preserve the type of the field if unspecified", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
t.Cleanup(cleanupFn)
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -99,8 +97,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should change the value to a particular field if already present", func(t *testing.T) {
|
t.Run("should change the value to a particular field if already present", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
t.Cleanup(cleanupFn)
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -188,105 +185,8 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func TestTableWriteController_SetStringValue(t *testing.T) {
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
|
||||||
service := tables.NewService(provider)
|
|
||||||
|
|
||||||
t.Run("should change the value of a string field if already present", 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.SetStringValue(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))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should change the value of a string field within a map if already present", func(t *testing.T) {
|
|
||||||
state := controllers.NewState()
|
|
||||||
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
|
||||||
writeController := controllers.NewTableWriteController(state, service, readController)
|
|
||||||
|
|
||||||
invokeCommand(t, readController.Init())
|
|
||||||
|
|
||||||
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
|
||||||
beforeStreet := beforeAddress.Value["street"].(*types.AttributeValueMemberS).Value
|
|
||||||
|
|
||||||
assert.Equal(t, "Fake st.", beforeStreet)
|
|
||||||
assert.False(t, state.ResultSet().IsDirty(0))
|
|
||||||
|
|
||||||
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "address.street"), "Fiction rd.")
|
|
||||||
|
|
||||||
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
|
||||||
afterStreet := afterAddress.Value["street"].(*types.AttributeValueMemberS).Value
|
|
||||||
|
|
||||||
assert.Equal(t, "Fiction rd.", afterStreet)
|
|
||||||
assert.True(t, state.ResultSet().IsDirty(0))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTableWriteController_SetNumberValue(t *testing.T) {
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
|
||||||
service := tables.NewService(provider)
|
|
||||||
|
|
||||||
t.Run("should change the value of a number field if already present", 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("age")
|
|
||||||
assert.Equal(t, "23", before)
|
|
||||||
assert.False(t, state.ResultSet().IsDirty(0))
|
|
||||||
|
|
||||||
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "age"), "46")
|
|
||||||
|
|
||||||
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
|
|
||||||
assert.Equal(t, "46", after)
|
|
||||||
assert.True(t, state.ResultSet().IsDirty(0))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should change the value of a number field within a map if already present", func(t *testing.T) {
|
|
||||||
state := controllers.NewState()
|
|
||||||
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
|
||||||
writeController := controllers.NewTableWriteController(state, service, readController)
|
|
||||||
|
|
||||||
invokeCommand(t, readController.Init())
|
|
||||||
|
|
||||||
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
|
||||||
beforeStreet := beforeAddress.Value["no"].(*types.AttributeValueMemberN).Value
|
|
||||||
|
|
||||||
assert.Equal(t, "123", beforeStreet)
|
|
||||||
assert.False(t, state.ResultSet().IsDirty(0))
|
|
||||||
|
|
||||||
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "address.no"), "456")
|
|
||||||
|
|
||||||
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
|
||||||
afterStreet := afterAddress.Value["no"].(*types.AttributeValueMemberN).Value
|
|
||||||
|
|
||||||
assert.Equal(t, "456", afterStreet)
|
|
||||||
assert.True(t, state.ResultSet().IsDirty(0))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -331,8 +231,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
||||||
|
|
||||||
func TestTableWriteController_PutItem(t *testing.T) {
|
func TestTableWriteController_PutItem(t *testing.T) {
|
||||||
t.Run("should put the selected item if dirty", func(t *testing.T) {
|
t.Run("should put the selected item if dirty", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -359,8 +258,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
|
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -391,8 +289,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
|
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -411,10 +308,111 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTableWriteController_PutItems(t *testing.T) {
|
||||||
|
t.Run("should put all dirty items if none are marked", func(t *testing.T) {
|
||||||
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
|
|
||||||
|
provider := dynamo.NewProvider(client)
|
||||||
|
service := tables.NewService(provider)
|
||||||
|
|
||||||
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
|
||||||
|
// Modify the item and put it
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
|
||||||
|
|
||||||
|
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
|
||||||
|
|
||||||
|
// Rescan the table
|
||||||
|
invokeCommand(t, readController.Rescan())
|
||||||
|
|
||||||
|
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("only put marked items", func(t *testing.T) {
|
||||||
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
|
|
||||||
|
provider := dynamo.NewProvider(client)
|
||||||
|
service := tables.NewService(provider)
|
||||||
|
|
||||||
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
|
||||||
|
// Modify the item and put it
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
|
||||||
|
invokeCommand(t, writeController.ToggleMark(0))
|
||||||
|
|
||||||
|
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
|
||||||
|
|
||||||
|
// Verify dirty items are unchanged
|
||||||
|
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(2))
|
||||||
|
|
||||||
|
// Rescan the table and verify dirty items were not written
|
||||||
|
invokeCommand(t, readController.Rescan())
|
||||||
|
|
||||||
|
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
|
||||||
|
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("do not put marked items which are not diry", func(t *testing.T) {
|
||||||
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
|
|
||||||
|
provider := dynamo.NewProvider(client)
|
||||||
|
service := tables.NewService(provider)
|
||||||
|
|
||||||
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
|
||||||
|
// Modify the item and put it
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
|
||||||
|
invokeCommand(t, writeController.ToggleMark(1))
|
||||||
|
|
||||||
|
invokeCommand(t, writeController.PutItems())
|
||||||
|
|
||||||
|
// Verify dirty items are unchanged
|
||||||
|
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(0))
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(2))
|
||||||
|
|
||||||
|
// Rescan the table and verify dirty items were not written
|
||||||
|
invokeCommand(t, readController.Rescan())
|
||||||
|
|
||||||
|
assert.Equal(t, "This is some value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
|
||||||
|
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
|
||||||
|
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(2))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestTableWriteController_TouchItem(t *testing.T) {
|
func TestTableWriteController_TouchItem(t *testing.T) {
|
||||||
t.Run("should put the selected item if unmodified", func(t *testing.T) {
|
t.Run("should put the selected item if unmodified", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -440,8 +438,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -464,8 +461,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
||||||
|
|
||||||
func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
||||||
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
|
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
@ -491,8 +487,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
||||||
"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/lmika/awstools/internal/common/sliceutils"
|
||||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +15,10 @@ type Provider struct {
|
||||||
client *dynamodb.Client
|
client *dynamodb.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewProvider(client *dynamodb.Client) *Provider {
|
||||||
|
return &Provider{client: client}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) ListTables(ctx context.Context) ([]string, error) {
|
func (p *Provider) ListTables(ctx context.Context) ([]string, error) {
|
||||||
out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{})
|
out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -60,8 +65,33 @@ func (p *Provider) PutItem(ctx context.Context, name string, item models.Item) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProvider(client *dynamodb.Client) *Provider {
|
func (p *Provider) PutItems(ctx context.Context, name string, items []models.Item) error {
|
||||||
return &Provider{client: client}
|
return p.batchPutItems(ctx, name, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) batchPutItems(ctx context.Context, name string, items []models.Item) error {
|
||||||
|
reqs := len(items)/25 + 1
|
||||||
|
for rn := 0; rn < reqs; rn++ {
|
||||||
|
s, f := rn*25, (rn+1)*25
|
||||||
|
if f > len(items) {
|
||||||
|
f = len(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsInThisRequest := items[s:f]
|
||||||
|
writeRequests := sliceutils.Map(itemsInThisRequest, func(item models.Item) types.WriteRequest {
|
||||||
|
return types.WriteRequest{PutRequest: &types.PutRequest{Item: item}}
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := p.client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{
|
||||||
|
RequestItems: map[string][]types.WriteRequest{
|
||||||
|
name: writeRequests,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errors.Wrapf(err, "unable to put page %v of back puts", rn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) {
|
func (p *Provider) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) {
|
||||||
|
|
|
@ -2,6 +2,8 @@ package dynamo_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
|
@ -13,8 +15,7 @@ import (
|
||||||
func TestProvider_ScanItems(t *testing.T) {
|
func TestProvider_ScanItems(t *testing.T) {
|
||||||
tableName := "test-table"
|
tableName := "test-table"
|
||||||
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
t.Run("should return scanned items from the table", func(t *testing.T) {
|
t.Run("should return scanned items from the table", func(t *testing.T) {
|
||||||
|
@ -38,12 +39,62 @@ func TestProvider_ScanItems(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvider_PutItems(t *testing.T) {
|
||||||
|
tableName := "test-table"
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
maxItems int
|
||||||
|
}{
|
||||||
|
{maxItems: 3},
|
||||||
|
{maxItems: 13},
|
||||||
|
{maxItems: 25},
|
||||||
|
{maxItems: 48},
|
||||||
|
{maxItems: 73},
|
||||||
|
{maxItems: 103},
|
||||||
|
{maxItems: 291},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(fmt.Sprintf("should put items in batches: size %v", scenario.maxItems), func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
client := testdynamo.SetupTestTable(t, []testdynamo.TestData{
|
||||||
|
{
|
||||||
|
TableName: tableName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
|
items := make([]models.Item, scenario.maxItems)
|
||||||
|
for i := 0; i < scenario.maxItems; i++ {
|
||||||
|
items[i] = models.Item{
|
||||||
|
"pk": &types.AttributeValueMemberS{Value: fmt.Sprintf("K#%v", i)},
|
||||||
|
"sk": &types.AttributeValueMemberS{Value: fmt.Sprintf("K#%v", i)},
|
||||||
|
"a": &types.AttributeValueMemberN{Value: fmt.Sprintf("%v", i)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the data
|
||||||
|
err := provider.PutItems(ctx, tableName, items)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify the data
|
||||||
|
readItems, err := provider.ScanItems(ctx, tableName, nil, scenario.maxItems+5)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, readItems, scenario.maxItems)
|
||||||
|
|
||||||
|
for i := 0; i < scenario.maxItems; i++ {
|
||||||
|
assert.Contains(t, readItems, items[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProvider_DeleteItem(t *testing.T) {
|
func TestProvider_DeleteItem(t *testing.T) {
|
||||||
tableName := "test-table"
|
tableName := "test-table"
|
||||||
|
|
||||||
t.Run("should delete item if exists in table", func(t *testing.T) {
|
t.Run("should delete item if exists in table", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -64,8 +115,7 @@ func TestProvider_DeleteItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should do nothing if key does not exist", func(t *testing.T) {
|
t.Run("should do nothing if key does not exist", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -85,8 +135,7 @@ func TestProvider_DeleteItem(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should return error if table name does not exist", func(t *testing.T) {
|
t.Run("should return error if table name does not exist", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
@ -14,4 +14,5 @@ type TableProvider interface {
|
||||||
ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error)
|
ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error)
|
||||||
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error
|
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error
|
||||||
PutItem(ctx context.Context, name string, item models.Item) error
|
PutItem(ctx context.Context, name string, item models.Item) error
|
||||||
|
PutItems(ctx context.Context, name string, items []models.Item) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package tables
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
||||||
|
"github.com/lmika/awstools/internal/common/sliceutils"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||||
|
@ -114,6 +115,36 @@ func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, in
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.ResultSet, shouldPut func(idx int) bool) (int, error) {
|
||||||
|
type dirtyItem struct {
|
||||||
|
item models.Item
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyItems := make([]dirtyItem, 0)
|
||||||
|
for i, item := range resultSet.Items() {
|
||||||
|
if shouldPut(i) {
|
||||||
|
dirtyItems = append(dirtyItems, dirtyItem{item, i})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dirtyItems) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.provider.PutItems(ctx, resultSet.TableInfo.Name, sliceutils.Map(dirtyItems, func(t dirtyItem) models.Item {
|
||||||
|
return t.item
|
||||||
|
})); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, di := range dirtyItems {
|
||||||
|
resultSet.SetDirty(di.idx, false)
|
||||||
|
resultSet.SetNew(di.idx, false)
|
||||||
|
}
|
||||||
|
return len(dirtyItems), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
|
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {
|
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {
|
||||||
|
|
|
@ -13,8 +13,7 @@ import (
|
||||||
func TestService_Describe(t *testing.T) {
|
func TestService_Describe(t *testing.T) {
|
||||||
tableName := "service-test-data"
|
tableName := "service-test-data"
|
||||||
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
t.Run("return details of the table", func(t *testing.T) {
|
t.Run("return details of the table", func(t *testing.T) {
|
||||||
|
@ -35,8 +34,7 @@ func TestService_Describe(t *testing.T) {
|
||||||
func TestService_Scan(t *testing.T) {
|
func TestService_Scan(t *testing.T) {
|
||||||
tableName := "service-test-data"
|
tableName := "service-test-data"
|
||||||
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
defer cleanupFn()
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
|
|
||||||
t.Run("return all columns and fields in sorted order", func(t *testing.T) {
|
t.Run("return all columns and fields in sorted order", func(t *testing.T) {
|
||||||
|
@ -52,9 +50,6 @@ func TestService_Scan(t *testing.T) {
|
||||||
// Hash first, then range, then columns in alphabetic order
|
// Hash first, then range, then columns in alphabetic order
|
||||||
assert.Equal(t, rs.TableInfo, ti)
|
assert.Equal(t, rs.TableInfo, ti)
|
||||||
assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"})
|
assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"})
|
||||||
//assert.Equal(t, rs.Items[0], testdynamo.TestRecordAsItem(t, testData[1]))
|
|
||||||
//assert.Equal(t, rs.Items[1], testdynamo.TestRecordAsItem(t, testData[0]))
|
|
||||||
//assert.Equal(t, rs.Items[2], testdynamo.TestRecordAsItem(t, testData[2]))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
||||||
},
|
},
|
||||||
|
|
||||||
"put": func(args []string) tea.Cmd {
|
"put": func(args []string) tea.Cmd {
|
||||||
return wc.PutItem(dtv.SelectedItemIndex())
|
return wc.PutItems()
|
||||||
},
|
},
|
||||||
"touch": func(args []string) tea.Cmd {
|
"touch": func(args []string) tea.Cmd {
|
||||||
return wc.TouchItem(dtv.SelectedItemIndex())
|
return wc.TouchItem(dtv.SelectedItemIndex())
|
||||||
|
|
|
@ -18,7 +18,7 @@ type TestData struct {
|
||||||
Data []map[string]interface{}
|
Data []map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()) {
|
func SetupTestTable(t *testing.T, testData []TestData) *dynamodb.Client {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -68,5 +68,5 @@ func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return dynamoClient, func() {}
|
return dynamoClient
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue