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/tidwall/pretty v1.2.0
|
||||
github.com/tomlazar/table v0.1.2
|
||||
github.com/zeebo/assert v1.1.0
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/tools v0.1.11
|
||||
)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
@ -34,50 +34,6 @@ type Backup struct {
|
||||
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(
|
||||
snapshotID, detailsID, status string,
|
||||
selector selectors.Selector,
|
||||
@ -94,3 +50,53 @@ func New(
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/zeebo/assert"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/alcionai/corso/internal/stats"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
)
|
||||
|
||||
@ -24,19 +24,18 @@ func TestBackupSuite(t *testing.T) {
|
||||
suite.Run(t, new(BackupSuite))
|
||||
}
|
||||
|
||||
func (suite *BackupSuite) TestBackup_HeadersValues() {
|
||||
t := suite.T()
|
||||
now := time.Now()
|
||||
|
||||
b := backup.Backup{
|
||||
func stubBackup(t time.Time) backup.Backup {
|
||||
sel := selectors.NewExchangeBackup()
|
||||
sel.Include(sel.Users(selectors.Any()))
|
||||
return backup.Backup{
|
||||
BaseModel: model.BaseModel{
|
||||
ID: model.StableID("stable"),
|
||||
ID: model.StableID("id"),
|
||||
},
|
||||
CreationTime: now,
|
||||
CreationTime: t,
|
||||
SnapshotID: "snapshot",
|
||||
DetailsID: "details",
|
||||
Status: "status",
|
||||
Selectors: selectors.Selector{},
|
||||
Selectors: sel.Selector,
|
||||
ReadWrites: stats.ReadWrites{
|
||||
ItemsRead: 1,
|
||||
ItemsWritten: 1,
|
||||
@ -44,138 +43,52 @@ func (suite *BackupSuite) TestBackup_HeadersValues() {
|
||||
WriteErrors: errors.New("1"),
|
||||
},
|
||||
StartAndEndTime: stats.StartAndEndTime{
|
||||
StartedAt: now,
|
||||
CompletedAt: now,
|
||||
StartedAt: t,
|
||||
CompletedAt: t,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *BackupSuite) TestBackup_HeadersValues() {
|
||||
t := suite.T()
|
||||
now := time.Now()
|
||||
b := stubBackup(now)
|
||||
|
||||
expectHs := []string{
|
||||
"Creation Time",
|
||||
"Stable ID",
|
||||
"Snapshot ID",
|
||||
"Details ID",
|
||||
"Started At",
|
||||
"ID",
|
||||
"Status",
|
||||
"Selectors",
|
||||
"Items Read",
|
||||
"Items Written",
|
||||
"Read Errors",
|
||||
"Write Errors",
|
||||
"Started At",
|
||||
"Completed At",
|
||||
}
|
||||
hs := b.Headers()
|
||||
assert.DeepEqual(t, expectHs, hs)
|
||||
assert.Equal(t, expectHs, hs)
|
||||
nowFmt := common.FormatTime(now)
|
||||
|
||||
expectVs := []string{
|
||||
nowFmt,
|
||||
"stable",
|
||||
"snapshot",
|
||||
"details",
|
||||
"status",
|
||||
"{}",
|
||||
"1",
|
||||
"1",
|
||||
"1",
|
||||
"1",
|
||||
nowFmt,
|
||||
nowFmt,
|
||||
"id",
|
||||
"status (2 errors)",
|
||||
selectors.All,
|
||||
}
|
||||
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()
|
||||
nowStr := now.Format(time.RFC3339Nano)
|
||||
b := stubBackup(now)
|
||||
|
||||
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"},
|
||||
},
|
||||
}
|
||||
resultIface := b.MinimumPrintable()
|
||||
result, ok := resultIface.(backup.Printable)
|
||||
require.True(t, ok)
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
hs := test.entry.Headers()
|
||||
assert.DeepEqual(t, test.expectHs, hs)
|
||||
vs := test.entry.Values()
|
||||
assert.DeepEqual(t, test.expectVs, vs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
assert.Equal(t, b.ID, result.ID, "id")
|
||||
assert.Equal(t, 2, result.ErrorCount, "error count")
|
||||
assert.Equal(t, now, result.StartedAt, "started at")
|
||||
assert.Equal(t, b.Status, result.Status, "status")
|
||||
|
||||
bselp := b.Selectors.Printable()
|
||||
assert.Equal(t, bselp, result.Selectors, "selectors")
|
||||
assert.Equal(t, bselp.Resources(), result.Selectors.Resources(), "selector resources")
|
||||
}
|
||||
|
||||
@ -42,15 +42,15 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type StoreDetailsUnitSuite struct {
|
||||
type DetailsUnitSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestStoreDetailsUnitSuite(t *testing.T) {
|
||||
suite.Run(t, new(StoreDetailsUnitSuite))
|
||||
func TestDetailsUnitSuite(t *testing.T) {
|
||||
suite.Run(t, new(DetailsUnitSuite))
|
||||
}
|
||||
|
||||
func (suite *StoreDetailsUnitSuite) TestGetDetails() {
|
||||
func (suite *DetailsUnitSuite) TestGetDetails() {
|
||||
ctx := context.Background()
|
||||
|
||||
table := []struct {
|
||||
@ -82,7 +82,7 @@ func (suite *StoreDetailsUnitSuite) TestGetDetails() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *StoreDetailsUnitSuite) TestGetDetailsFromBackupID() {
|
||||
func (suite *DetailsUnitSuite) TestGetDetailsFromBackupID() {
|
||||
ctx := context.Background()
|
||||
|
||||
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.
|
||||
// Ex: {user: u1, events: NoneTgt} => matches nothing.
|
||||
NoneTgt = ""
|
||||
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -217,7 +220,7 @@ func toResourceTypeMap(ms []map[string]string) map[string][]string {
|
||||
for _, m := range ms {
|
||||
res := m[scopeKeyResource]
|
||||
if res == AnyTgt {
|
||||
res = "All"
|
||||
res = All
|
||||
}
|
||||
r[res] = addToSet(r[res], m[scopeKeyDataType])
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user