Merge pull request #4 from lmika/feature/put-items

Added some basic editing functionality
This commit is contained in:
Leon Mika 2022-06-11 12:00:49 +10:00 committed by GitHub
commit 69653b7baf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1273 additions and 172 deletions

View file

@ -16,7 +16,7 @@ jobs:
postgres:
image: amazon/dynamodb-local:latest
ports:
- 8000:8000
- 18000:8000
steps:
- name: Checkout
uses: actions/checkout@v2

52
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,52 @@
name: release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: amazon/dynamodb-local:latest
ports:
- 18000:8000
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Configure
run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
- name: Test
run: |
set -xue
go get ./...
go test ./...
env:
GOPRIVATE: "github:com/lmika/*"
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Configure
run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
- name: Release
uses: goreleaser/goreleaser-action@v1
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest
args: release --skip-validate --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

54
.goreleaser.yml Normal file
View file

@ -0,0 +1,54 @@
builds:
- id: dynamo-browse
targets:
- windows_amd64
- linux_amd64
- darwin_amd64
- darwin_arm64
main: ./cmd/dynamo-browse/.
binary: dynamo-browse
archives:
- id: zip
builds:
- dynamo-browse
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
- goos: linux
format: tar.gz
- goos: macos
format: tar.gz
nfpms:
- id: package_nfpms
package_name: awstools
builds:
- dynamo-browse
vendor: lmika
homepage: https://awstools.lmika.dev/
maintainer: Leon Mika <lmika@lmika.org>
description: TUI tools for AWS administration
license: MIT
formats:
- deb
- rpm
bindir: /usr/local/bin
brews:
- name: awstools
tap:
owner: lmika
name: awstools
folder: Formula
homepage: https://awstools.lmika.dev
description: TUI tools for AWS administration
license: MIT
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

View file

@ -43,8 +43,9 @@ func main() {
tableService := tables.NewService(dynamoProvider)
tableReadController := controllers.NewTableReadController(tableService, *flagTable)
tableWriteController := controllers.NewTableWriteController(tableService, tableReadController)
state := controllers.NewState()
tableReadController := controllers.NewTableReadController(state, tableService, *flagTable)
tableWriteController := controllers.NewTableWriteController(state, tableService, tableReadController)
commandController := commandctrl.NewCommandController()
model := ui.NewModel(tableReadController, tableWriteController, commandController)

15
go.mod
View file

@ -12,9 +12,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.0
github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0
github.com/brianvoe/gofakeit/v6 v6.15.0
github.com/calyptia/go-bubble-table v0.1.0
github.com/charmbracelet/bubbles v0.10.3
github.com/charmbracelet/bubbletea v0.20.0
github.com/charmbracelet/bubbles v0.11.0
github.com/charmbracelet/bubbletea v0.21.0
github.com/charmbracelet/lipgloss v0.5.0
github.com/google/uuid v1.3.0
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e
@ -42,18 +41,20 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect
github.com/lmika/go-bubble-table v0.2.2-0.20220608033210-61eeb29a6239 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
github.com/muesli/cancelreader v0.2.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect
github.com/muesli/termenv v0.12.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

22
go.sum
View file

@ -62,12 +62,19 @@ github.com/brianvoe/gofakeit/v6 v6.15.0 h1:lJPGJZ2/07TRGDazyTzD5b18N3y4tmmJpdhCU
github.com/brianvoe/gofakeit/v6 v6.15.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/calyptia/go-bubble-table v0.1.0 h1:mXpaaBlrHGH4K8v5PvM8YqBFT9jlysS1YOycU2u3gEQ=
github.com/calyptia/go-bubble-table v0.1.0/go.mod h1:2nnweuFos+eEIIbgweXvZuX+ROOatsMwB3NHnX/vTC4=
github.com/calyptia/go-bubble-table v0.2.1 h1:NWcVRyGCLuP7QIA29uUFSY+IjmWcmUWHjy5J/CPb0Rk=
github.com/calyptia/go-bubble-table v0.2.1/go.mod h1:gJvzUOUzfQeA9JmgLumyJYWJMtuRQ7WxxTwc9tjEiGw=
github.com/charmbracelet/bubbles v0.10.3 h1:fKarbRaObLn/DCsZO4Y3vKCwRUzynQD9L+gGev1E/ho=
github.com/charmbracelet/bubbles v0.10.3/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA=
github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q=
github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
github.com/charmbracelet/bubbletea v0.20.0 h1:/b8LEPgCbNr7WWZ2LuE/BV1/r4t5PyYJtDb+J3vpwxc=
github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM=
github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
@ -93,6 +100,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e h1:0QkUe2ejnT/i+xbgGylMU1b+XnZponQKiPVNi+C/xgA=
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e/go.mod h1:qtkBmNC9OfD0STtOR9sF55pQchjIfNlC3gzm4n8CrqM=
github.com/lmika/go-bubble-table v0.2.2-0.20220608033210-61eeb29a6239 h1:GGw5pZtEFnHtD7kKdWsiwgcIwZTnok60sShrHVYz4ok=
github.com/lmika/go-bubble-table v0.2.2-0.20220608033210-61eeb29a6239/go.mod h1:0RT1upgKZ6qZ6B1SqseE3wWsPjSQRv/G/HjpYK8jNsg=
github.com/lmika/gopkgs v0.0.0-20211210041137-0dc91e939890 h1:mwl/exYV/WkBMeShqK7q+B2w2r+b0vP1TSA7clBn9kI=
github.com/lmika/gopkgs v0.0.0-20211210041137-0dc91e939890/go.mod h1:FH6OJSvYcJ9xY8CGs9yGgR89kMCK1UimuUQ6kE5YuJQ=
github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe h1:1UXS/6OFkbi6JrihPykmYO1VtsABB02QQ+YmYYzTY18=
@ -112,6 +121,10 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
@ -119,6 +132,8 @@ github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtl
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc=
github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -140,11 +155,18 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -0,0 +1,25 @@
package controllers
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/events"
)
type promptSequence struct {
prompts []string
receivedValues []string
onAllDone func(values []string) tea.Msg
}
func (ps *promptSequence) next() tea.Msg {
if len(ps.receivedValues) < len(ps.prompts) {
return events.PromptForInputMsg{
Prompt: ps.prompts[len(ps.receivedValues)],
OnDone: func(value string) tea.Cmd {
ps.receivedValues = append(ps.receivedValues, value)
return ps.next
},
}
}
return ps.onAllDone(ps.receivedValues)
}

View file

@ -1,28 +1,46 @@
package controllers
import (
"context"
"sync"
"github.com/lmika/awstools/internal/dynamo-browse/models"
)
type State struct {
ResultSet *models.ResultSet
SelectedItem models.Item
// InReadWriteMode indicates whether modifications can be made to the table
InReadWriteMode bool
mutex *sync.Mutex
resultSet *models.ResultSet
filter string
}
type stateContextKeyType struct{}
var stateContextKey = stateContextKeyType{}
func CurrentState(ctx context.Context) State {
state, _ := ctx.Value(stateContextKey).(State)
return state
func NewState() *State {
return &State{
mutex: new(sync.Mutex),
}
}
func ContextWithState(ctx context.Context, state State) context.Context {
return context.WithValue(ctx, stateContextKey, state)
func (s *State) ResultSet() *models.ResultSet {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.resultSet
}
func (s *State) Filter() string {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.filter
}
func (s *State) withResultSet(rs func(*models.ResultSet)) {
s.mutex.Lock()
defer s.mutex.Unlock()
rs(s.resultSet)
}
func (s *State) setResultSetAndFilter(resultSet *models.ResultSet, filter string) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.resultSet = resultSet
s.filter = filter
}

View file

@ -16,13 +16,15 @@ type TableReadController struct {
tableName string
// state
mutex *sync.Mutex
resultSet *models.ResultSet
filter string
mutex *sync.Mutex
state *State
//resultSet *models.ResultSet
//filter string
}
func NewTableReadController(tableService TableReadService, tableName string) *TableReadController {
func NewTableReadController(state *State, tableService TableReadService, tableName string) *TableReadController {
return &TableReadController{
state: state,
tableService: tableService,
tableName: tableName,
mutex: new(sync.Mutex),
@ -68,19 +70,19 @@ func (c *TableReadController) ScanTable(name string) tea.Cmd {
return events.Error(err)
}
return c.setResultSetAndFilter(resultSet, c.filter)
return c.setResultSetAndFilter(resultSet, c.state.Filter())
}
}
func (c *TableReadController) Rescan() tea.Cmd {
return func() tea.Msg {
return c.doScan(context.Background(), c.resultSet)
return c.doScan(context.Background(), c.state.ResultSet())
}
}
func (c *TableReadController) ExportCSV(filename string) tea.Cmd {
return func() tea.Msg {
resultSet := c.resultSet
resultSet := c.state.ResultSet()
if resultSet == nil {
return events.Error(errors.New("no result set"))
}
@ -119,39 +121,30 @@ func (c *TableReadController) doScan(ctx context.Context, resultSet *models.Resu
return events.Error(err)
}
newResultSet = c.tableService.Filter(newResultSet, c.filter)
newResultSet = c.tableService.Filter(newResultSet, c.state.Filter())
return c.setResultSetAndFilter(newResultSet, c.filter)
return c.setResultSetAndFilter(newResultSet, c.state.Filter())
}
func (c *TableReadController) ResultSet() *models.ResultSet {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.resultSet
}
//func (c *TableReadController) ResultSet() *models.ResultSet {
// c.mutex.Lock()
// defer c.mutex.Unlock()
//
// return c.resultSet
//}
func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, filter string) tea.Msg {
c.mutex.Lock()
defer c.mutex.Unlock()
c.resultSet = resultSet
c.filter = filter
c.state.setResultSetAndFilter(resultSet, filter)
return NewResultSet{resultSet}
}
func (c *TableReadController) Unmark() tea.Cmd {
return func() tea.Msg {
resultSet := c.ResultSet()
for i := range resultSet.Items() {
resultSet.SetMark(i, false)
}
c.mutex.Lock()
defer c.mutex.Unlock()
c.resultSet = resultSet
c.state.withResultSet(func(resultSet *models.ResultSet) {
for i := range resultSet.Items() {
resultSet.SetMark(i, false)
}
})
return ResultSetUpdated{}
}
}
@ -162,7 +155,7 @@ func (c *TableReadController) Filter() tea.Cmd {
Prompt: "filter: ",
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
resultSet := c.ResultSet()
resultSet := c.state.ResultSet()
newResultSet := c.tableService.Filter(resultSet, value)
return c.setResultSetAndFilter(newResultSet, value)

View file

@ -22,7 +22,7 @@ func TestTableReadController_InitTable(t *testing.T) {
service := tables.NewService(provider)
t.Run("should prompt for table if no table name provided", func(t *testing.T) {
readController := controllers.NewTableReadController(service, "")
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
cmd := readController.Init()
event := cmd()
@ -31,7 +31,7 @@ func TestTableReadController_InitTable(t *testing.T) {
})
t.Run("should scan table if table name provided", func(t *testing.T) {
readController := controllers.NewTableReadController(service, "")
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
cmd := readController.Init()
event := cmd()
@ -46,7 +46,7 @@ func TestTableReadController_ListTables(t *testing.T) {
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
readController := controllers.NewTableReadController(service, "")
readController := controllers.NewTableReadController(controllers.NewState(), service, "")
t.Run("returns a list of tables", func(t *testing.T) {
cmd := readController.ListTables()
@ -70,7 +70,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
readController := controllers.NewTableReadController(service, "alpha-table")
readController := controllers.NewTableReadController(controllers.NewState(), service, "alpha-table")
t.Run("should export result set to CSV file", func(t *testing.T) {
tempFile := tempFile(t)
@ -91,7 +91,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(service, "non-existant-table")
readController := controllers.NewTableReadController(controllers.NewState(), service, "non-existant-table")
invokeCommandExpectingError(t, readController.Init())
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
@ -114,13 +114,54 @@ func tempFile(t *testing.T) string {
return tempFile.Name()
}
func invokeCommand(t *testing.T, cmd tea.Cmd) {
func invokeCommand(t *testing.T, cmd tea.Cmd) tea.Msg {
msg := cmd()
err, isErr := msg.(events.ErrorMsg)
if isErr {
assert.Fail(t, fmt.Sprintf("expected no error but got one: %v", err))
}
return msg
}
func invokeCommandWithPrompt(t *testing.T, cmd tea.Cmd, promptValue string) {
msg := cmd()
pi, isPi := msg.(events.PromptForInputMsg)
if !isPi {
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
}
invokeCommand(t, pi.OnDone(promptValue))
}
func invokeCommandWithPrompts(t *testing.T, cmd tea.Cmd, promptValues ...string) {
msg := cmd()
for _, promptValue := range promptValues {
pi, isPi := msg.(events.PromptForInputMsg)
if !isPi {
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
}
msg = invokeCommand(t, pi.OnDone(promptValue))
}
}
func invokeCommandWithPromptsExpectingError(t *testing.T, cmd tea.Cmd, promptValues ...string) {
msg := cmd()
for _, promptValue := range promptValues {
pi, isPi := msg.(events.PromptForInputMsg)
if !isPi {
assert.Fail(t, fmt.Sprintf("expected prompt for input but didn't get one"))
}
msg = invokeCommand(t, pi.OnDone(promptValue))
}
_, isErr := msg.(events.ErrorMsg)
assert.True(t, isErr)
}
func invokeCommandExpectingError(t *testing.T, cmd tea.Cmd) {

View file

@ -3,18 +3,23 @@ package controllers
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
tea "github.com/charmbracelet/bubbletea"
"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"
"github.com/pkg/errors"
)
type TableWriteController struct {
state *State
tableService *tables.Service
tableReadControllers *TableReadController
}
func NewTableWriteController(tableService *tables.Service, tableReadControllers *TableReadController) *TableWriteController {
func NewTableWriteController(state *State, tableService *tables.Service, tableReadControllers *TableReadController) *TableWriteController {
return &TableWriteController{
state: state,
tableService: tableService,
tableReadControllers: tableReadControllers,
}
@ -22,16 +27,150 @@ func NewTableWriteController(tableService *tables.Service, tableReadControllers
func (twc *TableWriteController) ToggleMark(idx int) tea.Cmd {
return func() tea.Msg {
resultSet := twc.tableReadControllers.ResultSet()
resultSet.SetMark(idx, !resultSet.Marked(idx))
twc.state.withResultSet(func(resultSet *models.ResultSet) {
resultSet.SetMark(idx, !resultSet.Marked(idx))
})
return ResultSetUpdated{}
}
}
func (twc *TableWriteController) NewItem() tea.Cmd {
return func() tea.Msg {
// Work out which keys we need to prompt for
rs := twc.state.ResultSet()
keyPrompts := &promptSequence{
prompts: []string{rs.TableInfo.Keys.PartitionKey + ": "},
}
if rs.TableInfo.Keys.SortKey != "" {
keyPrompts.prompts = append(keyPrompts.prompts, rs.TableInfo.Keys.SortKey+": ")
}
keyPrompts.onAllDone = func(values []string) tea.Msg {
twc.state.withResultSet(func(set *models.ResultSet) {
newItem := models.Item{}
// TODO: deal with keys of different type
newItem[rs.TableInfo.Keys.PartitionKey] = &types.AttributeValueMemberS{Value: values[0]}
if len(values) == 2 {
newItem[rs.TableInfo.Keys.SortKey] = &types.AttributeValueMemberS{Value: values[1]}
}
set.AddNewItem(newItem, models.ItemAttribute{
New: true,
Dirty: true,
})
})
return NewResultSet{twc.state.ResultSet()}
}
return keyPrompts.next()
}
}
func (twc *TableWriteController) SetStringValue(idx int, key string) tea.Cmd {
return func() tea.Msg {
return events.PromptForInputMsg{
Prompt: "string value: ",
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
twc.state.withResultSet(func(set *models.ResultSet) {
set.Items()[idx][key] = &types.AttributeValueMemberS{Value: value}
set.SetDirty(idx, true)
})
return ResultSetUpdated{}
}
},
}
}
}
func (twc *TableWriteController) PutItem(idx int) tea.Cmd {
return func() tea.Msg {
resultSet := twc.state.ResultSet()
if !resultSet.IsDirty(idx) {
return events.Error(errors.New("item is not dirty"))
}
return events.PromptForInputMsg{
Prompt: "put item? ",
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
if value != "y" {
return nil
}
if err := twc.tableService.PutItemAt(context.Background(), resultSet, idx); err != nil {
return events.Error(err)
}
return ResultSetUpdated{}
}
},
}
}
}
func (twc *TableWriteController) TouchItem(idx int) tea.Cmd {
return func() tea.Msg {
resultSet := twc.state.ResultSet()
if resultSet.IsDirty(idx) {
return events.Error(errors.New("cannot touch dirty items"))
}
return events.PromptForInputMsg{
Prompt: "touch item? ",
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
if value != "y" {
return nil
}
if err := twc.tableService.PutItemAt(context.Background(), resultSet, idx); err != nil {
return events.Error(err)
}
return ResultSetUpdated{}
}
},
}
}
}
func (twc *TableWriteController) NoisyTouchItem(idx int) tea.Cmd {
return func() tea.Msg {
resultSet := twc.state.ResultSet()
if resultSet.IsDirty(idx) {
return events.Error(errors.New("cannot noisy touch dirty items"))
}
return events.PromptForInputMsg{
Prompt: "noisy touch item? ",
OnDone: func(value string) tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
if value != "y" {
return nil
}
item := resultSet.Items()[0]
if err := twc.tableService.Delete(ctx, resultSet.TableInfo, []models.Item{item}); err != nil {
return events.Error(err)
}
if err := twc.tableService.Put(ctx, resultSet.TableInfo, item); err != nil {
return events.Error(err)
}
return twc.tableReadControllers.doScan(ctx, resultSet)
}
},
}
}
}
func (twc *TableWriteController) DeleteMarked() tea.Cmd {
return func() tea.Msg {
resultSet := twc.tableReadControllers.ResultSet()
resultSet := twc.state.ResultSet()
markedItems := resultSet.MarkedItems()
if len(markedItems) == 0 {

View file

@ -1,6 +1,11 @@
package controllers_test
import (
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/lmika/awstools/test/testdynamo"
"github.com/stretchr/testify/assert"
"testing"
)
@ -173,24 +178,248 @@ func setupController(t *testing.T) (*controllers.TableWriteController, controlle
tableService: tableService,
}, cleanupFn
}
var testData = testdynamo.TestData{
{
"pk": "abc",
"sk": "222",
"alpha": "This is another some value",
"beta": 1231,
},
{
"pk": "abc",
"sk": "111",
"alpha": "This is some value",
},
{
"pk": "bbb",
"sk": "131",
"beta": 2468,
"gamma": "foobar",
},
}
*/
func TestTableWriteController_NewItem(t *testing.T) {
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
assert.Len(t, state.ResultSet().Items(), 3)
// Prompt for keys
invokeCommandWithPrompts(t, writeController.NewItem(), "pk-value", "sk-value")
newResultSet := state.ResultSet()
assert.Len(t, newResultSet.Items(), 4)
assert.Len(t, newResultSet.Items()[3], 2)
pk, _ := newResultSet.Items()[3].AttributeValueAsString("pk")
sk, _ := newResultSet.Items()[3].AttributeValueAsString("sk")
assert.Equal(t, "pk-value", pk)
assert.Equal(t, "sk-value", sk)
assert.True(t, newResultSet.IsNew(3))
assert.True(t, newResultSet.IsDirty(3))
})
}
func TestTableWriteController_SetStringValue(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
t.Run("should add a new empty item at the end of the result set", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", after)
assert.True(t, state.ResultSet().IsDirty(0))
})
t.Run("should prevent duplicate partition,sort keys", func(t *testing.T) {
t.Skip("TODO")
})
}
func TestTableWriteController_PutItem(t *testing.T) {
t.Run("should put the selected item if dirty", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.PutItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", after)
assert.False(t, state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item but do not put it
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.PutItem(0), "n")
current, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", current)
assert.True(t, state.ResultSet().IsDirty(0))
// Rescan the table to confirm item is not modified
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
invokeCommandExpectingError(t, writeController.PutItem(0))
})
}
func TestTableWriteController_TouchItem(t *testing.T) {
t.Run("should put the selected item if unmodified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.TouchItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
invokeCommandExpectingError(t, writeController.TouchItem(0))
})
}
func TestTableWriteController_NoisyTouchItem(t *testing.T) {
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.NoisyTouchItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
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))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
invokeCommandExpectingError(t, writeController.NoisyTouchItem(0))
})
}

View file

@ -0,0 +1,60 @@
package itemrender
import (
"fmt"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"sort"
)
type ListRenderer types.AttributeValueMemberL
func (sr *ListRenderer) TypeName() string {
return "L"
}
func (sr *ListRenderer) StringValue() string {
return ""
}
func (sr *ListRenderer) MetaInfo() string {
if len(sr.Value) == 1 {
return fmt.Sprintf("(1 item)")
}
return fmt.Sprintf("(%d items)", len(sr.Value))
}
func (sr *ListRenderer) SubItems() []SubItem {
subitems := make([]SubItem, len(sr.Value))
for i, r := range sr.Value {
subitems[i] = SubItem{Key: fmt.Sprint(i), Value: ToRenderer(r)}
}
return subitems
}
type MapRenderer types.AttributeValueMemberM
func (sr *MapRenderer) TypeName() string {
return "M"
}
func (sr *MapRenderer) StringValue() string {
return ""
}
func (sr *MapRenderer) MetaInfo() string {
if len(sr.Value) == 1 {
return fmt.Sprintf("(1 item)")
}
return fmt.Sprintf("(%d items)", len(sr.Value))
}
func (sr *MapRenderer) SubItems() []SubItem {
subitems := make([]SubItem, 0)
for k, r := range sr.Value {
subitems = append(subitems, SubItem{Key: k, Value: ToRenderer(r)})
}
sort.Slice(subitems, func(i, j int) bool {
return subitems[i].Key < subitems[j].Key
})
return subitems
}

View file

@ -0,0 +1,50 @@
package itemrender
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
type Renderer interface {
TypeName() string
StringValue() string
MetaInfo() string
SubItems() []SubItem
}
func ToRenderer(v types.AttributeValue) Renderer {
switch colVal := v.(type) {
case nil:
return nil
case *types.AttributeValueMemberS:
x := StringRenderer(*colVal)
return &x
case *types.AttributeValueMemberN:
x := NumberRenderer(*colVal)
return &x
case *types.AttributeValueMemberBOOL:
x := BoolRenderer(*colVal)
return &x
case *types.AttributeValueMemberNULL:
x := NullRenderer(*colVal)
return &x
case *types.AttributeValueMemberB:
x := BinaryRenderer(*colVal)
return &x
case *types.AttributeValueMemberL:
x := ListRenderer(*colVal)
return &x
case *types.AttributeValueMemberM:
x := MapRenderer(*colVal)
return &x
case *types.AttributeValueMemberBS:
return newBinarySetRenderer(colVal)
case *types.AttributeValueMemberNS:
return newNumberSetRenderer(colVal)
case *types.AttributeValueMemberSS:
return newStringSetRenderer(colVal)
}
return OtherRenderer{}
}
type SubItem struct {
Key string
Value Renderer
}

View file

@ -0,0 +1,19 @@
package itemrender
type OtherRenderer struct{}
func (u OtherRenderer) TypeName() string {
return "??"
}
func (sr OtherRenderer) StringValue() string {
return ""
}
func (u OtherRenderer) MetaInfo() string {
return "(unrecognised)"
}
func (u OtherRenderer) SubItems() []SubItem {
return nil
}

View file

@ -0,0 +1,98 @@
package itemrender
import (
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
type StringRenderer types.AttributeValueMemberS
func (sr *StringRenderer) TypeName() string {
return "S"
}
func (sr *StringRenderer) StringValue() string {
return sr.Value
}
func (sr *StringRenderer) MetaInfo() string {
return ""
}
func (sr *StringRenderer) SubItems() []SubItem {
return nil
}
type NumberRenderer types.AttributeValueMemberN
func (sr *NumberRenderer) TypeName() string {
return "N"
}
func (sr *NumberRenderer) StringValue() string {
return sr.Value
}
func (sr *NumberRenderer) MetaInfo() string {
return ""
}
func (sr *NumberRenderer) SubItems() []SubItem {
return nil
}
type BoolRenderer types.AttributeValueMemberBOOL
func (sr *BoolRenderer) TypeName() string {
return "BOOL"
}
func (sr *BoolRenderer) StringValue() string {
if sr.Value {
return "True"
}
return "False"
}
func (sr *BoolRenderer) MetaInfo() string {
return ""
}
func (sr *BoolRenderer) SubItems() []SubItem {
return nil
}
type BinaryRenderer types.AttributeValueMemberB
func (sr *BinaryRenderer) TypeName() string {
return "B"
}
func (sr *BinaryRenderer) StringValue() string {
return ""
}
func (sr *BinaryRenderer) MetaInfo() string {
return cardinality(len(sr.Value), "byte", "bytes")
}
func (sr *BinaryRenderer) SubItems() []SubItem {
return nil
}
type NullRenderer types.AttributeValueMemberNULL
func (sr *NullRenderer) TypeName() string {
return "NULL"
}
func (sr *NullRenderer) MetaInfo() string {
return ""
}
func (sr *NullRenderer) StringValue() string {
return "null"
}
func (sr *NullRenderer) SubItems() []SubItem {
return nil
}

View file

@ -0,0 +1,55 @@
package itemrender
import (
"fmt"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
type GenericRenderer struct {
typeName string
subitemValue []Renderer
}
func (sr *GenericRenderer) TypeName() string {
return sr.typeName
}
func (sr *GenericRenderer) StringValue() string {
return ""
}
func (sr *GenericRenderer) MetaInfo() string {
return cardinality(len(sr.subitemValue), "item", "items")
}
func (sr *GenericRenderer) SubItems() []SubItem {
subitems := make([]SubItem, len(sr.subitemValue))
for i, r := range sr.subitemValue {
subitems[i] = SubItem{Key: fmt.Sprint(i), Value: r}
}
return subitems
}
func newBinarySetRenderer(v *types.AttributeValueMemberBS) *GenericRenderer {
vs := make([]Renderer, len(v.Value))
for i, b := range v.Value {
vs[i] = &BinaryRenderer{Value: b}
}
return &GenericRenderer{typeName: "BS", subitemValue: vs}
}
func newNumberSetRenderer(v *types.AttributeValueMemberNS) *GenericRenderer {
vs := make([]Renderer, len(v.Value))
for i, n := range v.Value {
vs[i] = &NumberRenderer{Value: n}
}
return &GenericRenderer{typeName: "NS", subitemValue: vs}
}
func newStringSetRenderer(v *types.AttributeValueMemberSS) *GenericRenderer {
vs := make([]Renderer, len(v.Value))
for i, s := range v.Value {
vs[i] = &StringRenderer{Value: s}
}
return &GenericRenderer{typeName: "SS", subitemValue: vs}
}

View file

@ -0,0 +1,10 @@
package itemrender
import "fmt"
func cardinality(c int, single, multi string) string {
if c == 1 {
return fmt.Sprintf("(%d %v)", c, single)
}
return fmt.Sprintf("(%d %v)", c, multi)
}

View file

@ -1,6 +1,9 @@
package models
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
import (
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/awstools/internal/dynamo-browse/models/itemrender"
)
type Item map[string]types.AttributeValue
@ -28,3 +31,7 @@ func (i Item) KeyValue(info *TableInfo) map[string]types.AttributeValue {
func (i Item) AttributeValueAsString(key string) (string, bool) {
return attributeToString(i[key])
}
func (i Item) Renderer(key string) itemrender.Renderer {
return itemrender.ToRenderer(i[key])
}

View file

@ -10,6 +10,8 @@ type ResultSet struct {
type ItemAttribute struct {
Marked bool
Hidden bool
Dirty bool
New bool
}
func (rs *ResultSet) Items() []Item {
@ -21,6 +23,11 @@ func (rs *ResultSet) SetItems(items []Item) {
rs.attributes = make([]ItemAttribute, len(items))
}
func (rs *ResultSet) AddNewItem(item Item, attrs ItemAttribute) {
rs.items = append(rs.items, item)
rs.attributes = append(rs.attributes, attrs)
}
func (rs *ResultSet) SetMark(idx int, marked bool) {
rs.attributes[idx].Marked = marked
}
@ -29,6 +36,14 @@ func (rs *ResultSet) SetHidden(idx int, hidden bool) {
rs.attributes[idx].Hidden = hidden
}
func (rs *ResultSet) SetDirty(idx int, dirty bool) {
rs.attributes[idx].Dirty = dirty
}
func (rs *ResultSet) SetNew(idx int, isNew bool) {
rs.attributes[idx].New = isNew
}
func (rs *ResultSet) Marked(idx int) bool {
return rs.attributes[idx].Marked
}
@ -37,6 +52,14 @@ func (rs *ResultSet) Hidden(idx int) bool {
return rs.attributes[idx].Hidden
}
func (rs *ResultSet) IsDirty(idx int) bool {
return rs.attributes[idx].Dirty
}
func (rs *ResultSet) IsNew(idx int) bool {
return rs.attributes[idx].New
}
func (rs *ResultSet) MarkedItems() []Item {
items := make([]Item, 0)
for i, itemAttr := range rs.attributes {

View file

@ -64,17 +64,27 @@ func NewProvider(client *dynamodb.Client) *Provider {
return &Provider{client: client}
}
func (p *Provider) ScanItems(ctx context.Context, tableName string) ([]models.Item, error) {
res, err := p.client.Scan(ctx, &dynamodb.ScanInput{
func (p *Provider) ScanItems(ctx context.Context, tableName string, maxItems int) ([]models.Item, error) {
paginator := dynamodb.NewScanPaginator(p.client, &dynamodb.ScanInput{
TableName: aws.String(tableName),
Limit: aws.Int32(int32(maxItems)),
})
if err != nil {
return nil, errors.Wrapf(err, "cannot execute scan on table %v", tableName)
}
items := make([]models.Item, len(res.Items))
for i, itm := range res.Items {
items[i] = itm
items := make([]models.Item, 0)
outer:
for paginator.HasMorePages() {
res, err := paginator.NextPage(ctx)
if err != nil {
return nil, errors.Wrapf(err, "cannot execute scan on table %v", tableName)
}
for _, itm := range res.Items {
items = append(items, itm)
if len(items) >= maxItems {
break outer
}
}
}
return items, nil

View file

@ -20,7 +20,7 @@ func TestProvider_ScanItems(t *testing.T) {
t.Run("should return scanned items from the table", func(t *testing.T) {
ctx := context.Background()
items, err := provider.ScanItems(ctx, tableName)
items, err := provider.ScanItems(ctx, tableName, 100)
assert.NoError(t, err)
assert.Len(t, items, 3)
@ -32,7 +32,7 @@ func TestProvider_ScanItems(t *testing.T) {
t.Run("should return error if table name does not exist", func(t *testing.T) {
ctx := context.Background()
items, err := provider.ScanItems(ctx, "does-not-exist")
items, err := provider.ScanItems(ctx, "does-not-exist", 100)
assert.Error(t, err)
assert.Nil(t, items)
})
@ -53,7 +53,7 @@ func TestProvider_DeleteItem(t *testing.T) {
"sk": &types.AttributeValueMemberS{Value: "222"},
})
items, err := provider.ScanItems(ctx, tableName)
items, err := provider.ScanItems(ctx, tableName, 100)
assert.NoError(t, err)
assert.Len(t, items, 2)
@ -75,7 +75,7 @@ func TestProvider_DeleteItem(t *testing.T) {
"sk": &types.AttributeValueMemberS{Value: "999"},
})
items, err := provider.ScanItems(ctx, tableName)
items, err := provider.ScanItems(ctx, tableName, 100)
assert.NoError(t, err)
assert.Len(t, items, 3)
@ -91,7 +91,7 @@ func TestProvider_DeleteItem(t *testing.T) {
ctx := context.Background()
items, err := provider.ScanItems(ctx, "does-not-exist")
items, err := provider.ScanItems(ctx, "does-not-exist", 100)
assert.Error(t, err)
assert.Nil(t, items)
})

View file

@ -10,7 +10,7 @@ import (
type TableProvider interface {
ListTables(ctx context.Context) ([]string, error)
DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error)
ScanItems(ctx context.Context, tableName string) ([]models.Item, error)
ScanItems(ctx context.Context, tableName string, maxItems int) ([]models.Item, error)
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error
PutItem(ctx context.Context, name string, item models.Item) error
}

View file

@ -28,7 +28,7 @@ func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo
}
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
results, err := s.provider.ScanItems(ctx, tableInfo.Name)
results, err := s.provider.ScanItems(ctx, tableInfo.Name, 1000)
if err != nil {
return nil, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name)
}
@ -81,6 +81,17 @@ func (s *Service) Put(ctx context.Context, tableInfo *models.TableInfo, item mod
return s.provider.PutItem(ctx, tableInfo.Name, item)
}
func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, index int) error {
item := resultSet.Items()[index]
if err := s.provider.PutItem(ctx, resultSet.TableInfo.Name, item); err != nil {
return err
}
resultSet.SetDirty(index, false)
resultSet.SetNew(index, false)
return nil
}
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
for _, item := range items {
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {

View file

@ -48,6 +48,25 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
},
"unmark": commandctrl.NoArgCommand(rc.Unmark()),
"delete": commandctrl.NoArgCommand(wc.DeleteMarked()),
// TEMP
"new-item": commandctrl.NoArgCommand(wc.NewItem()),
"set-s": func(args []string) tea.Cmd {
if len(args) == 0 {
return events.SetError(errors.New("expected field"))
}
return wc.SetStringValue(dtv.SelectedItemIndex(), args[0])
},
"put": func(args []string) tea.Cmd {
return wc.PutItem(dtv.SelectedItemIndex())
},
"touch": func(args []string) tea.Cmd {
return wc.TouchItem(dtv.SelectedItemIndex())
},
"noisy-touch": func(args []string) tea.Cmd {
return wc.NoisyTouchItem(dtv.SelectedItemIndex())
},
},
})

View file

@ -2,10 +2,11 @@ package dynamoitemview
import (
"fmt"
"github.com/lmika/awstools/internal/dynamo-browse/models/itemrender"
"io"
"strings"
"text/tabwriter"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@ -16,9 +17,14 @@ import (
var (
activeHeaderStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#ffffff")).
Background(lipgloss.Color("#4479ff"))
Bold(true).
Foreground(lipgloss.Color("#ffffff")).
Background(lipgloss.Color("#4479ff"))
fieldTypeStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#2B800C", Dark: "#73C653"})
metaInfoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#888888"))
)
type Model struct {
@ -83,15 +89,8 @@ func (m *Model) updateViewportToSelectedMessage() {
viewportContent := &strings.Builder{}
tabWriter := tabwriter.NewWriter(viewportContent, 0, 1, 1, ' ', 0)
for _, colName := range m.currentResultSet.Columns {
switch colVal := m.selectedItem[colName].(type) {
case nil:
break
case *types.AttributeValueMemberS:
fmt.Fprintf(tabWriter, "%v\tS\t%s\n", colName, colVal.Value)
case *types.AttributeValueMemberN:
fmt.Fprintf(tabWriter, "%v\tN\t%s\n", colName, colVal.Value)
default:
fmt.Fprintf(tabWriter, "%v\t?\t%s\n", colName, "(other)")
if r := m.selectedItem.Renderer(colName); r != nil {
m.renderItem(tabWriter, "", colName, r)
}
}
@ -100,3 +99,13 @@ func (m *Model) updateViewportToSelectedMessage() {
m.viewport.Height = m.h - m.frameTitle.HeaderHeight()
m.viewport.SetContent(viewportContent.String())
}
func (m *Model) renderItem(w io.Writer, prefix string, name string, r itemrender.Renderer) {
fmt.Fprintf(w, "%s%v\t%s\t%s%s\n",
prefix, name, fieldTypeStyle.Render(r.TypeName()), r.StringValue(), metaInfoStyle.Render(r.MetaInfo()))
if subitems := r.SubItems(); len(subitems) > 0 {
for _, si := range subitems {
m.renderItem(w, prefix+" ", si.Key, si.Value)
}
}
}

View file

@ -1,7 +1,7 @@
package dynamotableview
import (
table "github.com/calyptia/go-bubble-table"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
@ -9,6 +9,7 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamoitemview"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
table "github.com/lmika/go-bubble-table"
)
var (
@ -18,18 +19,43 @@ var (
Background(lipgloss.Color("#4479ff"))
)
type KeyBinding struct {
MoveUp key.Binding
MoveDown key.Binding
PageUp key.Binding
PageDown key.Binding
Home key.Binding
End key.Binding
ColLeft key.Binding
ColRight key.Binding
}
type Model struct {
frameTitle frame.FrameTitle
table table.Model
w, h int
keyBinding KeyBinding
// model state
colOffset int
rows []table.Row
resultSet *models.ResultSet
}
type columnModel struct {
m *Model
}
func (cm columnModel) Len() int {
return len(cm.m.resultSet.Columns[cm.m.colOffset:])
}
func (cm columnModel) Header(index int) string {
return cm.m.resultSet.Columns[cm.m.colOffset+index]
}
func New() *Model {
tbl := table.New([]string{"pk", "sk"}, 100, 100)
tbl := table.New(table.SimpleColumns([]string{"pk", "sk"}), 100, 100)
rows := make([]table.Row, 0)
tbl.SetRows(rows)
@ -38,6 +64,16 @@ func New() *Model {
return &Model{
frameTitle: frameTitle,
table: tbl,
keyBinding: KeyBinding{
MoveUp: key.NewBinding(key.WithKeys("i", "up")),
MoveDown: key.NewBinding(key.WithKeys("k", "down")),
PageUp: key.NewBinding(key.WithKeys("I", "pgup")),
PageDown: key.NewBinding(key.WithKeys("K", "pgdown")),
Home: key.NewBinding(key.WithKeys("I", "home")),
End: key.NewBinding(key.WithKeys("K", "end")),
ColLeft: key.NewBinding(key.WithKeys("j", "left")),
ColRight: key.NewBinding(key.WithKeys("l", "right")),
},
}
}
@ -52,26 +88,51 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.updateTable()
return m, m.postSelectedItemChanged
case tea.KeyMsg:
switch msg.String() {
switch {
// Table nav
case "i", "up":
case key.Matches(msg, m.keyBinding.MoveUp):
m.table.GoUp()
return m, m.postSelectedItemChanged
case "k", "down":
case key.Matches(msg, m.keyBinding.MoveDown):
m.table.GoDown()
return m, m.postSelectedItemChanged
case "I", "pgup":
case key.Matches(msg, m.keyBinding.PageUp):
m.table.GoPageUp()
return m, m.postSelectedItemChanged
case "K", "pgdn":
case key.Matches(msg, m.keyBinding.PageDown):
m.table.GoPageDown()
return m, m.postSelectedItemChanged
case key.Matches(msg, m.keyBinding.Home):
m.table.GoTop()
return m, m.postSelectedItemChanged
case key.Matches(msg, m.keyBinding.End):
m.table.GoBottom()
return m, m.postSelectedItemChanged
case key.Matches(msg, m.keyBinding.ColLeft):
m.setLeftmostDisplayedColumn(m.colOffset - 1)
return m, nil
case key.Matches(msg, m.keyBinding.ColRight):
m.setLeftmostDisplayedColumn(m.colOffset + 1)
return m, nil
}
}
return m, nil
}
func (m *Model) setLeftmostDisplayedColumn(newCol int) {
if newCol < 0 {
m.colOffset = 0
} else if newCol >= len(m.resultSet.Columns) {
m.colOffset = len(m.resultSet.Columns) - 1
} else {
m.colOffset = newCol
}
// TEMP
m.table.GoDown()
m.table.GoUp()
}
func (m *Model) View() string {
return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View())
}
@ -85,23 +146,41 @@ func (m *Model) Resize(w, h int) layout.ResizingModel {
}
func (m *Model) updateTable() {
m.colOffset = 0
m.frameTitle.SetTitle("Table: " + m.resultSet.TableInfo.Name)
m.rebuildTable()
}
func (m *Model) rebuildTable() {
resultSet := m.resultSet
m.frameTitle.SetTitle("Table: " + resultSet.TableInfo.Name)
newTbl := table.New(resultSet.Columns, m.w, m.h-m.frameTitle.HeaderHeight())
newTbl := table.New(columnModel{m}, m.w, m.h-m.frameTitle.HeaderHeight())
newRows := make([]table.Row, 0)
for i, r := range resultSet.Items() {
if resultSet.Hidden(i) {
continue
}
newRows = append(newRows, itemTableRow{resultSet: resultSet, itemIndex: i, item: r})
newRows = append(newRows, itemTableRow{
model: m,
resultSet: resultSet,
itemIndex: i,
item: r,
})
}
m.rows = newRows
newTbl.SetRows(newRows)
/*
for newTbl.Cursor() != m.table.Cursor() {
if newTbl.Cursor() < m.table.Cursor() {
newTbl.GoDown()
} else if newTbl.Cursor() > m.table.Cursor() {
newTbl.GoUp()
}
}
*/
m.table = newTbl
}
@ -135,5 +214,6 @@ func (m *Model) postSelectedItemChanged() tea.Msg {
}
func (m *Model) Refresh() {
m.table.SetRows(m.rows)
}

View file

@ -6,17 +6,24 @@ import (
"io"
"strings"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
table "github.com/calyptia/go-bubble-table"
"github.com/lmika/awstools/internal/dynamo-browse/models"
table "github.com/lmika/go-bubble-table"
)
var (
markedRowStyle = lipgloss.NewStyle().
Background(lipgloss.Color("#e1e1e1"))
Background(lipgloss.AdaptiveColor{Dark: "#e1e1e1", Light: "#414141"})
dirtyRowStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#e13131"))
newRowStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#31e131"))
metaInfoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#888888"))
)
type itemTableRow struct {
model *Model
resultSet *models.ResultSet
itemIndex int
item models.Item
@ -24,33 +31,37 @@ type itemTableRow struct {
func (mtr itemTableRow) Render(w io.Writer, model table.Model, index int) {
isMarked := mtr.resultSet.Marked(mtr.itemIndex)
isDirty := mtr.resultSet.IsDirty(mtr.itemIndex)
isNew := mtr.resultSet.IsNew(mtr.itemIndex)
var style lipgloss.Style
if index == model.Cursor() {
style = model.Styles.SelectedRow
}
if isMarked {
style = style.Copy().Inherit(markedRowStyle)
}
if isNew {
style = style.Copy().Inherit(newRowStyle)
} else if isDirty {
style = style.Copy().Inherit(dirtyRowStyle)
}
metaInfoStyle := style.Copy().Inherit(metaInfoStyle)
sb := strings.Builder{}
for i, colName := range mtr.resultSet.Columns {
for i, colName := range mtr.resultSet.Columns[mtr.model.colOffset:] {
if i > 0 {
sb.WriteString("\t")
sb.WriteString(style.Render("\t"))
}
switch colVal := mtr.item[colName].(type) {
case nil:
sb.WriteString("(nil)")
case *types.AttributeValueMemberS:
sb.WriteString(colVal.Value)
case *types.AttributeValueMemberN:
sb.WriteString(colVal.Value)
default:
sb.WriteString("(other)")
if r := mtr.item.Renderer(colName); r != nil {
sb.WriteString(style.Render(r.StringValue()))
if mi := r.MetaInfo(); mi != "" {
sb.WriteString(metaInfoStyle.Render(mi))
}
}
}
if index == model.Cursor() {
style := model.Styles.SelectedRow
if isMarked {
style = style.Copy().Inherit(markedRowStyle)
}
fmt.Fprintln(w, style.Render(sb.String()))
} else if isMarked {
fmt.Fprintln(w, markedRowStyle.Render(sb.String()))
} else {
fmt.Fprintln(w, sb.String())
}
fmt.Fprintln(w, sb.String())
}

View file

@ -0,0 +1,37 @@
package itemdisplay
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
)
type Model struct {
baseMode tea.Model
}
func New(baseMode tea.Model) *Model {
return &Model{
baseMode: baseMode,
}
}
func (m *Model) Init() tea.Cmd {
return nil
}
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector
m.baseMode = cc.Collect(m.baseMode.Update(msg))
return m, cc.Cmd()
}
func (m *Model) View() string {
return m.baseMode.View()
}
func (m *Model) Resize(w, h int) layout.ResizingModel {
m.baseMode = layout.Resize(m.baseMode, w, h)
return m
}

View file

@ -1,6 +1,7 @@
package tableselect
import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@ -32,6 +33,22 @@ func newListController(tableNames []string, w, h int) listController {
Padding(0, 0, 0, 1)
list := list.New(items, delegate, w, h)
list.KeyMap.CursorUp = key.NewBinding(
key.WithKeys("up", "i"),
key.WithHelp("↑/i", "up"),
)
list.KeyMap.CursorDown = key.NewBinding(
key.WithKeys("down", "k"),
key.WithHelp("↓/k", "down"),
)
list.KeyMap.PrevPage = key.NewBinding(
key.WithKeys("left", "j", "pgup", "b", "u"),
key.WithHelp("←/j/pgup", "prev page"),
)
list.KeyMap.NextPage = key.NewBinding(
key.WithKeys("right", "l", "pgdown", "f", "d"),
key.WithHelp("→/l/pgdn", "next page"),
)
list.SetShowTitle(false)
return listController{list: list}

View file

@ -1,12 +1,12 @@
package loglines
import (
table "github.com/calyptia/go-bubble-table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/slog-view/models"
table "github.com/lmika/go-bubble-table"
"path/filepath"
)
@ -28,7 +28,7 @@ type Model struct {
func New() *Model {
frameTitle := frame.NewFrameTitle("File: ", true, activeHeaderStyle)
table := table.New([]string{"level", "error", "message"}, 0, 0)
table := table.New(table.SimpleColumns{"level", "error", "message"}, 0, 0)
return &Model{
frameTitle: frameTitle,
@ -40,7 +40,7 @@ func (m *Model) SetLogFile(newLogFile *models.LogFile) {
m.logFile = newLogFile
m.frameTitle.SetTitle("File: " + filepath.Base(newLogFile.Filename))
cols := []string{"level", "error", "message"}
cols := table.SimpleColumns{"level", "error", "message"}
newTbl := table.New(cols, m.w, m.h-m.frameTitle.HeaderHeight())
newRows := make([]table.Row, len(newLogFile.Lines))

View file

@ -2,7 +2,7 @@ package loglines
import (
"fmt"
table "github.com/calyptia/go-bubble-table"
table "github.com/lmika/go-bubble-table"
"github.com/lmika/awstools/internal/slog-view/models"
"io"
"strings"

View file

@ -7,7 +7,6 @@ import (
"log"
"strings"
table "github.com/calyptia/go-bubble-table"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
@ -16,6 +15,7 @@ import (
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/sqs-browse/controllers"
"github.com/lmika/awstools/internal/sqs-browse/models"
table "github.com/lmika/go-bubble-table"
)
var (
@ -45,7 +45,7 @@ type uiModel struct {
}
func NewModel(dispatcher *dispatcher.Dispatcher, msgSendingHandlers *controllers.MessageSendingController) tea.Model {
tbl := table.New([]string{"seq", "message"}, 100, 20)
tbl := table.New(table.SimpleColumns{"seq", "message"}, 100, 20)
rows := make([]table.Row, 0)
tbl.SetRows(rows)

View file

@ -5,7 +5,7 @@ import (
"io"
"strings"
table "github.com/calyptia/go-bubble-table"
table "github.com/lmika/go-bubble-table"
"github.com/lmika/awstools/internal/sqs-browse/models"
)

View file

@ -1,12 +1,12 @@
package ssmlist
import (
table "github.com/calyptia/go-bubble-table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/ssm-browse/models"
table "github.com/lmika/go-bubble-table"
)
var (
@ -27,7 +27,7 @@ type Model struct {
func New() *Model {
frameTitle := frame.NewFrameTitle("SSM: /", true, activeHeaderStyle)
table := table.New([]string{"name", "type", "value"}, 0, 0)
table := table.New(table.SimpleColumns{"name", "type", "value"}, 0, 0)
return &Model{
frameTitle: frameTitle,
@ -41,7 +41,7 @@ func (m *Model) SetPrefix(newPrefix string) {
func (m *Model) SetParameters(parameters *models.SSMParameters) {
m.parameters = parameters
cols := []string{"name", "type", "value"}
cols := table.SimpleColumns{"name", "type", "value"}
newTbl := table.New(cols, m.w, m.h-m.frameTitle.HeaderHeight())
newRows := make([]table.Row, len(parameters.Items))

View file

@ -2,7 +2,7 @@ package ssmlist
import (
"fmt"
table "github.com/calyptia/go-bubble-table"
table "github.com/lmika/go-bubble-table"
"github.com/lmika/awstools/internal/ssm-browse/models"
"io"
"strings"

View file

@ -5,6 +5,7 @@ import (
"github.com/brianvoe/gofakeit/v6"
"github.com/google/uuid"
"log"
"strconv"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
@ -19,7 +20,7 @@ import (
func main() {
ctx := context.Background()
tableName := "awstools-test"
totalItems := 300
totalItems := 5000
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
@ -27,7 +28,7 @@ func main() {
}
dynamoClient := dynamodb.NewFromConfig(cfg,
dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL("http://localhost:8000")))
dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL("http://localhost:18000")))
if _, err = dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
@ -66,13 +67,22 @@ func main() {
for i := 0; i < totalItems; i++ {
key := uuid.New().String()
if err := tableService.Put(ctx, tableInfo, models.Item{
"pk": &types.AttributeValueMemberS{Value: key},
"sk": &types.AttributeValueMemberS{Value: key},
"name": &types.AttributeValueMemberS{Value: gofakeit.Name()},
"address": &types.AttributeValueMemberS{Value: gofakeit.Address().Address},
"city": &types.AttributeValueMemberS{Value: gofakeit.Address().City},
"phone": &types.AttributeValueMemberS{Value: gofakeit.Phone()},
"web": &types.AttributeValueMemberS{Value: gofakeit.URL()},
"pk": &types.AttributeValueMemberS{Value: key},
"sk": &types.AttributeValueMemberS{Value: key},
"name": &types.AttributeValueMemberS{Value: gofakeit.Name()},
"address": &types.AttributeValueMemberS{Value: gofakeit.Address().Address},
"city": &types.AttributeValueMemberS{Value: gofakeit.Address().City},
"phone": &types.AttributeValueMemberN{Value: gofakeit.Phone()},
"web": &types.AttributeValueMemberS{Value: gofakeit.URL()},
"inOffice": &types.AttributeValueMemberBOOL{Value: gofakeit.Bool()},
"ratings": &types.AttributeValueMemberL{Value: []types.AttributeValue{
&types.AttributeValueMemberS{Value: gofakeit.Adverb()},
&types.AttributeValueMemberN{Value: "12.34"},
}},
"values": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{
"adverb": &types.AttributeValueMemberS{Value: gofakeit.Adverb()},
"int": &types.AttributeValueMemberN{Value: strconv.Itoa(int(gofakeit.Int32()))},
}},
}); err != nil {
log.Fatalln(err)
}