add printable backup struct and tabular formatting (#435)
This commit is contained in:
parent
992504ed36
commit
aca3d856af
@ -19,7 +19,6 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
github.com/tidwall/pretty v1.2.0
|
github.com/tidwall/pretty v1.2.0
|
||||||
github.com/tomlazar/table v0.1.2
|
github.com/tomlazar/table v0.1.2
|
||||||
github.com/zeebo/assert v1.1.0
|
|
||||||
go.uber.org/zap v1.21.0
|
go.uber.org/zap v1.21.0
|
||||||
golang.org/x/tools v0.1.11
|
golang.org/x/tools v0.1.11
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/common"
|
"github.com/alcionai/corso/internal/common"
|
||||||
@ -34,50 +34,6 @@ type Backup struct {
|
|||||||
stats.StartAndEndTime
|
stats.StartAndEndTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// MinimumPrintable reduces the Backup to its minimally printable details.
|
|
||||||
func (b Backup) MinimumPrintable() any {
|
|
||||||
// todo: implement printable backup struct
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers returns the human-readable names of properties in a Backup
|
|
||||||
// for printing out to a terminal in a columnar display.
|
|
||||||
func (b Backup) Headers() []string {
|
|
||||||
return []string{
|
|
||||||
"Creation Time",
|
|
||||||
"Stable ID",
|
|
||||||
"Snapshot ID",
|
|
||||||
"Details ID",
|
|
||||||
"Status",
|
|
||||||
"Selectors",
|
|
||||||
"Items Read",
|
|
||||||
"Items Written",
|
|
||||||
"Read Errors",
|
|
||||||
"Write Errors",
|
|
||||||
"Started At",
|
|
||||||
"Completed At",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values returns the values matching the Headers list for printing
|
|
||||||
// out to a terminal in a columnar display.
|
|
||||||
func (b Backup) Values() []string {
|
|
||||||
return []string{
|
|
||||||
common.FormatTime(b.CreationTime),
|
|
||||||
string(b.ID),
|
|
||||||
b.SnapshotID,
|
|
||||||
b.DetailsID,
|
|
||||||
b.Status,
|
|
||||||
b.Selectors.String(),
|
|
||||||
strconv.Itoa(b.ReadWrites.ItemsRead),
|
|
||||||
strconv.Itoa(b.ReadWrites.ItemsWritten),
|
|
||||||
strconv.Itoa(support.GetNumberOfErrors(b.ReadWrites.ReadErrors)),
|
|
||||||
strconv.Itoa(support.GetNumberOfErrors(b.ReadWrites.WriteErrors)),
|
|
||||||
common.FormatTime(b.StartAndEndTime.StartedAt),
|
|
||||||
common.FormatTime(b.StartAndEndTime.CompletedAt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
snapshotID, detailsID, status string,
|
snapshotID, detailsID, status string,
|
||||||
selector selectors.Selector,
|
selector selectors.Selector,
|
||||||
@ -94,3 +50,53 @@ func New(
|
|||||||
StartAndEndTime: se,
|
StartAndEndTime: se,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
// CLI Output
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Printable struct {
|
||||||
|
ID model.StableID `json:"id"`
|
||||||
|
ErrorCount int `json:"errorCount"`
|
||||||
|
StartedAt time.Time `json:"started at"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Selectors selectors.Printable `json:"selectors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimumPrintable reduces the Backup to its minimally printable details.
|
||||||
|
func (b Backup) MinimumPrintable() any {
|
||||||
|
// todo: implement printable backup struct
|
||||||
|
return Printable{
|
||||||
|
ID: b.ID,
|
||||||
|
ErrorCount: support.GetNumberOfErrors(b.ReadErrors) + support.GetNumberOfErrors(b.WriteErrors),
|
||||||
|
StartedAt: b.StartedAt,
|
||||||
|
Status: b.Status,
|
||||||
|
Version: "0",
|
||||||
|
Selectors: b.Selectors.Printable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers returns the human-readable names of properties in a Backup
|
||||||
|
// for printing out to a terminal in a columnar display.
|
||||||
|
func (b Backup) Headers() []string {
|
||||||
|
return []string{
|
||||||
|
"Started At",
|
||||||
|
"ID",
|
||||||
|
"Status",
|
||||||
|
"Selectors",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values returns the values matching the Headers list for printing
|
||||||
|
// out to a terminal in a columnar display.
|
||||||
|
func (b Backup) Values() []string {
|
||||||
|
errCount := support.GetNumberOfErrors(b.ReadErrors) + support.GetNumberOfErrors(b.WriteErrors)
|
||||||
|
status := fmt.Sprintf("%s (%d errors)", b.Status, errCount)
|
||||||
|
return []string{
|
||||||
|
common.FormatTime(b.StartedAt),
|
||||||
|
string(b.ID),
|
||||||
|
status,
|
||||||
|
b.Selectors.Printable().Resources(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,14 +5,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/zeebo/assert"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/common"
|
"github.com/alcionai/corso/internal/common"
|
||||||
"github.com/alcionai/corso/internal/model"
|
"github.com/alcionai/corso/internal/model"
|
||||||
"github.com/alcionai/corso/internal/stats"
|
"github.com/alcionai/corso/internal/stats"
|
||||||
"github.com/alcionai/corso/pkg/backup"
|
"github.com/alcionai/corso/pkg/backup"
|
||||||
"github.com/alcionai/corso/pkg/backup/details"
|
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,19 +24,18 @@ func TestBackupSuite(t *testing.T) {
|
|||||||
suite.Run(t, new(BackupSuite))
|
suite.Run(t, new(BackupSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupSuite) TestBackup_HeadersValues() {
|
func stubBackup(t time.Time) backup.Backup {
|
||||||
t := suite.T()
|
sel := selectors.NewExchangeBackup()
|
||||||
now := time.Now()
|
sel.Include(sel.Users(selectors.Any()))
|
||||||
|
return backup.Backup{
|
||||||
b := backup.Backup{
|
|
||||||
BaseModel: model.BaseModel{
|
BaseModel: model.BaseModel{
|
||||||
ID: model.StableID("stable"),
|
ID: model.StableID("id"),
|
||||||
},
|
},
|
||||||
CreationTime: now,
|
CreationTime: t,
|
||||||
SnapshotID: "snapshot",
|
SnapshotID: "snapshot",
|
||||||
DetailsID: "details",
|
DetailsID: "details",
|
||||||
Status: "status",
|
Status: "status",
|
||||||
Selectors: selectors.Selector{},
|
Selectors: sel.Selector,
|
||||||
ReadWrites: stats.ReadWrites{
|
ReadWrites: stats.ReadWrites{
|
||||||
ItemsRead: 1,
|
ItemsRead: 1,
|
||||||
ItemsWritten: 1,
|
ItemsWritten: 1,
|
||||||
@ -44,138 +43,52 @@ func (suite *BackupSuite) TestBackup_HeadersValues() {
|
|||||||
WriteErrors: errors.New("1"),
|
WriteErrors: errors.New("1"),
|
||||||
},
|
},
|
||||||
StartAndEndTime: stats.StartAndEndTime{
|
StartAndEndTime: stats.StartAndEndTime{
|
||||||
StartedAt: now,
|
StartedAt: t,
|
||||||
CompletedAt: now,
|
CompletedAt: t,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupSuite) TestBackup_HeadersValues() {
|
||||||
|
t := suite.T()
|
||||||
|
now := time.Now()
|
||||||
|
b := stubBackup(now)
|
||||||
|
|
||||||
expectHs := []string{
|
expectHs := []string{
|
||||||
"Creation Time",
|
"Started At",
|
||||||
"Stable ID",
|
"ID",
|
||||||
"Snapshot ID",
|
|
||||||
"Details ID",
|
|
||||||
"Status",
|
"Status",
|
||||||
"Selectors",
|
"Selectors",
|
||||||
"Items Read",
|
|
||||||
"Items Written",
|
|
||||||
"Read Errors",
|
|
||||||
"Write Errors",
|
|
||||||
"Started At",
|
|
||||||
"Completed At",
|
|
||||||
}
|
}
|
||||||
hs := b.Headers()
|
hs := b.Headers()
|
||||||
assert.DeepEqual(t, expectHs, hs)
|
assert.Equal(t, expectHs, hs)
|
||||||
nowFmt := common.FormatTime(now)
|
nowFmt := common.FormatTime(now)
|
||||||
|
|
||||||
expectVs := []string{
|
expectVs := []string{
|
||||||
nowFmt,
|
nowFmt,
|
||||||
"stable",
|
"id",
|
||||||
"snapshot",
|
"status (2 errors)",
|
||||||
"details",
|
selectors.All,
|
||||||
"status",
|
|
||||||
"{}",
|
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
nowFmt,
|
|
||||||
nowFmt,
|
|
||||||
}
|
}
|
||||||
vs := b.Values()
|
vs := b.Values()
|
||||||
assert.DeepEqual(t, expectVs, vs)
|
assert.Equal(t, expectVs, vs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupSuite) TestDetailsEntry_HeadersValues() {
|
func (suite *BackupSuite) TestBackup_MinimumPrintable() {
|
||||||
|
t := suite.T()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
nowStr := now.Format(time.RFC3339Nano)
|
b := stubBackup(now)
|
||||||
|
|
||||||
table := []struct {
|
resultIface := b.MinimumPrintable()
|
||||||
name string
|
result, ok := resultIface.(backup.Printable)
|
||||||
entry details.DetailsEntry
|
require.True(t, ok)
|
||||||
expectHs []string
|
|
||||||
expectVs []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no info",
|
|
||||||
entry: details.DetailsEntry{
|
|
||||||
RepoRef: "reporef",
|
|
||||||
},
|
|
||||||
expectHs: []string{"Repo Ref"},
|
|
||||||
expectVs: []string{"reporef"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "exhange info",
|
|
||||||
entry: details.DetailsEntry{
|
|
||||||
RepoRef: "reporef",
|
|
||||||
ItemInfo: details.ItemInfo{
|
|
||||||
Exchange: &details.ExchangeInfo{
|
|
||||||
Sender: "sender",
|
|
||||||
Subject: "subject",
|
|
||||||
Received: now,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectHs: []string{"Repo Ref", "Sender", "Subject", "Received"},
|
|
||||||
expectVs: []string{"reporef", "sender", "subject", nowStr},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sharepoint info",
|
|
||||||
entry: details.DetailsEntry{
|
|
||||||
RepoRef: "reporef",
|
|
||||||
ItemInfo: details.ItemInfo{
|
|
||||||
Sharepoint: &details.SharepointInfo{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectHs: []string{"Repo Ref"},
|
|
||||||
expectVs: []string{"reporef"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range table {
|
assert.Equal(t, b.ID, result.ID, "id")
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
assert.Equal(t, 2, result.ErrorCount, "error count")
|
||||||
hs := test.entry.Headers()
|
assert.Equal(t, now, result.StartedAt, "started at")
|
||||||
assert.DeepEqual(t, test.expectHs, hs)
|
assert.Equal(t, b.Status, result.Status, "status")
|
||||||
vs := test.entry.Values()
|
|
||||||
assert.DeepEqual(t, test.expectVs, vs)
|
bselp := b.Selectors.Printable()
|
||||||
})
|
assert.Equal(t, bselp, result.Selectors, "selectors")
|
||||||
}
|
assert.Equal(t, bselp.Resources(), result.Selectors.Resources(), "selector resources")
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *BackupSuite) TestDetailsModel_Path() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
ents []details.DetailsEntry
|
|
||||||
expect []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil entries",
|
|
||||||
ents: nil,
|
|
||||||
expect: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single entry",
|
|
||||||
ents: []details.DetailsEntry{
|
|
||||||
{RepoRef: "abcde"},
|
|
||||||
},
|
|
||||||
expect: []string{"abcde"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple entries",
|
|
||||||
ents: []details.DetailsEntry{
|
|
||||||
{RepoRef: "abcde"},
|
|
||||||
{RepoRef: "12345"},
|
|
||||||
},
|
|
||||||
expect: []string{"abcde", "12345"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
d := details.Details{
|
|
||||||
DetailsModel: details.DetailsModel{
|
|
||||||
Entries: test.ents,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.DeepEqual(t, test.expect, d.Paths())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,15 +42,15 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type StoreDetailsUnitSuite struct {
|
type DetailsUnitSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStoreDetailsUnitSuite(t *testing.T) {
|
func TestDetailsUnitSuite(t *testing.T) {
|
||||||
suite.Run(t, new(StoreDetailsUnitSuite))
|
suite.Run(t, new(DetailsUnitSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StoreDetailsUnitSuite) TestGetDetails() {
|
func (suite *DetailsUnitSuite) TestGetDetails() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -82,7 +82,7 @@ func (suite *StoreDetailsUnitSuite) TestGetDetails() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StoreDetailsUnitSuite) TestGetDetailsFromBackupID() {
|
func (suite *DetailsUnitSuite) TestGetDetailsFromBackupID() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -114,3 +114,98 @@ func (suite *StoreDetailsUnitSuite) TestGetDetailsFromBackupID() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
|
||||||
|
now := time.Now()
|
||||||
|
nowStr := now.Format(time.RFC3339Nano)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
entry details.DetailsEntry
|
||||||
|
expectHs []string
|
||||||
|
expectVs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no info",
|
||||||
|
entry: details.DetailsEntry{
|
||||||
|
RepoRef: "reporef",
|
||||||
|
},
|
||||||
|
expectHs: []string{"Repo Ref"},
|
||||||
|
expectVs: []string{"reporef"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exhange info",
|
||||||
|
entry: details.DetailsEntry{
|
||||||
|
RepoRef: "reporef",
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
Exchange: &details.ExchangeInfo{
|
||||||
|
Sender: "sender",
|
||||||
|
Subject: "subject",
|
||||||
|
Received: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectHs: []string{"Repo Ref", "Sender", "Subject", "Received"},
|
||||||
|
expectVs: []string{"reporef", "sender", "subject", nowStr},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sharepoint info",
|
||||||
|
entry: details.DetailsEntry{
|
||||||
|
RepoRef: "reporef",
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
Sharepoint: &details.SharepointInfo{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectHs: []string{"Repo Ref"},
|
||||||
|
expectVs: []string{"reporef"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
hs := test.entry.Headers()
|
||||||
|
assert.Equal(t, test.expectHs, hs)
|
||||||
|
vs := test.entry.Values()
|
||||||
|
assert.Equal(t, test.expectVs, vs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DetailsUnitSuite) TestDetailsModel_Path() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
ents []details.DetailsEntry
|
||||||
|
expect []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil entries",
|
||||||
|
ents: nil,
|
||||||
|
expect: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single entry",
|
||||||
|
ents: []details.DetailsEntry{
|
||||||
|
{RepoRef: "abcde"},
|
||||||
|
},
|
||||||
|
expect: []string{"abcde"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple entries",
|
||||||
|
ents: []details.DetailsEntry{
|
||||||
|
{RepoRef: "abcde"},
|
||||||
|
{RepoRef: "12345"},
|
||||||
|
},
|
||||||
|
expect: []string{"abcde", "12345"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
d := details.Details{
|
||||||
|
DetailsModel: details.DetailsModel{
|
||||||
|
Entries: test.ents,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.expect, d.Paths())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -49,10 +49,13 @@ const (
|
|||||||
// selector to fail.
|
// selector to fail.
|
||||||
// Ex: {user: u1, events: NoneTgt} => matches nothing.
|
// Ex: {user: u1, events: NoneTgt} => matches nothing.
|
||||||
NoneTgt = ""
|
NoneTgt = ""
|
||||||
|
|
||||||
delimiter = ","
|
delimiter = ","
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// All is the resource name that gets output when the resource is AnyTgt.
|
||||||
|
// It is not used aside from printing resources.
|
||||||
|
const All = "All"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Selector
|
// Selector
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -217,7 +220,7 @@ func toResourceTypeMap(ms []map[string]string) map[string][]string {
|
|||||||
for _, m := range ms {
|
for _, m := range ms {
|
||||||
res := m[scopeKeyResource]
|
res := m[scopeKeyResource]
|
||||||
if res == AnyTgt {
|
if res == AnyTgt {
|
||||||
res = "All"
|
res = All
|
||||||
}
|
}
|
||||||
r[res] = addToSet(r[res], m[scopeKeyDataType])
|
r[res] = addToSet(r[res], m[scopeKeyDataType])
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user