From 721d3abe5ebbc2d3d4267e81c7c9cf979ede1db0 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Thu, 11 Aug 2022 22:23:39 +1000 Subject: [PATCH 1/6] backstack: added saving of backstack to workspace --- cmd/dynamo-browse/main.go | 17 +++++- internal/common/workspaces/manager.go | 37 +++++++++++++ internal/common/workspaces/workspaces.go | 19 +++++++ .../dynamo-browse/controllers/tableread.go | 21 ++++--- internal/dynamo-browse/models/models.go | 5 +- .../models/serialisable/resultset.go | 19 +++++++ .../workspacestore/resultsetsnapshot.go | 55 +++++++++++++++++++ .../services/workspaces/iface.go | 9 +++ .../services/workspaces/service.go | 43 +++++++++++++++ 9 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 internal/common/workspaces/manager.go create mode 100644 internal/common/workspaces/workspaces.go create mode 100644 internal/dynamo-browse/models/serialisable/resultset.go create mode 100644 internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go create mode 100644 internal/dynamo-browse/services/workspaces/iface.go create mode 100644 internal/dynamo-browse/services/workspaces/service.go diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index e0695cf..b2213d2 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -11,9 +11,12 @@ import ( "github.com/lmika/audax/internal/common/ui/commandctrl" "github.com/lmika/audax/internal/common/ui/logging" "github.com/lmika/audax/internal/common/ui/osstyle" + "github.com/lmika/audax/internal/common/workspaces" "github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" "github.com/lmika/audax/internal/dynamo-browse/services/tables" + workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces" "github.com/lmika/audax/internal/dynamo-browse/ui" "github.com/lmika/gopkgs/cli" "log" @@ -34,6 +37,16 @@ func main() { cli.Fatalf("cannot load AWS config: %v", err) } + wsManager := workspaces.New(workspaces.MetaInfo{ + Command: "sqs-browse", + }) + //ws, err := wsManager.CreateTemp() + ws, err := wsManager.Open("temp.workspace") + if err != nil { + cli.Fatalf("cannot create workspace: %v", ws) + } + defer ws.Close() + var dynamoClient *dynamodb.Client if *flagLocal != "" { host, port, err := net.SplitHostPort(*flagLocal) @@ -53,11 +66,13 @@ func main() { } dynamoProvider := dynamo.NewProvider(dynamoClient) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws) tableService := tables.NewService(dynamoProvider) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) state := controllers.NewState() - tableReadController := controllers.NewTableReadController(state, tableService, *flagTable) + tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, *flagTable) tableWriteController := controllers.NewTableWriteController(state, tableService, tableReadController) commandController := commandctrl.NewCommandController() diff --git a/internal/common/workspaces/manager.go b/internal/common/workspaces/manager.go new file mode 100644 index 0000000..da08a4d --- /dev/null +++ b/internal/common/workspaces/manager.go @@ -0,0 +1,37 @@ +package workspaces + +import ( + "github.com/asdine/storm" + "github.com/pkg/errors" + "os" +) + +type MetaInfo struct { + Command string +} + +type Manager struct { + metainfo MetaInfo +} + +func New(metaInfo MetaInfo) *Manager { + return &Manager{metainfo: metaInfo} +} + +func (m *Manager) Open(filename string) (*Workspace, error) { + db, err := storm.Open(filename) + if err != nil { + return nil, errors.Wrapf(err, "cannot open workspace at %v", filename) + } + return &Workspace{db: db}, nil +} + +func (m *Manager) CreateTemp() (*Workspace, error) { + workspaceFile, err := os.CreateTemp("", m.metainfo.Command+"*.workspace") + if err != nil { + return nil, errors.Wrapf(err, "cannot create workspace file") + } + workspaceFile.Close() // We just need the filename + + return m.Open(workspaceFile.Name()) +} diff --git a/internal/common/workspaces/workspaces.go b/internal/common/workspaces/workspaces.go new file mode 100644 index 0000000..00bcc87 --- /dev/null +++ b/internal/common/workspaces/workspaces.go @@ -0,0 +1,19 @@ +package workspaces + +import ( + "github.com/asdine/storm" + "log" +) + +type Workspace struct { + db *storm.DB +} + +func (ws *Workspace) DB() *storm.DB { + return ws.db +} + +func (ws *Workspace) Close() { + log.Printf("close workspace") + ws.db.Close() +} diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index c9a3861..f17917f 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -7,14 +7,17 @@ import ( "github.com/lmika/audax/internal/common/ui/events" "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/services/workspaces" "github.com/pkg/errors" + "log" "os" "sync" ) type TableReadController struct { - tableService TableReadService - tableName string + tableService TableReadService + workspaceService *workspaces.Service + tableName string // state mutex *sync.Mutex @@ -23,12 +26,13 @@ type TableReadController struct { //filter string } -func NewTableReadController(state *State, tableService TableReadService, tableName string) *TableReadController { +func NewTableReadController(state *State, tableService TableReadService, workspaceService *workspaces.Service, tableName string) *TableReadController { return &TableReadController{ - state: state, - tableService: tableService, - tableName: tableName, - mutex: new(sync.Mutex), + state: state, + tableService: tableService, + workspaceService: workspaceService, + tableName: tableName, + mutex: new(sync.Mutex), } } @@ -99,6 +103,9 @@ func (c *TableReadController) PromptForQuery() tea.Cmd { return events.Error(err) } + if err := c.workspaceService.PushSnapshot(resultSet); err != nil { + log.Printf("cannot push snapshot: %v", err) + } return c.setResultSetAndFilter(newResultSet, "") }) }, diff --git a/internal/dynamo-browse/models/models.go b/internal/dynamo-browse/models/models.go index 7bd9469..c13b901 100644 --- a/internal/dynamo-browse/models/models.go +++ b/internal/dynamo-browse/models/models.go @@ -3,9 +3,8 @@ package models import "sort" type ResultSet struct { - TableInfo *TableInfo - Query Queryable - //Columns []string + TableInfo *TableInfo + Query Queryable items []Item attributes []ItemAttribute diff --git a/internal/dynamo-browse/models/serialisable/resultset.go b/internal/dynamo-browse/models/serialisable/resultset.go new file mode 100644 index 0000000..2bdec08 --- /dev/null +++ b/internal/dynamo-browse/models/serialisable/resultset.go @@ -0,0 +1,19 @@ +package serialisable + +import ( + "github.com/lmika/audax/internal/dynamo-browse/models" + "time" +) + +type ResultSetSnapshot struct { + ID int64 `storm:"id,increment"` + BackLink int64 `storm:"index"` + Time time.Time + TableInfo *models.TableInfo + Query Query + Filter string +} + +type Query struct { + Expression string +} diff --git a/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go new file mode 100644 index 0000000..303f949 --- /dev/null +++ b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go @@ -0,0 +1,55 @@ +package workspacestore + +import ( + "github.com/asdine/storm" + "github.com/lmika/audax/internal/common/workspaces" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" + "github.com/pkg/errors" + "log" +) + +const resultSetSnapshotsBucket = "ResultSetSnapshots" + +type ResultSetSnapshotStore struct { + ws storm.Node +} + +func NewResultSetSnapshotStore(ws *workspaces.Workspace) *ResultSetSnapshotStore { + return &ResultSetSnapshotStore{ + ws: ws.DB().From(resultSetSnapshotsBucket), + } +} + +func (s *ResultSetSnapshotStore) Save(rs *serialisable.ResultSetSnapshot) error { + if err := s.ws.Save(rs); err != nil { + return errors.Wrap(err, "cannot save result set") + } + log.Printf("saved result set") + return nil +} + +func (s *ResultSetSnapshotStore) SetAsHead(resultSetID int64) error { + if err := s.ws.Set("head", "id", resultSetID); err != nil { + return errors.Wrap(err, "cannot set as head") + } + log.Printf("saved result set head") + return nil +} + +func (s *ResultSetSnapshotStore) Head() (*serialisable.ResultSetSnapshot, error) { + var headResultSetID int64 + if err := s.ws.Get("head", "id", &headResultSetID); err != nil && !errors.Is(err, storm.ErrNotFound) { + return nil, errors.Wrap(err, "cannot get head") + } + + var rss serialisable.ResultSetSnapshot + if err := s.ws.One("ID", headResultSetID, &rss); err != nil { + if errors.Is(err, storm.ErrNotFound) { + return nil, nil + } else { + return nil, errors.Wrap(err, "cannot get head") + } + } + + return &rss, nil +} diff --git a/internal/dynamo-browse/services/workspaces/iface.go b/internal/dynamo-browse/services/workspaces/iface.go new file mode 100644 index 0000000..58c8d45 --- /dev/null +++ b/internal/dynamo-browse/services/workspaces/iface.go @@ -0,0 +1,9 @@ +package workspaces + +import "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" + +type ResultSetSnapshotStore interface { + Save(rs *serialisable.ResultSetSnapshot) error + SetAsHead(resultSetId int64) error + Head() (*serialisable.ResultSetSnapshot, error) +} diff --git a/internal/dynamo-browse/services/workspaces/service.go b/internal/dynamo-browse/services/workspaces/service.go new file mode 100644 index 0000000..30ae078 --- /dev/null +++ b/internal/dynamo-browse/services/workspaces/service.go @@ -0,0 +1,43 @@ +package workspaces + +import ( + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" + "github.com/pkg/errors" + "time" +) + +type Service struct { + store ResultSetSnapshotStore +} + +func NewService(store ResultSetSnapshotStore) *Service { + return &Service{ + store: store, + } +} + +func (s *Service) PushSnapshot(rs *models.ResultSet) error { + newSnapshot := &serialisable.ResultSetSnapshot{ + Time: time.Now(), + TableInfo: rs.TableInfo, + } + if q := rs.Query; q != nil { + newSnapshot.Query.Expression = q.String() + } + + if head, err := s.store.Head(); head != nil { + newSnapshot.BackLink = head.ID + } else if err != nil { + return errors.Wrap(err, "cannot get head result set") + } + + if err := s.store.Save(newSnapshot); err != nil { + return errors.Wrap(err, "cannot save snapshot") + } + if err := s.store.SetAsHead(newSnapshot.ID); err != nil { + return errors.Wrap(err, "cannot set new snapshot as head") + } + + return nil +} From ec9ac34d26d8cf42cb58b03b9b1999f8aac89d82 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sat, 13 Aug 2022 11:42:21 +1000 Subject: [PATCH 2/6] backstack: an initial complete version of the backstack This needs a lot of work, and a fair bit of refactoring. --- .../dynamo-browse/controllers/tableread.go | 158 +++++++++++++----- .../models/serialisable/resultset.go | 19 --- .../models/serialisable/viewsnapshot.go | 14 ++ .../workspacestore/resultsetsnapshot.go | 31 +++- .../services/workspaces/iface.go | 7 +- .../services/workspaces/service.go | 36 +++- internal/dynamo-browse/ui/model.go | 2 + 7 files changed, 195 insertions(+), 72 deletions(-) delete mode 100644 internal/dynamo-browse/models/serialisable/resultset.go create mode 100644 internal/dynamo-browse/models/serialisable/viewsnapshot.go diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index f17917f..8021e9a 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -16,7 +16,7 @@ import ( type TableReadController struct { tableService TableReadService - workspaceService *workspaces.Service + workspaceService *workspaces.ViewSnapshotService tableName string // state @@ -26,7 +26,7 @@ type TableReadController struct { //filter string } -func NewTableReadController(state *State, tableService TableReadService, workspaceService *workspaces.Service, tableName string) *TableReadController { +func NewTableReadController(state *State, tableService TableReadService, workspaceService *workspaces.ViewSnapshotService, tableName string) *TableReadController { return &TableReadController{ state: state, tableService: tableService, @@ -84,65 +84,107 @@ func (c *TableReadController) PromptForQuery() tea.Cmd { return events.PromptForInputMsg{ Prompt: "query: ", OnDone: func(value string) tea.Cmd { - if value == "" { - return func() tea.Msg { - resultSet := c.state.ResultSet() - return c.doScan(context.Background(), resultSet, nil) + return func() tea.Msg { + return c.runQuery(c.state.ResultSet().TableInfo, value, "", true) + } + + /* + if value == "" { + return func() tea.Msg { + resultSet := c.state.ResultSet() + return c.doScan(context.Background(), resultSet, nil) + } } - } - expr, err := queryexpr.Parse(value) - if err != nil { - return events.SetError(err) - } - - return c.doIfNoneDirty(func() tea.Msg { - resultSet := c.state.ResultSet() - newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, expr) + expr, err := queryexpr.Parse(value) if err != nil { - return events.Error(err) + return events.SetError(err) } - if err := c.workspaceService.PushSnapshot(resultSet); err != nil { - log.Printf("cannot push snapshot: %v", err) - } - return c.setResultSetAndFilter(newResultSet, "") - }) + return c.doIfNoneDirty(func() tea.Msg { + resultSet := c.state.ResultSet() + newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, expr) + if err != nil { + return events.Error(err) + } + + if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { + log.Printf("cannot push snapshot: %v", err) + } + return c.setResultSetAndFilter(newResultSet, "") + }) + */ }, } } } -func (c *TableReadController) doIfNoneDirty(cmd tea.Cmd) tea.Cmd { +func (c *TableReadController) runQuery(tableInfo *models.TableInfo, query, newFilter string, pushSnapshot bool) tea.Msg { + if query == "" { + newResultSet, err := c.tableService.ScanOrQuery(context.Background(), tableInfo, nil) + if err != nil { + return events.Error(err) + } + + if newFilter != "" { + newResultSet = c.tableService.Filter(newResultSet, newFilter) + } + return c.setResultSetAndFilter(newResultSet, newFilter) + } + + expr, err := queryexpr.Parse(query) + if err != nil { + return events.SetError(err) + } + + return c.doIfNoneDirty(func() tea.Msg { + resultSet := c.state.ResultSet() + newResultSet, err := c.tableService.ScanOrQuery(context.Background(), tableInfo, expr) + if err != nil { + return events.Error(err) + } + + if pushSnapshot { + if err := c.workspaceService.PushSnapshot(resultSet, c.state.Filter()); err != nil { + log.Printf("cannot push snapshot: %v", err) + } + } + if newFilter != "" { + newResultSet = c.tableService.Filter(newResultSet, newFilter) + } + return c.setResultSetAndFilter(newResultSet, newFilter) + }) +} + +func (c *TableReadController) doIfNoneDirty(cmd tea.Cmd) tea.Msg { var anyDirty = false for i := 0; i < len(c.state.ResultSet().Items()); i++ { anyDirty = anyDirty || c.state.ResultSet().IsDirty(i) } if !anyDirty { - return cmd + return cmd() } - return func() tea.Msg { - return events.PromptForInputMsg{ - Prompt: "reset modified items? ", - OnDone: func(value string) tea.Cmd { - if value != "y" { - return events.SetStatus("operation aborted") - } + return events.PromptForInputMsg{ + Prompt: "reset modified items? ", + OnDone: func(value string) tea.Cmd { + if value != "y" { + return events.SetStatus("operation aborted") + } - return cmd - }, - } + return cmd + }, } - } func (c *TableReadController) Rescan() tea.Cmd { - return c.doIfNoneDirty(func() tea.Msg { - resultSet := c.state.ResultSet() - return c.doScan(context.Background(), resultSet, resultSet.Query) - }) + return func() tea.Msg { + return c.doIfNoneDirty(func() tea.Msg { + resultSet := c.state.ResultSet() + return c.doScan(context.Background(), resultSet, resultSet.Query) + }) + } } func (c *TableReadController) ExportCSV(filename string) tea.Cmd { @@ -186,6 +228,7 @@ func (c *TableReadController) doScan(ctx context.Context, resultSet *models.Resu return events.Error(err) } + c.workspaceService.PushSnapshot(resultSet, c.state.Filter()) newResultSet = c.tableService.Filter(newResultSet, c.state.Filter()) return c.setResultSetAndFilter(newResultSet, c.state.Filter()) @@ -214,6 +257,8 @@ func (c *TableReadController) Filter() tea.Cmd { OnDone: func(value string) tea.Cmd { return func() tea.Msg { resultSet := c.state.ResultSet() + + c.workspaceService.PushSnapshot(resultSet, c.state.Filter()) newResultSet := c.tableService.Filter(resultSet, value) return c.setResultSetAndFilter(newResultSet, value) @@ -222,3 +267,40 @@ func (c *TableReadController) Filter() tea.Cmd { } } } + +func (c *TableReadController) ViewBack() tea.Cmd { + return func() tea.Msg { + viewSnapshot, err := c.workspaceService.PopSnapshot() + if err != nil { + return events.Error(err) + } else if viewSnapshot == nil { + return events.StatusMsg("Backstack is empty") + } + + currentResultSet := c.state.ResultSet() + + var currentQueryExpr string + if currentResultSet.Query != nil { + currentQueryExpr = currentResultSet.Query.String() + } + + if viewSnapshot.TableName == currentResultSet.TableInfo.Name && viewSnapshot.Query == currentQueryExpr { + log.Printf("backstack: setting filter to '%v'", viewSnapshot.Filter) + + newResultSet := c.tableService.Filter(currentResultSet, viewSnapshot.Filter) + return c.setResultSetAndFilter(newResultSet, viewSnapshot.Filter) + } + + tableInfo := currentResultSet.TableInfo + if viewSnapshot.TableName != currentResultSet.TableInfo.Name { + tableInfo, err = c.tableService.Describe(context.Background(), viewSnapshot.TableName) + if err != nil { + return events.Error(err) + } + } + + log.Printf("backstack: running query: table = '%v', query = '%v', filter = '%v'", + tableInfo.Name, viewSnapshot.Query, viewSnapshot.Filter) + return c.runQuery(tableInfo, viewSnapshot.Query, viewSnapshot.Filter, false) + } +} diff --git a/internal/dynamo-browse/models/serialisable/resultset.go b/internal/dynamo-browse/models/serialisable/resultset.go deleted file mode 100644 index 2bdec08..0000000 --- a/internal/dynamo-browse/models/serialisable/resultset.go +++ /dev/null @@ -1,19 +0,0 @@ -package serialisable - -import ( - "github.com/lmika/audax/internal/dynamo-browse/models" - "time" -) - -type ResultSetSnapshot struct { - ID int64 `storm:"id,increment"` - BackLink int64 `storm:"index"` - Time time.Time - TableInfo *models.TableInfo - Query Query - Filter string -} - -type Query struct { - Expression string -} diff --git a/internal/dynamo-browse/models/serialisable/viewsnapshot.go b/internal/dynamo-browse/models/serialisable/viewsnapshot.go new file mode 100644 index 0000000..80cd04a --- /dev/null +++ b/internal/dynamo-browse/models/serialisable/viewsnapshot.go @@ -0,0 +1,14 @@ +package serialisable + +import ( + "time" +) + +type ViewSnapshot struct { + ID int64 `storm:"id,increment"` + BackLink int64 `storm:"index"` + Time time.Time + TableName string + Query string + Filter string +} diff --git a/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go index 303f949..09ef0cf 100644 --- a/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go +++ b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go @@ -20,15 +20,22 @@ func NewResultSetSnapshotStore(ws *workspaces.Workspace) *ResultSetSnapshotStore } } -func (s *ResultSetSnapshotStore) Save(rs *serialisable.ResultSetSnapshot) error { +func (s *ResultSetSnapshotStore) Save(rs *serialisable.ViewSnapshot) error { if err := s.ws.Save(rs); err != nil { return errors.Wrap(err, "cannot save result set") } - log.Printf("saved result set") + log.Printf("saved result set: table='%v', query='%v', filter='%v'", rs.TableName, rs.Query, rs.Filter) return nil } func (s *ResultSetSnapshotStore) SetAsHead(resultSetID int64) error { + if resultSetID == 0 { + if err := s.ws.Delete("head", "id"); err != nil { + return errors.Wrap(err, "cannot remove head") + } + return nil + } + if err := s.ws.Set("head", "id", resultSetID); err != nil { return errors.Wrap(err, "cannot set as head") } @@ -36,13 +43,13 @@ func (s *ResultSetSnapshotStore) SetAsHead(resultSetID int64) error { return nil } -func (s *ResultSetSnapshotStore) Head() (*serialisable.ResultSetSnapshot, error) { +func (s *ResultSetSnapshotStore) Head() (*serialisable.ViewSnapshot, error) { var headResultSetID int64 if err := s.ws.Get("head", "id", &headResultSetID); err != nil && !errors.Is(err, storm.ErrNotFound) { return nil, errors.Wrap(err, "cannot get head") } - var rss serialisable.ResultSetSnapshot + var rss serialisable.ViewSnapshot if err := s.ws.One("ID", headResultSetID, &rss); err != nil { if errors.Is(err, storm.ErrNotFound) { return nil, nil @@ -53,3 +60,19 @@ func (s *ResultSetSnapshotStore) Head() (*serialisable.ResultSetSnapshot, error) return &rss, nil } + +func (s *ResultSetSnapshotStore) Remove(resultSetId int64) error { + var rss serialisable.ViewSnapshot + if err := s.ws.One("ID", resultSetId, &rss); err != nil { + if errors.Is(err, storm.ErrNotFound) { + return nil + } else { + return errors.Wrapf(err, "cannot get snapshot with ID %v", resultSetId) + } + } + + if err := s.ws.DeleteStruct(&rss); err != nil { + return errors.Wrap(err, "cannot delete snapshot") + } + return nil +} diff --git a/internal/dynamo-browse/services/workspaces/iface.go b/internal/dynamo-browse/services/workspaces/iface.go index 58c8d45..75a151a 100644 --- a/internal/dynamo-browse/services/workspaces/iface.go +++ b/internal/dynamo-browse/services/workspaces/iface.go @@ -2,8 +2,9 @@ package workspaces import "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" -type ResultSetSnapshotStore interface { - Save(rs *serialisable.ResultSetSnapshot) error +type ViewSnapshotStore interface { + Save(rs *serialisable.ViewSnapshot) error SetAsHead(resultSetId int64) error - Head() (*serialisable.ResultSetSnapshot, error) + Head() (*serialisable.ViewSnapshot, error) + Remove(resultSetId int64) error } diff --git a/internal/dynamo-browse/services/workspaces/service.go b/internal/dynamo-browse/services/workspaces/service.go index 30ae078..540494a 100644 --- a/internal/dynamo-browse/services/workspaces/service.go +++ b/internal/dynamo-browse/services/workspaces/service.go @@ -7,24 +7,25 @@ import ( "time" ) -type Service struct { - store ResultSetSnapshotStore +type ViewSnapshotService struct { + store ViewSnapshotStore } -func NewService(store ResultSetSnapshotStore) *Service { - return &Service{ +func NewService(store ViewSnapshotStore) *ViewSnapshotService { + return &ViewSnapshotService{ store: store, } } -func (s *Service) PushSnapshot(rs *models.ResultSet) error { - newSnapshot := &serialisable.ResultSetSnapshot{ +func (s *ViewSnapshotService) PushSnapshot(rs *models.ResultSet, filter string) error { + newSnapshot := &serialisable.ViewSnapshot{ Time: time.Now(), - TableInfo: rs.TableInfo, + TableName: rs.TableInfo.Name, } if q := rs.Query; q != nil { - newSnapshot.Query.Expression = q.String() + newSnapshot.Query = q.String() } + newSnapshot.Filter = filter if head, err := s.store.Head(); head != nil { newSnapshot.BackLink = head.ID @@ -41,3 +42,22 @@ func (s *Service) PushSnapshot(rs *models.ResultSet) error { return nil } + +func (s *ViewSnapshotService) PopSnapshot() (*serialisable.ViewSnapshot, error) { + vs, err := s.store.Head() + if err != nil { + return nil, errors.Wrap(err, "cannot get snapshot head") + } + if vs == nil { + return vs, nil + } + + if err := s.store.SetAsHead(vs.BackLink); err != nil { + return nil, errors.Wrap(err, "cannot set new head") + } + if err := s.store.Remove(vs.ID); err != nil { + return nil, errors.Wrap(err, "cannot remove old ID") + } + + return vs, nil +} diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index 3f746f5..eaefd2f 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -149,6 +149,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, m.tableReadController.PromptForQuery() case "/": return m, m.tableReadController.Filter() + case "backspace": + return m, m.tableReadController.ViewBack() //case "e": // m.itemEdit.Visible() // return m, nil From 6c5787b27185242fc9d911faa540758b746ef4d0 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 14 Aug 2022 09:16:28 +1000 Subject: [PATCH 3/6] backstack: reimplemented the backstack This is a much cleaner implementation. --- .../dynamo-browse/controllers/tableread.go | 88 +++++++++---------- .../dynamo-browse/controllers/tablewrite.go | 4 +- .../services/workspaces/service.go | 12 ++- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index 8021e9a..b994eb0 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -74,8 +74,9 @@ func (c *TableReadController) ScanTable(name string) tea.Cmd { if err != nil { return events.Error(err) } + resultSet = c.tableService.Filter(resultSet, c.state.Filter()) - return c.setResultSetAndFilter(resultSet, c.state.Filter()) + return c.setResultSetAndFilter(resultSet, c.state.Filter(), true) } } @@ -88,32 +89,33 @@ func (c *TableReadController) PromptForQuery() tea.Cmd { return c.runQuery(c.state.ResultSet().TableInfo, value, "", true) } - /* - if value == "" { - return func() tea.Msg { - resultSet := c.state.ResultSet() - return c.doScan(context.Background(), resultSet, nil) - } - } - - expr, err := queryexpr.Parse(value) - if err != nil { - return events.SetError(err) - } - - return c.doIfNoneDirty(func() tea.Msg { - resultSet := c.state.ResultSet() - newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, expr) - if err != nil { - return events.Error(err) - } - - if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { - log.Printf("cannot push snapshot: %v", err) - } - return c.setResultSetAndFilter(newResultSet, "") - }) - */ + //if value == "" { + // return func() tea.Msg { + // resultSet := c.state.ResultSet() + // if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { + // log.Printf("cannot push snapshot: %v", err) + // } + // return c.doScan(context.Background(), resultSet, nil) + // } + //} + // + //expr, err := queryexpr.Parse(value) + //if err != nil { + // return events.SetError(err) + //} + // + //return c.doIfNoneDirty(func() tea.Msg { + // resultSet := c.state.ResultSet() + // newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, expr) + // if err != nil { + // return events.Error(err) + // } + // + // if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { + // log.Printf("cannot push snapshot: %v", err) + // } + // return c.setResultSetAndFilter(newResultSet, "") + //}) }, } } @@ -129,7 +131,8 @@ func (c *TableReadController) runQuery(tableInfo *models.TableInfo, query, newFi if newFilter != "" { newResultSet = c.tableService.Filter(newResultSet, newFilter) } - return c.setResultSetAndFilter(newResultSet, newFilter) + + return c.setResultSetAndFilter(newResultSet, newFilter, pushSnapshot) } expr, err := queryexpr.Parse(query) @@ -138,21 +141,15 @@ func (c *TableReadController) runQuery(tableInfo *models.TableInfo, query, newFi } return c.doIfNoneDirty(func() tea.Msg { - resultSet := c.state.ResultSet() newResultSet, err := c.tableService.ScanOrQuery(context.Background(), tableInfo, expr) if err != nil { return events.Error(err) } - if pushSnapshot { - if err := c.workspaceService.PushSnapshot(resultSet, c.state.Filter()); err != nil { - log.Printf("cannot push snapshot: %v", err) - } - } if newFilter != "" { newResultSet = c.tableService.Filter(newResultSet, newFilter) } - return c.setResultSetAndFilter(newResultSet, newFilter) + return c.setResultSetAndFilter(newResultSet, newFilter, pushSnapshot) }) } @@ -182,7 +179,7 @@ func (c *TableReadController) Rescan() tea.Cmd { return func() tea.Msg { return c.doIfNoneDirty(func() tea.Msg { resultSet := c.state.ResultSet() - return c.doScan(context.Background(), resultSet, resultSet.Query) + return c.doScan(context.Background(), resultSet, resultSet.Query, true) }) } } @@ -222,19 +219,24 @@ func (c *TableReadController) ExportCSV(filename string) tea.Cmd { } } -func (c *TableReadController) doScan(ctx context.Context, resultSet *models.ResultSet, query models.Queryable) tea.Msg { +func (c *TableReadController) doScan(ctx context.Context, resultSet *models.ResultSet, query models.Queryable, pushBackstack bool) tea.Msg { newResultSet, err := c.tableService.ScanOrQuery(ctx, resultSet.TableInfo, query) if err != nil { return events.Error(err) } - c.workspaceService.PushSnapshot(resultSet, c.state.Filter()) newResultSet = c.tableService.Filter(newResultSet, c.state.Filter()) - return c.setResultSetAndFilter(newResultSet, c.state.Filter()) + return c.setResultSetAndFilter(newResultSet, c.state.Filter(), pushBackstack) } -func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string) tea.Msg { +func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string, pushBackstack bool) tea.Msg { + if pushBackstack { + if err := c.workspaceService.PushSnapshot(resultSet, filter); err != nil { + log.Printf("cannot push snapshot: %v", err) + } + } + c.state.setResultSetAndFilter(resultSet, filter) return c.state.buildNewResultSetMessage("") } @@ -257,11 +259,9 @@ func (c *TableReadController) Filter() tea.Cmd { OnDone: func(value string) tea.Cmd { return func() tea.Msg { resultSet := c.state.ResultSet() - - c.workspaceService.PushSnapshot(resultSet, c.state.Filter()) newResultSet := c.tableService.Filter(resultSet, value) - return c.setResultSetAndFilter(newResultSet, value) + return c.setResultSetAndFilter(newResultSet, value, true) } }, } @@ -288,7 +288,7 @@ func (c *TableReadController) ViewBack() tea.Cmd { log.Printf("backstack: setting filter to '%v'", viewSnapshot.Filter) newResultSet := c.tableService.Filter(currentResultSet, viewSnapshot.Filter) - return c.setResultSetAndFilter(newResultSet, viewSnapshot.Filter) + return c.setResultSetAndFilter(newResultSet, viewSnapshot.Filter, false) } tableInfo := currentResultSet.TableInfo diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go index 61958a1..5f79d7d 100644 --- a/internal/dynamo-browse/controllers/tablewrite.go +++ b/internal/dynamo-browse/controllers/tablewrite.go @@ -400,7 +400,7 @@ func (twc *TableWriteController) NoisyTouchItem(idx int) tea.Cmd { return events.Error(err) } - return twc.tableReadControllers.doScan(ctx, resultSet, resultSet.Query) + return twc.tableReadControllers.doScan(ctx, resultSet, resultSet.Query, false) } }, } @@ -431,7 +431,7 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd { return events.Error(err) } - return twc.tableReadControllers.doScan(ctx, resultSet, resultSet.Query) + return twc.tableReadControllers.doScan(ctx, resultSet, resultSet.Query, false) } }, } diff --git a/internal/dynamo-browse/services/workspaces/service.go b/internal/dynamo-browse/services/workspaces/service.go index 540494a..34854dc 100644 --- a/internal/dynamo-browse/services/workspaces/service.go +++ b/internal/dynamo-browse/services/workspaces/service.go @@ -47,9 +47,8 @@ func (s *ViewSnapshotService) PopSnapshot() (*serialisable.ViewSnapshot, error) vs, err := s.store.Head() if err != nil { return nil, errors.Wrap(err, "cannot get snapshot head") - } - if vs == nil { - return vs, nil + } else if vs == nil || vs.BackLink == 0 { + return nil, nil } if err := s.store.SetAsHead(vs.BackLink); err != nil { @@ -59,5 +58,12 @@ func (s *ViewSnapshotService) PopSnapshot() (*serialisable.ViewSnapshot, error) return nil, errors.Wrap(err, "cannot remove old ID") } + vs, err = s.store.Head() + if err != nil { + return nil, errors.Wrap(err, "cannot get snapshot head") + } else if vs == nil || vs.BackLink == 0 { + return nil, nil + } + return vs, nil } From cc58db2d494e6228ab7df1a27a426f6fc0ef3791 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 14 Aug 2022 09:20:24 +1000 Subject: [PATCH 4/6] backstack: have added the workspace flag --- cmd/dynamo-browse/main.go | 14 ++++++-------- internal/common/workspaces/manager.go | 9 +++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index b2213d2..a5efea6 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -28,6 +28,7 @@ func main() { var flagTable = flag.String("t", "", "dynamodb table name") var flagLocal = flag.String("local", "", "local endpoint") var flagDebug = flag.String("debug", "", "file to log debug messages") + var flagWorkspace = flag.String("w", "", "workspace file") flag.Parse() ctx := context.Background() @@ -37,11 +38,11 @@ func main() { cli.Fatalf("cannot load AWS config: %v", err) } - wsManager := workspaces.New(workspaces.MetaInfo{ - Command: "sqs-browse", - }) - //ws, err := wsManager.CreateTemp() - ws, err := wsManager.Open("temp.workspace") + closeFn := logging.EnableLogging(*flagDebug) + defer closeFn() + + wsManager := workspaces.New(workspaces.MetaInfo{Command: "dynamo-browse"}) + ws, err := wsManager.OpenOrCreate(*flagWorkspace) if err != nil { cli.Fatalf("cannot create workspace: %v", ws) } @@ -83,9 +84,6 @@ func main() { p := tea.NewProgram(model, tea.WithAltScreen()) - closeFn := logging.EnableLogging(*flagDebug) - defer closeFn() - // Pre-determine if layout has dark background. This prevents calls for creating a list to hang. if lipgloss.HasDarkBackground() { if colorScheme := osstyle.CurrentColorScheme(); colorScheme == osstyle.ColorSchemeLightMode { diff --git a/internal/common/workspaces/manager.go b/internal/common/workspaces/manager.go index da08a4d..3a1834d 100644 --- a/internal/common/workspaces/manager.go +++ b/internal/common/workspaces/manager.go @@ -3,6 +3,7 @@ package workspaces import ( "github.com/asdine/storm" "github.com/pkg/errors" + "log" "os" ) @@ -18,11 +19,19 @@ func New(metaInfo MetaInfo) *Manager { return &Manager{metainfo: metaInfo} } +func (m *Manager) OpenOrCreate(filename string) (*Workspace, error) { + if filename == "" { + return m.CreateTemp() + } + return m.Open(filename) +} + func (m *Manager) Open(filename string) (*Workspace, error) { db, err := storm.Open(filename) if err != nil { return nil, errors.Wrapf(err, "cannot open workspace at %v", filename) } + log.Printf("open workspace: %v", filename) return &Workspace{db: db}, nil } From bdd97f1b41c56f0cbe27d4371cc568ecfa1023c2 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 14 Aug 2022 09:22:56 +1000 Subject: [PATCH 5/6] backstack: some code cleanup --- .../dynamo-browse/controllers/tableread.go | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index b994eb0..a5caa5a 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -22,8 +22,6 @@ type TableReadController struct { // state mutex *sync.Mutex state *State - //resultSet *models.ResultSet - //filter string } func NewTableReadController(state *State, tableService TableReadService, workspaceService *workspaces.ViewSnapshotService, tableName string) *TableReadController { @@ -88,34 +86,6 @@ func (c *TableReadController) PromptForQuery() tea.Cmd { return func() tea.Msg { return c.runQuery(c.state.ResultSet().TableInfo, value, "", true) } - - //if value == "" { - // return func() tea.Msg { - // resultSet := c.state.ResultSet() - // if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { - // log.Printf("cannot push snapshot: %v", err) - // } - // return c.doScan(context.Background(), resultSet, nil) - // } - //} - // - //expr, err := queryexpr.Parse(value) - //if err != nil { - // return events.SetError(err) - //} - // - //return c.doIfNoneDirty(func() tea.Msg { - // resultSet := c.state.ResultSet() - // newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, expr) - // if err != nil { - // return events.Error(err) - // } - // - // if err := c.workspaceService.PushSnapshot(resultSet, ""); err != nil { - // log.Printf("cannot push snapshot: %v", err) - // } - // return c.setResultSetAndFilter(newResultSet, "") - //}) }, } } From a1c03dcb634c8565734235fad3ad0ef0023dbecf Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 14 Aug 2022 09:46:19 +1000 Subject: [PATCH 6/6] backstack: fixed unit tests --- .../controllers/tableread_test.go | 47 ++++++++++++--- .../controllers/tablewrite_test.go | 57 +++++++++++++------ 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/internal/dynamo-browse/controllers/tableread_test.go b/internal/dynamo-browse/controllers/tableread_test.go index 3ac1794..e2f3329 100644 --- a/internal/dynamo-browse/controllers/tableread_test.go +++ b/internal/dynamo-browse/controllers/tableread_test.go @@ -4,9 +4,12 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/common/workspaces" "github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" "github.com/lmika/audax/internal/dynamo-browse/services/tables" + workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces" "github.com/lmika/audax/test/testdynamo" "github.com/stretchr/testify/assert" "os" @@ -17,11 +20,14 @@ import ( func TestTableReadController_InitTable(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) t.Run("should prompt for table if no table name provided", func(t *testing.T) { - readController := controllers.NewTableReadController(controllers.NewState(), service, "") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "") cmd := readController.Init() event := cmd() @@ -30,7 +36,7 @@ func TestTableReadController_InitTable(t *testing.T) { }) t.Run("should scan table if table name provided", func(t *testing.T) { - readController := controllers.NewTableReadController(controllers.NewState(), service, "") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "") cmd := readController.Init() event := cmd() @@ -42,9 +48,12 @@ func TestTableReadController_InitTable(t *testing.T) { func TestTableReadController_ListTables(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) - readController := controllers.NewTableReadController(controllers.NewState(), service, "") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "") t.Run("returns a list of tables", func(t *testing.T) { cmd := readController.ListTables() @@ -65,10 +74,13 @@ func TestTableReadController_ListTables(t *testing.T) { func TestTableReadController_Rescan(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "bravo-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "bravo-table") t.Run("should perform a rescan", func(t *testing.T) { invokeCommand(t, readController.Init()) @@ -99,9 +111,12 @@ func TestTableReadController_Rescan(t *testing.T) { func TestTableReadController_ExportCSV(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) - readController := controllers.NewTableReadController(controllers.NewState(), service, "bravo-table") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "bravo-table") t.Run("should export result set to CSV file", func(t *testing.T) { tempFile := tempFile(t) @@ -122,7 +137,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(controllers.NewState(), service, "non-existant-table") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "non-existant-table") invokeCommandExpectingError(t, readController.Init()) invokeCommandExpectingError(t, readController.ExportCSV(tempFile)) @@ -134,9 +149,12 @@ func TestTableReadController_ExportCSV(t *testing.T) { func TestTableReadController_Query(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) - readController := controllers.NewTableReadController(controllers.NewState(), service, "bravo-table") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "bravo-table") t.Run("should run scan with filter based on user query", func(t *testing.T) { tempFile := tempFile(t) @@ -156,7 +174,7 @@ func TestTableReadController_Query(t *testing.T) { t.Run("should return error if result set is not set", func(t *testing.T) { tempFile := tempFile(t) - readController := controllers.NewTableReadController(controllers.NewState(), service, "non-existant-table") + readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, "non-existant-table") invokeCommandExpectingError(t, readController.Init()) invokeCommandExpectingError(t, readController.ExportCSV(tempFile)) @@ -177,6 +195,19 @@ func tempFile(t *testing.T) string { return tempFile.Name() } +func testWorkspace(t *testing.T) *workspaces.Workspace { + wsTempFile := tempFile(t) + + wsManager := workspaces.New(workspaces.MetaInfo{Command: "dynamo-browse"}) + ws, err := wsManager.Open(wsTempFile) + if err != nil { + t.Fatalf("cannot create workspace manager: %v", err) + } + t.Cleanup(func() { ws.Close() }) + + return ws +} + func invokeCommand(t *testing.T, cmd tea.Cmd) tea.Msg { msg := cmd() diff --git a/internal/dynamo-browse/controllers/tablewrite_test.go b/internal/dynamo-browse/controllers/tablewrite_test.go index cf6f1e4..ff29b8c 100644 --- a/internal/dynamo-browse/controllers/tablewrite_test.go +++ b/internal/dynamo-browse/controllers/tablewrite_test.go @@ -6,13 +6,18 @@ import ( "github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" "github.com/lmika/audax/internal/dynamo-browse/services/tables" + workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces" "github.com/lmika/audax/test/testdynamo" "github.com/stretchr/testify/assert" "testing" ) func TestTableWriteController_NewItem(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) @@ -20,7 +25,7 @@ func TestTableWriteController_NewItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -43,6 +48,9 @@ func TestTableWriteController_NewItem(t *testing.T) { } func TestTableWriteController_SetAttributeValue(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should preserve the type of the field if unspecified", func(t *testing.T) { scenarios := []struct { @@ -80,7 +88,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -100,7 +108,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -154,7 +162,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) { for _, scenario := range scenarios { t.Run(fmt.Sprintf("should change the value of a field to type %v", scenario.attrType), func(t *testing.T) { state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -175,7 +183,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) { t.Run(fmt.Sprintf("should change value of nested field to type %v", scenario.attrType), func(t *testing.T) { state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -205,12 +213,15 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) { func TestTableWriteController_DeleteAttribute(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + provider := dynamo.NewProvider(client) service := tables.NewService(provider) t.Run("should delete top level attribute", func(t *testing.T) { state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -226,7 +237,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) { t.Run("should delete attribute of map", func(t *testing.T) { state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -247,6 +258,9 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) { } func TestTableWriteController_PutItem(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should put the selected item if dirty", func(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) @@ -254,7 +268,7 @@ func TestTableWriteController_PutItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -281,7 +295,7 @@ func TestTableWriteController_PutItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -312,7 +326,7 @@ func TestTableWriteController_PutItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -326,6 +340,9 @@ func TestTableWriteController_PutItem(t *testing.T) { } func TestTableWriteController_PutItems(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should put all dirty items if none are marked", func(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) @@ -333,7 +350,7 @@ func TestTableWriteController_PutItems(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -361,7 +378,7 @@ func TestTableWriteController_PutItems(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -397,7 +414,7 @@ func TestTableWriteController_PutItems(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) invokeCommand(t, readController.Init()) @@ -428,6 +445,9 @@ func TestTableWriteController_PutItems(t *testing.T) { } func TestTableWriteController_TouchItem(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should put the selected item if unmodified", func(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) @@ -435,7 +455,7 @@ func TestTableWriteController_TouchItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -461,7 +481,7 @@ func TestTableWriteController_TouchItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -477,6 +497,9 @@ func TestTableWriteController_TouchItem(t *testing.T) { } func TestTableWriteController_NoisyTouchItem(t *testing.T) { + resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t)) + workspaceService := workspaces_service.NewService(resultSetSnapshotStore) + t.Run("should delete and put the selected item if unmodified", func(t *testing.T) { client := testdynamo.SetupTestTable(t, testData) @@ -484,7 +507,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table @@ -510,7 +533,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) { service := tables.NewService(provider) state := controllers.NewState() - readController := controllers.NewTableReadController(state, service, "alpha-table") + readController := controllers.NewTableReadController(state, service, workspaceService, "alpha-table") writeController := controllers.NewTableWriteController(state, service, readController) // Read the table