Changed set-attr to support changing attributes of marked items

This commit is contained in:
Leon Mika 2022-07-17 22:01:25 +10:00
parent 7fc67e4d9a
commit 0f0bf70d01
6 changed files with 108 additions and 65 deletions

View file

@ -60,8 +60,11 @@ func (c *CommandController) Alias(commandName string) Command {
return events.SetError(errors.New("no such command: " + commandName))
}
if len(args) > 1 {
return command(args[1:])
}
return command([]string{})
}
}
func (c *CommandController) lookupCommand(name string) Command {

View file

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/sliceutils"
"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"
@ -112,12 +113,15 @@ func (twc *TableWriteController) setStringValue(idx int, attr attrPath) tea.Cmd
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
err := attr.setAt(set.Items()[idx], &types.AttributeValueMemberS{Value: value})
if err != nil {
if err := twc.applyToItems(set, idx, func(idx int, item models.Item) error {
if err := attr.setAt(item, &types.AttributeValueMemberS{Value: value}); err != nil {
return err
}
set.SetDirty(idx, true)
return nil
}); err != nil {
return err
}
set.RefreshColumns()
return nil
}); err != nil {
@ -130,6 +134,19 @@ func (twc *TableWriteController) setStringValue(idx int, attr attrPath) tea.Cmd
}
}
func (twc *TableWriteController) applyToItems(rs *models.ResultSet, selectedIndex int, applyFn func(idx int, item models.Item) error) error {
if markedItems := rs.MarkedItems(); len(markedItems) > 0 {
for _, mi := range markedItems {
if err := applyFn(mi.Index, mi.Item); err != nil {
return err
}
}
return nil
}
return applyFn(selectedIndex, rs.Items()[selectedIndex])
}
func (twc *TableWriteController) setNumberValue(idx int, attr attrPath) tea.Cmd {
return func() tea.Msg {
return events.PromptForInputMsg{
@ -137,12 +154,15 @@ func (twc *TableWriteController) setNumberValue(idx int, attr attrPath) tea.Cmd
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
err := attr.setAt(set.Items()[idx], &types.AttributeValueMemberN{Value: value})
if err != nil {
if err := twc.applyToItems(set, idx, func(idx int, item models.Item) error {
if err := attr.setAt(item, &types.AttributeValueMemberN{Value: value}); err != nil {
return err
}
set.SetDirty(idx, true)
return nil
}); err != nil {
return err
}
set.RefreshColumns()
return nil
}); err != nil {
@ -167,12 +187,15 @@ func (twc *TableWriteController) setBoolValue(idx int, attr attrPath) tea.Cmd {
}
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
err := attr.setAt(set.Items()[idx], &types.AttributeValueMemberBOOL{Value: b})
if err != nil {
if err := twc.applyToItems(set, idx, func(idx int, item models.Item) error {
if err := attr.setAt(item, &types.AttributeValueMemberBOOL{Value: b}); err != nil {
return err
}
set.SetDirty(idx, true)
return nil
}); err != nil {
return err
}
set.RefreshColumns()
return nil
}); err != nil {
@ -188,12 +211,15 @@ func (twc *TableWriteController) setBoolValue(idx int, attr attrPath) tea.Cmd {
func (twc *TableWriteController) setNullValue(idx int, attr attrPath) tea.Cmd {
return func() tea.Msg {
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
err := attr.setAt(set.Items()[idx], &types.AttributeValueMemberNULL{Value: true})
if err != nil {
if err := twc.applyToItems(set, idx, func(idx int, item models.Item) error {
if err := attr.setAt(item, &types.AttributeValueMemberNULL{Value: true}); err != nil {
return err
}
set.SetDirty(idx, true)
return nil
}); err != nil {
return err
}
set.RefreshColumns()
return nil
}); err != nil {
@ -260,21 +286,29 @@ func (twc *TableWriteController) PutItem(idx int) tea.Cmd {
func (twc *TableWriteController) PutItems() tea.Cmd {
return func() tea.Msg {
var (
expectedPuts int
markedItems int
markedItemCount int
)
var itemsToPut []models.ItemIndex
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 markedItems := rs.MarkedItems(); len(markedItems) > 0 {
for _, mi := range markedItems {
markedItemCount += 1
if rs.IsDirty(mi.Index) {
itemsToPut = append(itemsToPut, mi)
}
}
} else {
for i, itm := range rs.Items() {
if rs.IsDirty(i) {
itemsToPut = append(itemsToPut, models.ItemIndex{Item: itm, Index: i})
}
}
}
})
if expectedPuts == 0 {
if markedItems > 0 {
if len(itemsToPut) == 0 {
if markedItemCount > 0 {
return events.StatusMsg("no marked items are modified")
} else {
return events.StatusMsg("no items are modified")
@ -282,10 +316,10 @@ func (twc *TableWriteController) PutItems() tea.Cmd {
}
var promptMessage string
if markedItems > 0 {
promptMessage = applyToN("put ", expectedPuts, "marked item", "marked items", "? ")
if markedItemCount > 0 {
promptMessage = applyToN("put ", len(itemsToPut), "marked item", "marked items", "? ")
} else {
promptMessage = applyToN("put ", expectedPuts, "item", "items", "? ")
promptMessage = applyToN("put ", len(itemsToPut), "item", "items", "? ")
}
return events.PromptForInputMsg{
@ -297,13 +331,9 @@ func (twc *TableWriteController) PutItems() tea.Cmd {
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))
})
err := twc.tableService.PutSelectedItems(context.Background(), rs, itemsToPut)
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 {
@ -311,7 +341,7 @@ func (twc *TableWriteController) PutItems() tea.Cmd {
}
return ResultSetUpdated{
statusMessage: applyToN("", expectedPuts, "item", "item", " put to table"),
statusMessage: applyToN("", len(itemsToPut), "item", "item", " put to table"),
}
}
},
@ -395,7 +425,9 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
if err := twc.tableService.Delete(ctx, resultSet.TableInfo, markedItems); err != nil {
if err := twc.tableService.Delete(ctx, resultSet.TableInfo, sliceutils.Map(markedItems, func(index models.ItemIndex) models.Item {
return index.Item
})); err != nil {
return events.Error(err)
}

View file

@ -77,22 +77,37 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
}
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should set value of field %v", scenario.attrKey), func(t *testing.T) {
t.Run(fmt.Sprintf("should set value of field: %v", scenario.attrKey), 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.SetAttributeValue(0, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
after, _ := state.ResultSet().Items()[0][scenario.attrKey]
assert.Equal(t, scenario.expected, after)
assert.True(t, state.ResultSet().IsDirty(0))
})
t.Run(fmt.Sprintf("should set value of marked fields: %v", scenario.attrKey), func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommand(t, writeController.ToggleMark(0))
invokeCommand(t, writeController.ToggleMark(2))
invokeCommandWithPrompt(t, writeController.SetAttributeValue(1, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
after1, _ := state.ResultSet().Items()[0][scenario.attrKey]
assert.Equal(t, scenario.expected, after1)
assert.True(t, state.ResultSet().IsDirty(0))
after2, _ := state.ResultSet().Items()[2][scenario.attrKey]
assert.Equal(t, scenario.expected, after2)
assert.True(t, state.ResultSet().IsDirty(2))
})
}
})

View file

@ -5,6 +5,11 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/models/itemrender"
)
type ItemIndex struct {
Index int
Item Item
}
type Item map[string]types.AttributeValue
// Clone creates a clone of the current item

View file

@ -70,11 +70,11 @@ func (rs *ResultSet) IsNew(idx int) bool {
return rs.attributes[idx].New
}
func (rs *ResultSet) MarkedItems() []Item {
items := make([]Item, 0)
func (rs *ResultSet) MarkedItems() []ItemIndex {
items := make([]ItemIndex, 0)
for i, itemAttr := range rs.attributes {
if itemAttr.Marked && !itemAttr.Hidden {
items = append(items, rs.items[i])
items = append(items, ItemIndex{Index: i, Item: rs.items[i]})
}
}
return items

View file

@ -115,34 +115,22 @@ func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, in
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
func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.ResultSet, markedItems []models.ItemIndex) error {
if len(markedItems) == 0 {
return nil
}
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
if err := s.provider.PutItems(ctx, resultSet.TableInfo.Name, sliceutils.Map(markedItems, func(t models.ItemIndex) models.Item {
return t.Item
})); err != nil {
return 0, err
return err
}
for _, di := range dirtyItems {
resultSet.SetDirty(di.idx, false)
resultSet.SetNew(di.idx, false)
for _, di := range markedItems {
resultSet.SetDirty(di.Index, false)
resultSet.SetNew(di.Index, false)
}
return len(dirtyItems), nil
return nil
}
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {