sqs-browse: added notion of workspaces in sqs-browse
Also added a tool to generate test tables
This commit is contained in:
parent
cecdbafabb
commit
30dbc4eefe
16 changed files with 239 additions and 60 deletions
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func TestCommandController_Prompt(t *testing.T) {
|
||||
t.Run("prompt user for a command", func(t *testing.T) {
|
||||
cmd := commandctrl.NewCommandController()
|
||||
cmd := commandctrl.NewCommandController(nil)
|
||||
|
||||
ctx, uiCtx := testuictx.New(context.Background())
|
||||
err := cmd.Prompt().Execute(ctx)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func TestTableWriteController_Delete(t *testing.T) {
|
|||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
||||
ResultSet: resultSet,
|
||||
SelectedItem: resultSet.Items[1],
|
||||
InReadWriteMode: false,
|
||||
InReadWriteMode: true,
|
||||
})
|
||||
|
||||
op := twc.Delete()
|
||||
|
|
@ -91,7 +91,7 @@ func TestTableWriteController_Delete(t *testing.T) {
|
|||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
||||
ResultSet: resultSet,
|
||||
SelectedItem: resultSet.Items[1],
|
||||
InReadWriteMode: false,
|
||||
InReadWriteMode: true,
|
||||
})
|
||||
|
||||
op := twc.Delete()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package modexpr
|
|||
|
||||
import (
|
||||
"github.com/alecthomas/participle/v2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type astExpr struct {
|
||||
|
|
@ -12,19 +10,17 @@ type astExpr struct {
|
|||
}
|
||||
|
||||
type astAttribute struct {
|
||||
Name string `parser:"@Ident '='"`
|
||||
Value string `parser:"@String"`
|
||||
Names *astKeyList `parser:"@@ '='"`
|
||||
Value *astLiteralValue `parser:"@@"`
|
||||
}
|
||||
|
||||
func (a astAttribute) dynamoValue() (types.AttributeValue, error) {
|
||||
// TODO: should be based on type
|
||||
s, err := strconv.Unquote(a.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot unquote string")
|
||||
}
|
||||
return &types.AttributeValueMemberS{Value: s}, nil
|
||||
type astKeyList struct {
|
||||
Names []string `parser:"@Ident ('/' @Ident)*"`
|
||||
}
|
||||
|
||||
type astLiteralValue struct {
|
||||
String string `parser:"@String"`
|
||||
}
|
||||
|
||||
var parser = participle.MustBuild(&astExpr{})
|
||||
|
||||
|
|
|
|||
31
internal/dynamo-browse/models/modexpr/astmods.go
Normal file
31
internal/dynamo-browse/models/modexpr/astmods.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package modexpr
|
||||
|
||||
import "github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
|
||||
func (a *astExpr) calcPatchMods(item models.Item) ([]patchMod, error) {
|
||||
patchMods := make([]patchMod, 0)
|
||||
|
||||
for _, attr := range a.Attributes {
|
||||
attrPatchMods, err := attr.calcPatchMods(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patchMods = append(patchMods, attrPatchMods...)
|
||||
}
|
||||
|
||||
return patchMods, nil
|
||||
}
|
||||
|
||||
func (a *astAttribute) calcPatchMods(item models.Item) ([]patchMod, error) {
|
||||
value, err := a.Value.dynamoValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchMods := make([]patchMod, 0)
|
||||
for _, key := range a.Names.Names {
|
||||
patchMods = append(patchMods, setAttributeMod{key: key, to: value})
|
||||
}
|
||||
|
||||
return patchMods, nil
|
||||
}
|
||||
|
|
@ -7,15 +7,14 @@ type ModExpr struct {
|
|||
}
|
||||
|
||||
func (me *ModExpr) Patch(item models.Item) (models.Item, error) {
|
||||
newItem := item.Clone()
|
||||
mods, err := me.ast.calcPatchMods(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, attribute := range me.ast.Attributes {
|
||||
var err error
|
||||
name := attribute.Name
|
||||
newItem[name], err = attribute.dynamoValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newItem := item.Clone()
|
||||
for _, mod := range mods {
|
||||
mod.Apply(newItem)
|
||||
}
|
||||
|
||||
return newItem, nil
|
||||
|
|
|
|||
|
|
@ -36,4 +36,20 @@ func TestModExpr_Patch(t *testing.T) {
|
|||
assert.Equal(t, "new value", newItem["alpha"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "another new value", newItem["beta"].(*types.AttributeValueMemberS).Value)
|
||||
})
|
||||
|
||||
t.Run("patch with key tuple", func(t *testing.T) {
|
||||
modExpr, err := modexpr.Parse(`alpha/beta="new value"`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldItem := models.Item{
|
||||
"old": &types.AttributeValueMemberS{Value: "before"},
|
||||
"beta": &types.AttributeValueMemberS{Value: "before beta"},
|
||||
}
|
||||
newItem, err := modExpr.Patch(oldItem)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "before", newItem["old"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "new value", newItem["alpha"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "new value", newItem["beta"].(*types.AttributeValueMemberS).Value)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
19
internal/dynamo-browse/models/modexpr/mods.go
Normal file
19
internal/dynamo-browse/models/modexpr/mods.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package modexpr
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
)
|
||||
|
||||
type patchMod interface {
|
||||
Apply(item models.Item)
|
||||
}
|
||||
|
||||
type setAttributeMod struct {
|
||||
key string
|
||||
to types.AttributeValue
|
||||
}
|
||||
|
||||
func (sa setAttributeMod) Apply(item models.Item) {
|
||||
item[sa.key] = sa.to
|
||||
}
|
||||
15
internal/dynamo-browse/models/modexpr/values.go
Normal file
15
internal/dynamo-browse/models/modexpr/values.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package modexpr
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (a *astLiteralValue) dynamoValue() (types.AttributeValue, error) {
|
||||
s, err := strconv.Unquote(a.String)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot unquote string")
|
||||
}
|
||||
return &types.AttributeValueMemberS{Value: s}, nil
|
||||
}
|
||||
|
|
@ -155,10 +155,14 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
// Tea events
|
||||
case tea.WindowSizeMsg:
|
||||
fixedViewsHeight := lipgloss.Height(m.headerView()) + lipgloss.Height(m.splitterView()) + lipgloss.Height(m.footerView())
|
||||
tableHeight := msg.Height / 2
|
||||
viewportHeight := msg.Height / 2 // TODO: make this dynamic
|
||||
if viewportHeight > 15 {
|
||||
viewportHeight = 15
|
||||
}
|
||||
tableHeight := msg.Height - fixedViewsHeight - viewportHeight
|
||||
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(msg.Width, msg.Height-tableHeight-fixedViewsHeight)
|
||||
m.viewport = viewport.New(msg.Width, viewportHeight)
|
||||
m.viewport.SetContent("(no message selected)")
|
||||
m.ready = true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package models
|
|||
import "time"
|
||||
|
||||
type Message struct {
|
||||
ID uint64
|
||||
ExtID string
|
||||
Queue string
|
||||
ID uint64 `storm:"id,increment"`
|
||||
ExtID string `storm:"unique"`
|
||||
Queue string `storm:"index"`
|
||||
Received time.Time
|
||||
Data string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package memstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/lmika/awstools/internal/sqs-browse/models"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
messages []models.Message
|
||||
|
||||
mtx *sync.Mutex
|
||||
currSeqNo uint64
|
||||
}
|
||||
|
||||
func (s *Store) Save(ctx context.Context, msg *models.Message) error {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
s.currSeqNo++
|
||||
msg.ID = s.currSeqNo
|
||||
s.messages = append(s.messages, *msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewStore() *Store {
|
||||
return &Store{
|
||||
messages: make([]models.Message, 0),
|
||||
mtx: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
30
internal/sqs-browse/providers/stormstore/memstore.go
Normal file
30
internal/sqs-browse/providers/stormstore/memstore.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package stormstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/asdine/storm"
|
||||
"github.com/lmika/awstools/internal/sqs-browse/models"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *storm.DB
|
||||
}
|
||||
|
||||
// TODO: should probably be a workspace provider
|
||||
func NewStore(filename string) (*Store, error) {
|
||||
db, err := storm.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot open store %v", filename)
|
||||
}
|
||||
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *Store) Close() {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *Store) Save(ctx context.Context, msg *models.Message) error {
|
||||
return s.db.Save(msg)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue