add printable backup struct and tabular formatting (#435)

This commit is contained in:
Keepers 2022-07-28 13:25:16 -06:00 committed by GitHub
parent 992504ed36
commit aca3d856af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 195 additions and 179 deletions

View File

@ -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
)

View File

@ -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(),
}
}

View File

@ -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")
}

View File

@ -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())
})
}
}

View File

@ -48,11 +48,14 @@ const (
// None() to any selector will force all matches() checks on that
// selector to fail.
// Ex: {user: u1, events: NoneTgt} => matches nothing.
NoneTgt = ""
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])
}