print stats after backup (#3128)
Prints backup stats to the CLI following completion. In case of multiple users, the stats for each backup is printed at the end of the backup, rather than at the end of the command. --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included #### Type of change - [x] 🌻 Feature #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test
This commit is contained in:
parent
d5fac8a480
commit
6e982d6bdc
@ -230,7 +230,13 @@ func runBackups(
|
||||
}
|
||||
|
||||
bIDs = append(bIDs, string(bo.Results.BackupID))
|
||||
Infof(ctx, "Done - ID: %v\n", bo.Results.BackupID)
|
||||
|
||||
if !DisplayJSONFormat() {
|
||||
Infof(ctx, "Done\n")
|
||||
printBackupStats(ctx, r, string(bo.Results.BackupID))
|
||||
} else {
|
||||
Infof(ctx, "Done - ID: %v\n", bo.Results.BackupID)
|
||||
}
|
||||
}
|
||||
|
||||
bups, berrs := r.Backups(ctx, bIDs)
|
||||
@ -335,3 +341,13 @@ func getAccountAndConnect(ctx context.Context) (repository.Repository, *account.
|
||||
func ifShow(flag string) bool {
|
||||
return strings.ToLower(strings.TrimSpace(flag)) == "show"
|
||||
}
|
||||
|
||||
func printBackupStats(ctx context.Context, r repository.Repository, bid string) {
|
||||
b, err := r.Backup(ctx, bid)
|
||||
if err != nil {
|
||||
logger.CtxErr(ctx, err).Error("finding backup immediately after backup operation completion")
|
||||
}
|
||||
|
||||
b.ToPrintable().Stats.Print(ctx)
|
||||
Info(ctx, " ")
|
||||
}
|
||||
|
||||
@ -50,8 +50,8 @@ func AddOutputFlag(cmd *cobra.Command) {
|
||||
cobra.CheckErr(fs.MarkHidden("json-debug"))
|
||||
}
|
||||
|
||||
// JSONFormat returns true if the printer plans to output as json.
|
||||
func JSONFormat() bool {
|
||||
// DisplayJSONFormat returns true if the printer plans to output as json.
|
||||
func DisplayJSONFormat() bool {
|
||||
return outputAsJSON || outputAsJSONDebug
|
||||
}
|
||||
|
||||
|
||||
@ -3,9 +3,12 @@ package backup
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/alcionai/corso/src/cli/print"
|
||||
"github.com/alcionai/corso/src/internal/common"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
@ -141,6 +144,8 @@ func New(
|
||||
// CLI Output
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// ----- print backups
|
||||
|
||||
// Print writes the Backup to StdOut, in the format requested by the caller.
|
||||
func (b Backup) Print(ctx context.Context) {
|
||||
print.Item(ctx, b)
|
||||
@ -162,36 +167,36 @@ func PrintAll(ctx context.Context, bs []*Backup) {
|
||||
}
|
||||
|
||||
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"`
|
||||
BytesRead int64 `json:"bytesRead"`
|
||||
BytesUploaded int64 `json:"bytesUploaded"`
|
||||
Owner string `json:"owner"`
|
||||
ID model.StableID `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
Owner string `json:"owner"`
|
||||
Stats backupStats `json:"stats"`
|
||||
}
|
||||
|
||||
// ToPrintable reduces the Backup to its minimally printable details.
|
||||
func (b Backup) ToPrintable() Printable {
|
||||
return Printable{
|
||||
ID: b.ID,
|
||||
Status: b.Status,
|
||||
Version: "0",
|
||||
Owner: b.Selector.DiscreteOwner,
|
||||
Stats: b.toStats(),
|
||||
}
|
||||
}
|
||||
|
||||
// MinimumPrintable reduces the Backup to its minimally printable details.
|
||||
func (b Backup) MinimumPrintable() any {
|
||||
return Printable{
|
||||
ID: b.ID,
|
||||
ErrorCount: b.ErrorCount,
|
||||
StartedAt: b.StartedAt,
|
||||
Status: b.Status,
|
||||
Version: "0",
|
||||
BytesRead: b.BytesRead,
|
||||
BytesUploaded: b.BytesUploaded,
|
||||
Owner: b.Selector.DiscreteOwner,
|
||||
}
|
||||
return b.ToPrintable()
|
||||
}
|
||||
|
||||
// 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",
|
||||
"Started At",
|
||||
"Duration",
|
||||
"Status",
|
||||
"Resource Owner",
|
||||
}
|
||||
@ -255,10 +260,78 @@ func (b Backup) Values() []string {
|
||||
name = b.Selector.DiscreteOwner
|
||||
}
|
||||
|
||||
bs := b.toStats()
|
||||
|
||||
return []string{
|
||||
common.FormatTabularDisplayTime(b.StartedAt),
|
||||
string(b.ID),
|
||||
common.FormatTabularDisplayTime(b.StartedAt),
|
||||
bs.EndedAt.Sub(bs.StartedAt).String(),
|
||||
status,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
// ----- print backup stats
|
||||
|
||||
func (b Backup) toStats() backupStats {
|
||||
return backupStats{
|
||||
ID: string(b.ID),
|
||||
BytesRead: b.BytesRead,
|
||||
BytesUploaded: b.BytesUploaded,
|
||||
EndedAt: b.CompletedAt,
|
||||
ErrorCount: b.ErrorCount,
|
||||
ItemsRead: b.ItemsRead,
|
||||
ItemsSkipped: b.TotalSkippedItems,
|
||||
ItemsWritten: b.ItemsWritten,
|
||||
StartedAt: b.StartedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// interface compliance checks
|
||||
var _ print.Printable = &backupStats{}
|
||||
|
||||
type backupStats struct {
|
||||
ID string `json:"id"`
|
||||
BytesRead int64 `json:"bytesRead"`
|
||||
BytesUploaded int64 `json:"bytesUploaded"`
|
||||
EndedAt time.Time `json:"endedAt"`
|
||||
ErrorCount int `json:"errorCount"`
|
||||
ItemsRead int `json:"itemsRead"`
|
||||
ItemsSkipped int `json:"itemsSkipped"`
|
||||
ItemsWritten int `json:"itemsWritten"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
}
|
||||
|
||||
// Print writes the Backup to StdOut, in the format requested by the caller.
|
||||
func (bs backupStats) Print(ctx context.Context) {
|
||||
print.Item(ctx, bs)
|
||||
}
|
||||
|
||||
// MinimumPrintable reduces the Backup to its minimally printable details.
|
||||
func (bs backupStats) MinimumPrintable() any {
|
||||
return bs
|
||||
}
|
||||
|
||||
// Headers returns the human-readable names of properties in a Backup
|
||||
// for printing out to a terminal in a columnar display.
|
||||
func (bs backupStats) Headers() []string {
|
||||
return []string{
|
||||
"ID",
|
||||
"Bytes Uploaded",
|
||||
"Items Uploaded",
|
||||
"Items Skipped",
|
||||
"Errors",
|
||||
}
|
||||
}
|
||||
|
||||
// Values returns the values matching the Headers list for printing
|
||||
// out to a terminal in a columnar display.
|
||||
func (bs backupStats) Values() []string {
|
||||
return []string{
|
||||
bs.ID,
|
||||
humanize.Bytes(uint64(bs.BytesUploaded)),
|
||||
strconv.Itoa(bs.ItemsWritten),
|
||||
strconv.Itoa(bs.ItemsSkipped),
|
||||
strconv.Itoa(bs.ErrorCount),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package backup_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -50,7 +52,7 @@ func stubBackup(t time.Time, ownerID, ownerName string) backup.Backup {
|
||||
},
|
||||
StartAndEndTime: stats.StartAndEndTime{
|
||||
StartedAt: t,
|
||||
CompletedAt: t,
|
||||
CompletedAt: t.Add(1 * time.Minute),
|
||||
},
|
||||
SkippedCounts: stats.SkippedCounts{
|
||||
TotalSkippedItems: 1,
|
||||
@ -63,22 +65,27 @@ func (suite *BackupUnitSuite) TestBackup_HeadersValues() {
|
||||
var (
|
||||
t = suite.T()
|
||||
now = time.Now()
|
||||
later = now.Add(1 * time.Minute)
|
||||
b = stubBackup(now, "id", "name")
|
||||
expectHs = []string{
|
||||
"Started At",
|
||||
"ID",
|
||||
"Started At",
|
||||
"Duration",
|
||||
"Status",
|
||||
"Resource Owner",
|
||||
}
|
||||
nowFmt = common.FormatTabularDisplayTime(now)
|
||||
expectVs = []string{
|
||||
nowFmt,
|
||||
"id",
|
||||
nowFmt,
|
||||
"1m0s",
|
||||
"status (2 errors, 1 skipped: 1 malware)",
|
||||
"test",
|
||||
}
|
||||
)
|
||||
|
||||
b.StartAndEndTime.CompletedAt = later
|
||||
|
||||
// single skipped malware
|
||||
hs := b.Headers()
|
||||
assert.Equal(t, expectHs, hs)
|
||||
@ -182,7 +189,7 @@ func (suite *BackupUnitSuite) TestBackup_Values_statusVariations() {
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
result := test.bup.Values()
|
||||
assert.Equal(suite.T(), test.expect, result[2], "status value")
|
||||
assert.Equal(suite.T(), test.expect, result[3], "status value")
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -197,10 +204,57 @@ func (suite *BackupUnitSuite) TestBackup_MinimumPrintable() {
|
||||
require.True(t, ok)
|
||||
|
||||
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, 2, result.Stats.ErrorCount, "error count")
|
||||
assert.Equal(t, now, result.Stats.StartedAt, "started at")
|
||||
assert.Equal(t, b.Status, result.Status, "status")
|
||||
assert.Equal(t, b.BytesRead, result.BytesRead, "size")
|
||||
assert.Equal(t, b.BytesUploaded, result.BytesUploaded, "stored size")
|
||||
assert.Equal(t, b.BytesRead, result.Stats.BytesRead, "size")
|
||||
assert.Equal(t, b.BytesUploaded, result.Stats.BytesUploaded, "stored size")
|
||||
assert.Equal(t, b.Selector.DiscreteOwner, result.Owner, "owner")
|
||||
}
|
||||
|
||||
func (suite *BackupUnitSuite) TestStats() {
|
||||
var (
|
||||
t = suite.T()
|
||||
start = time.Now()
|
||||
b = stubBackup(start, "owner", "ownername")
|
||||
s = b.ToPrintable().Stats
|
||||
)
|
||||
|
||||
assert.Equal(t, b.BytesRead, s.BytesRead, "bytes read")
|
||||
assert.Equal(t, b.BytesUploaded, s.BytesUploaded, "bytes uploaded")
|
||||
assert.Equal(t, b.CompletedAt, s.EndedAt, "completion time")
|
||||
assert.Equal(t, b.ErrorCount, s.ErrorCount, "error count")
|
||||
assert.Equal(t, b.ItemsRead, s.ItemsRead, "items read")
|
||||
assert.Equal(t, b.TotalSkippedItems, s.ItemsSkipped, "items skipped")
|
||||
assert.Equal(t, b.ItemsWritten, s.ItemsWritten, "items written")
|
||||
assert.Equal(t, b.StartedAt, s.StartedAt, "started at")
|
||||
}
|
||||
|
||||
func (suite *BackupUnitSuite) TestStats_headersValues() {
|
||||
var (
|
||||
t = suite.T()
|
||||
start = time.Now()
|
||||
b = stubBackup(start, "owner", "ownername")
|
||||
s = b.ToPrintable().Stats
|
||||
)
|
||||
|
||||
expectHeaders := []string{
|
||||
"ID",
|
||||
"Bytes Uploaded",
|
||||
"Items Uploaded",
|
||||
"Items Skipped",
|
||||
"Errors",
|
||||
}
|
||||
|
||||
assert.Equal(t, expectHeaders, s.Headers())
|
||||
|
||||
expectValues := []string{
|
||||
"id",
|
||||
humanize.Bytes(uint64(b.BytesUploaded)),
|
||||
strconv.Itoa(b.ItemsWritten),
|
||||
strconv.Itoa(b.TotalSkippedItems),
|
||||
strconv.Itoa(b.ErrorCount),
|
||||
}
|
||||
|
||||
assert.Equal(t, expectValues, s.Values())
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ type DetailsModel struct {
|
||||
// Print writes the DetailModel Entries to StdOut, in the format
|
||||
// requested by the caller.
|
||||
func (dm DetailsModel) PrintEntries(ctx context.Context) {
|
||||
if print.JSONFormat() {
|
||||
if print.DisplayJSONFormat() {
|
||||
printJSON(ctx, dm)
|
||||
} else {
|
||||
printTable(ctx, dm)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user