Fixed some small paper-cuts

- Fixed a bug that was pushing duplicate view entries to the backstack
- The appended column will now be selected once added
This commit is contained in:
Leon Mika 2022-10-16 09:50:27 +11:00
parent b51c13dfb1
commit bfd0943c4f
14 changed files with 155 additions and 46 deletions

View file

@ -19,7 +19,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/services/jobs" "github.com/lmika/audax/internal/dynamo-browse/services/jobs"
keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings" keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
"github.com/lmika/audax/internal/dynamo-browse/services/tables" "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/services/viewsnapshot"
"github.com/lmika/audax/internal/dynamo-browse/ui" "github.com/lmika/audax/internal/dynamo-browse/ui"
"github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles"
@ -93,7 +93,7 @@ func main() {
} }
tableService := tables.NewService(dynamoProvider, settingStore) tableService := tables.NewService(dynamoProvider, settingStore)
workspaceService := workspaces_service.NewService(resultSetSnapshotStore) workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo) itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
jobsService := jobs.NewService(eventBus) jobsService := jobs.NewService(eventBus)

View file

@ -90,7 +90,10 @@ func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg {
cc.colModel.Columns = newCols cc.colModel.Columns = newCols
} }
return ColumnsUpdated{} return tea.Batch(
events.SetTeaMessage(ColumnsUpdated{}),
events.SetTeaMessage(SetSelectedColumnInColSelector(afterIndex+1)),
)()
}) })
} }

View file

@ -16,6 +16,8 @@ type SettingsUpdated struct {
type ColumnsUpdated struct { type ColumnsUpdated struct {
} }
type SetSelectedColumnInColSelector int
type MoveLeftmostDisplayedColumnInTableViewBy int type MoveLeftmostDisplayedColumnInTableViewBy int
type NewResultSet struct { type NewResultSet struct {

View file

@ -9,7 +9,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr"
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable" "github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/workspaces" "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot"
bus "github.com/lmika/events" bus "github.com/lmika/events"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.design/x/clipboard" "golang.design/x/clipboard"
@ -39,7 +39,7 @@ const (
type TableReadController struct { type TableReadController struct {
tableService TableReadService tableService TableReadService
workspaceService *workspaces.ViewSnapshotService workspaceService *viewsnapshot.ViewSnapshotService
itemRendererService *itemrenderer.Service itemRendererService *itemrenderer.Service
jobController *JobsController jobController *JobsController
eventBus *bus.Bus eventBus *bus.Bus
@ -55,7 +55,7 @@ type TableReadController struct {
func NewTableReadController( func NewTableReadController(
state *State, state *State,
tableService TableReadService, tableService TableReadService,
workspaceService *workspaces.ViewSnapshotService, workspaceService *viewsnapshot.ViewSnapshotService,
itemRendererService *itemrenderer.Service, itemRendererService *itemrenderer.Service,
jobController *JobsController, jobController *JobsController,
eventBus *bus.Bus, eventBus *bus.Bus,
@ -211,8 +211,16 @@ func (c *TableReadController) doScan(resultSet *models.ResultSet, query models.Q
} }
func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string, pushBackstack bool, op resultSetUpdateOp) tea.Msg { func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string, pushBackstack bool, op resultSetUpdateOp) tea.Msg {
if pushBackstack { if resultSet != nil && pushBackstack {
if err := c.workspaceService.PushSnapshot(resultSet, filter); err != nil { details := serialisable.ViewSnapshotDetails{
TableName: resultSet.TableInfo.Name,
Filter: filter,
}
if q := resultSet.Query; q != nil {
details.Query = q.String()
}
if err := c.workspaceService.PushSnapshot(details); err != nil {
log.Printf("cannot push snapshot: %v", err) log.Printf("cannot push snapshot: %v", err)
} }
} }
@ -308,13 +316,13 @@ func (c *TableReadController) updateViewToSnapshot(viewSnapshot *serialisable.Vi
if currentResultSet == nil { if currentResultSet == nil {
return NewJob(c.jobController, "Fetching table info…", func(ctx context.Context) (*models.TableInfo, error) { return NewJob(c.jobController, "Fetching table info…", func(ctx context.Context) (*models.TableInfo, error) {
tableInfo, err := c.tableService.Describe(context.Background(), viewSnapshot.TableName) tableInfo, err := c.tableService.Describe(context.Background(), viewSnapshot.Details.TableName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return tableInfo, nil return tableInfo, nil
}).OnDone(func(tableInfo *models.TableInfo) tea.Msg { }).OnDone(func(tableInfo *models.TableInfo) tea.Msg {
return c.runQuery(tableInfo, viewSnapshot.Query, viewSnapshot.Filter, false) return c.runQuery(tableInfo, viewSnapshot.Details.Query, viewSnapshot.Details.Filter, false)
}).Submit() }).Submit()
} }
@ -323,22 +331,22 @@ func (c *TableReadController) updateViewToSnapshot(viewSnapshot *serialisable.Vi
currentQueryExpr = currentResultSet.Query.String() currentQueryExpr = currentResultSet.Query.String()
} }
if viewSnapshot.TableName == currentResultSet.TableInfo.Name && viewSnapshot.Query == currentQueryExpr { if viewSnapshot.Details.TableName == currentResultSet.TableInfo.Name && viewSnapshot.Details.Query == currentQueryExpr {
return NewJob(c.jobController, "Applying filter…", func(ctx context.Context) (*models.ResultSet, error) { return NewJob(c.jobController, "Applying filter…", func(ctx context.Context) (*models.ResultSet, error) {
return c.tableService.Filter(currentResultSet, viewSnapshot.Filter), nil return c.tableService.Filter(currentResultSet, viewSnapshot.Details.Filter), nil
}).OnEither(c.handleResultSetFromJobResult(viewSnapshot.Filter, false, resultSetUpdateSnapshotRestore)).Submit() }).OnEither(c.handleResultSetFromJobResult(viewSnapshot.Details.Filter, false, resultSetUpdateSnapshotRestore)).Submit()
} }
return NewJob(c.jobController, "Running query…", func(ctx context.Context) (tea.Msg, error) { return NewJob(c.jobController, "Running query…", func(ctx context.Context) (tea.Msg, error) {
tableInfo := currentResultSet.TableInfo tableInfo := currentResultSet.TableInfo
if viewSnapshot.TableName != currentResultSet.TableInfo.Name { if viewSnapshot.Details.TableName != currentResultSet.TableInfo.Name {
tableInfo, err = c.tableService.Describe(context.Background(), viewSnapshot.TableName) tableInfo, err = c.tableService.Describe(context.Background(), viewSnapshot.Details.TableName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return c.runQuery(tableInfo, viewSnapshot.Query, viewSnapshot.Filter, false), nil return c.runQuery(tableInfo, viewSnapshot.Details.Query, viewSnapshot.Details.Filter, false), nil
}).OnDone(func(m tea.Msg) tea.Msg { }).OnDone(func(m tea.Msg) tea.Msg {
return m return m
}).Submit() }).Submit()

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/common/ui/events" "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/controllers"
"github.com/lmika/audax/test/testdynamo" "github.com/lmika/audax/test/testdynamo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -124,19 +123,6 @@ func tempFile(t *testing.T) string {
return tempFile.Name() 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, msg tea.Msg) tea.Msg { func invokeCommand(t *testing.T, msg tea.Msg) tea.Msg {
err, isErr := msg.(events.ErrorMsg) err, isErr := msg.(events.ErrorMsg)
if isErr { if isErr {

View file

@ -13,6 +13,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/services/tables" "github.com/lmika/audax/internal/dynamo-browse/services/tables"
workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces" workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces"
"github.com/lmika/audax/test/testdynamo" "github.com/lmika/audax/test/testdynamo"
"github.com/lmika/audax/test/testworkspace"
bus "github.com/lmika/events" bus "github.com/lmika/events"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
@ -583,7 +584,7 @@ type serviceConfig struct {
} }
func newService(t *testing.T, cfg serviceConfig) *services { func newService(t *testing.T, cfg serviceConfig) *services {
ws := testWorkspace(t) ws := testworkspace.New(t)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws) resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
settingStore := settingstore.New(ws) settingStore := settingstore.New(ws)

View file

@ -5,10 +5,14 @@ import (
) )
type ViewSnapshot struct { type ViewSnapshot struct {
ID int64 `storm:"id,increment"` ID int64 `storm:"id,increment"`
BackLink int64 `storm:"index"` BackLink int64 `storm:"index"`
ForeLink int64 `storm:"index"` ForeLink int64 `storm:"index"`
Time time.Time Time time.Time
Details ViewSnapshotDetails
}
type ViewSnapshotDetails struct {
TableName string TableName string
Query string Query string
Filter string Filter string

View file

@ -24,7 +24,6 @@ func (s *ResultSetSnapshotStore) Save(rs *serialisable.ViewSnapshot) error {
if err := s.ws.Save(rs); err != nil { if err := s.ws.Save(rs); err != nil {
return errors.Wrap(err, "cannot save result set") return errors.Wrap(err, "cannot save result set")
} }
log.Printf("saved result set: table='%v', query='%v', filter='%v'", rs.TableName, rs.Query, rs.Filter)
return nil return nil
} }
@ -142,3 +141,7 @@ func (s *ResultSetSnapshotStore) Remove(resultSetId int64) error {
} }
return nil return nil
} }
func (s *ResultSetSnapshotStore) Len() (int, error) {
return s.ws.Count(&serialisable.ViewSnapshot{})
}

View file

@ -1,4 +1,4 @@
package workspaces package viewsnapshot
import "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" import "github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
@ -8,6 +8,7 @@ type ViewSnapshotStore interface {
CurrentlyViewedSnapshot() (*serialisable.ViewSnapshot, error) CurrentlyViewedSnapshot() (*serialisable.ViewSnapshot, error)
SetCurrentlyViewedSnapshot(resultSetId int64) error SetCurrentlyViewedSnapshot(resultSetId int64) error
Find(resultSetID int64) (*serialisable.ViewSnapshot, error) Find(resultSetID int64) (*serialisable.ViewSnapshot, error)
Len() (int, error)
Head() (*serialisable.ViewSnapshot, error) Head() (*serialisable.ViewSnapshot, error)
Remove(resultSetId int64) error Remove(resultSetId int64) error
Dehead(fromNode *serialisable.ViewSnapshot) error Dehead(fromNode *serialisable.ViewSnapshot) error

View file

@ -1,7 +1,6 @@
package workspaces package viewsnapshot
import ( import (
"github.com/lmika/audax/internal/dynamo-browse/models"
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable" "github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
"github.com/pkg/errors" "github.com/pkg/errors"
"time" "time"
@ -17,21 +16,22 @@ func NewService(store ViewSnapshotStore) *ViewSnapshotService {
} }
} }
func (s *ViewSnapshotService) PushSnapshot(rs *models.ResultSet, filter string) error { func (s *ViewSnapshotService) PushSnapshot(details serialisable.ViewSnapshotDetails) error {
newSnapshot := &serialisable.ViewSnapshot{ newSnapshot := &serialisable.ViewSnapshot{
Time: time.Now(), Time: time.Now(),
TableName: rs.TableInfo.Name, Details: details,
} }
if q := rs.Query; q != nil {
newSnapshot.Query = q.String()
}
newSnapshot.Filter = filter
oldHead, err := s.store.CurrentlyViewedSnapshot() oldHead, err := s.store.CurrentlyViewedSnapshot()
if err != nil { if err != nil {
return errors.Wrap(err, "cannot get snapshot head") return errors.Wrap(err, "cannot get snapshot head")
} }
if oldHead != nil && oldHead.Details == details {
// Attempting to push a duplicate
return nil
}
if oldHead != nil { if oldHead != nil {
newSnapshot.BackLink = oldHead.ID newSnapshot.BackLink = oldHead.ID
@ -62,6 +62,10 @@ func (s *ViewSnapshotService) PushSnapshot(rs *models.ResultSet, filter string)
return nil return nil
} }
func (s *ViewSnapshotService) Len() (int, error) {
return s.store.Len()
}
func (s *ViewSnapshotService) ViewRestore() (*serialisable.ViewSnapshot, error) { func (s *ViewSnapshotService) ViewRestore() (*serialisable.ViewSnapshot, error) {
vs, err := s.store.CurrentlyViewedSnapshot() vs, err := s.store.CurrentlyViewedSnapshot()
if err != nil { if err != nil {

View file

@ -0,0 +1,53 @@
package viewsnapshot_test
import (
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot"
"github.com/lmika/audax/test/testworkspace"
"github.com/stretchr/testify/assert"
"testing"
)
func TestViewSnapshotService_PushSnapshot(t *testing.T) {
t.Run("should not push duplicate snapshots", func(t *testing.T) {
ws := testworkspace.New(t)
service := viewsnapshot.NewService(workspacestore.NewResultSetSnapshotStore(ws))
// Push some snapshots
err := service.PushSnapshot(serialisable.ViewSnapshotDetails{
TableName: "normal-table",
Query: "pk = 'abc'",
Filter: "",
})
assert.NoError(t, err)
cnt, err := service.Len()
assert.NoError(t, err)
assert.Equal(t, 1, cnt)
err = service.PushSnapshot(serialisable.ViewSnapshotDetails{
TableName: "abnormal-table",
Query: "pk = 'abc'",
Filter: "fla",
})
assert.NoError(t, err)
cnt, err = service.Len()
assert.NoError(t, err)
assert.Equal(t, 2, cnt)
// Push a duplicate
err = service.PushSnapshot(serialisable.ViewSnapshotDetails{
TableName: "abnormal-table",
Query: "pk = 'abc'",
Filter: "fla",
})
assert.NoError(t, err)
cnt, err = service.Len()
assert.NoError(t, err)
assert.Equal(t, 2, cnt)
})
}

View file

@ -10,6 +10,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
table "github.com/lmika/go-bubble-table" table "github.com/lmika/go-bubble-table"
"log"
"strings" "strings"
) )
@ -46,6 +47,12 @@ func (c *colListModel) Init() tea.Cmd {
func (m *colListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *colListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case controllers.SetSelectedColumnInColSelector:
// HACK: this needs to work for all cases
log.Printf("%d == %d?", int(msg), m.table.Cursor()+1)
if int(msg) == m.table.Cursor()+1 {
m.table.GoDown()
}
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
// Column operations // Column operations

View file

@ -49,6 +49,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case controllers.ColumnsUpdated: case controllers.ColumnsUpdated:
m.colListModel.refreshTable() m.colListModel.refreshTable()
m.subModel = cc.Collect(m.subModel.Update(msg)).(tea.Model) m.subModel = cc.Collect(m.subModel.Update(msg)).(tea.Model)
case controllers.SetSelectedColumnInColSelector:
m.compositor = cc.Collect(m.compositor.Update(msg)).(*layout.Compositor)
case tea.KeyMsg: case tea.KeyMsg:
m.compositor = cc.Collect(m.compositor.Update(msg)).(*layout.Compositor) m.compositor = cc.Collect(m.compositor.Update(msg)).(*layout.Compositor)
default: default:

View file

@ -0,0 +1,35 @@
package testworkspace
import (
"github.com/lmika/audax/internal/common/workspaces"
"github.com/stretchr/testify/assert"
"os"
"testing"
)
func New(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 tempFile(t *testing.T) string {
t.Helper()
tempFile, err := os.CreateTemp("", "export.csv")
assert.NoError(t, err)
tempFile.Close()
t.Cleanup(func() {
os.Remove(tempFile.Name())
})
return tempFile.Name()
}