remove selector printable, list discreteOwner (#2034)

## Description

The selectors printable code wasn't being used in any valid way, so that's out.  Backup List printing now refers to the DiscreteOwner in the embedded selector as the ResourceOwner reference.  Renamed the "Selectors"
column in the List print table to "Resource Owner" to better match what should be contained in that field.

## Does this PR need a docs update or release note?

- [x]  Yes, it's included

## Type of change

- [x] 🐛 Bugfix
- [x] 🧹 Tech Debt/Cleanup

## Issue(s)

* #1617

## Test Plan

- [x] 💪 Manual
- [x]  Unit test
This commit is contained in:
Keepers 2023-01-09 15:42:17 -07:00 committed by GitHub
parent f3bf09ba8a
commit bf0a01708a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 23 additions and 328 deletions

View File

@ -325,42 +325,36 @@ func (suite *ExchangeUtilsSuite) TestAddExchangeInclude() {
}{ }{
{ {
name: "no inputs", name: "no inputs",
resources: empty,
folders: empty, folders: empty,
items: empty, items: empty,
expectIncludeLen: 0, expectIncludeLen: 0,
}, },
{ {
name: "single inputs", name: "single inputs",
resources: single,
folders: single, folders: single,
items: single, items: single,
expectIncludeLen: 1, expectIncludeLen: 1,
}, },
{ {
name: "multi inputs", name: "multi inputs",
resources: multi,
folders: multi, folders: multi,
items: multi, items: multi,
expectIncludeLen: 1, expectIncludeLen: 1,
}, },
{ {
name: "folder contains", name: "folder contains",
resources: empty,
folders: containsOnly, folders: containsOnly,
items: empty, items: empty,
expectIncludeLen: 1, expectIncludeLen: 1,
}, },
{ {
name: "folder prefixes", name: "folder prefixes",
resources: empty,
folders: prefixOnly, folders: prefixOnly,
items: empty, items: empty,
expectIncludeLen: 1, expectIncludeLen: 1,
}, },
{ {
name: "folder prefixes and contains", name: "folder prefixes and contains",
resources: empty,
folders: containsAndPrefix, folders: containsAndPrefix,
items: empty, items: empty,
expectIncludeLen: 2, expectIncludeLen: 2,

View File

@ -525,14 +525,16 @@ func (suite *DataCollectionsIntegrationSuite) TestEventsSerializationRegression(
expected: DefaultCalendar, expected: DefaultCalendar,
scope: selectors.NewExchangeBackup(users).EventCalendars( scope: selectors.NewExchangeBackup(users).EventCalendars(
[]string{DefaultCalendar}, []string{DefaultCalendar},
selectors.PrefixMatch())[0], selectors.PrefixMatch(),
)[0],
}, },
{ {
name: "Birthday Calendar", name: "Birthday Calendar",
expected: "Birthdays", expected: "Birthdays",
scope: selectors.NewExchangeBackup(users).EventCalendars( scope: selectors.NewExchangeBackup(users).EventCalendars(
[]string{"Birthdays"}, []string{"Birthdays"},
selectors.PrefixMatch())[0], selectors.PrefixMatch(),
)[0],
}, },
} }

View File

@ -28,8 +28,8 @@ type Backup struct {
// Status of the operation // Status of the operation
Status string `json:"status"` Status string `json:"status"`
// Selectors used in this operation // Selector used in this operation
Selectors selectors.Selector `json:"selectors"` Selector selectors.Selector `json:"selectors"`
// stats are embedded so that the values appear as top-level properties // stats are embedded so that the values appear as top-level properties
stats.Errs stats.Errs
@ -58,7 +58,7 @@ func New(
SnapshotID: snapshotID, SnapshotID: snapshotID,
DetailsID: detailsID, DetailsID: detailsID,
Status: status, Status: status,
Selectors: selector, Selector: selector,
ReadWrites: rw, ReadWrites: rw,
StartAndEndTime: se, StartAndEndTime: se,
} }
@ -89,14 +89,13 @@ func PrintAll(ctx context.Context, bs []*Backup) {
} }
type Printable struct { type Printable struct {
ID model.StableID `json:"id"` ID model.StableID `json:"id"`
ErrorCount int `json:"errorCount"` ErrorCount int `json:"errorCount"`
StartedAt time.Time `json:"started at"` StartedAt time.Time `json:"started at"`
Status string `json:"status"` Status string `json:"status"`
Version string `json:"version"` Version string `json:"version"`
Selectors selectors.Printable `json:"selectors"` BytesRead int64 `json:"bytesRead"`
BytesRead int64 `json:"bytesRead"` BytesUploaded int64 `json:"bytesUploaded"`
BytesUploaded int64 `json:"bytesUploaded"`
} }
// MinimumPrintable reduces the Backup to its minimally printable details. // MinimumPrintable reduces the Backup to its minimally printable details.
@ -107,7 +106,6 @@ func (b Backup) MinimumPrintable() any {
StartedAt: b.StartedAt, StartedAt: b.StartedAt,
Status: b.Status, Status: b.Status,
Version: "0", Version: "0",
Selectors: b.Selectors.ToPrintable(),
BytesRead: b.BytesRead, BytesRead: b.BytesRead,
BytesUploaded: b.BytesUploaded, BytesUploaded: b.BytesUploaded,
} }
@ -120,7 +118,7 @@ func (b Backup) Headers() []string {
"Started At", "Started At",
"ID", "ID",
"Status", "Status",
"Selectors", "Resource Owner",
} }
} }
@ -134,6 +132,6 @@ func (b Backup) Values() []string {
common.FormatTabularDisplayTime(b.StartedAt), common.FormatTabularDisplayTime(b.StartedAt),
string(b.ID), string(b.ID),
status, status,
b.Selectors.ToPrintable().Resources(), b.Selector.DiscreteOwner,
} }
} }

View File

@ -25,7 +25,7 @@ func TestBackupSuite(t *testing.T) {
} }
func stubBackup(t time.Time) backup.Backup { func stubBackup(t time.Time) backup.Backup {
sel := selectors.NewExchangeBackup(selectors.Any()) sel := selectors.NewExchangeBackup([]string{"test"})
sel.Include(sel.AllData()) sel.Include(sel.AllData())
return backup.Backup{ return backup.Backup{
@ -39,7 +39,7 @@ func stubBackup(t time.Time) backup.Backup {
SnapshotID: "snapshot", SnapshotID: "snapshot",
DetailsID: "details", DetailsID: "details",
Status: "status", Status: "status",
Selectors: sel.Selector, Selector: sel.Selector,
Errs: stats.Errs{ Errs: stats.Errs{
ReadErrors: errors.New("1"), ReadErrors: errors.New("1"),
WriteErrors: errors.New("1"), WriteErrors: errors.New("1"),
@ -66,7 +66,7 @@ func (suite *BackupSuite) TestBackup_HeadersValues() {
"Started At", "Started At",
"ID", "ID",
"Status", "Status",
"Selectors", "Resource Owner",
} }
hs := b.Headers() hs := b.Headers()
assert.Equal(t, expectHs, hs) assert.Equal(t, expectHs, hs)
@ -76,7 +76,7 @@ func (suite *BackupSuite) TestBackup_HeadersValues() {
nowFmt, nowFmt,
"id", "id",
"status (2 errors)", "status (2 errors)",
selectors.All, "test",
} }
vs := b.Values() vs := b.Values()
@ -96,11 +96,6 @@ func (suite *BackupSuite) TestBackup_MinimumPrintable() {
assert.Equal(t, 2, result.ErrorCount, "error count") assert.Equal(t, 2, result.ErrorCount, "error count")
assert.Equal(t, now, result.StartedAt, "started at") assert.Equal(t, now, result.StartedAt, "started at")
assert.Equal(t, b.Status, result.Status, "status") assert.Equal(t, b.Status, result.Status, "status")
bselp := b.Selectors.ToPrintable()
assert.Equal(t, bselp, result.Selectors, "selectors")
assert.Equal(t, bselp.Resources(), result.Selectors.Resources(), "selector resources")
assert.Equal(t, b.BytesRead, result.BytesRead, "size") assert.Equal(t, b.BytesRead, result.BytesRead, "size")
assert.Equal(t, b.BytesUploaded, result.BytesUploaded, "stored size") assert.Equal(t, b.BytesUploaded, result.BytesUploaded, "stored size")
} }

View File

@ -306,7 +306,7 @@ func (r repository) BackupDetails(ctx context.Context, backupID string) (*detail
deets, err := streamstore.New( deets, err := streamstore.New(
r.dataLayer, r.dataLayer,
r.Account.ID(), r.Account.ID(),
b.Selectors.PathService()).ReadBackupDetails(ctx, dID) b.Selector.PathService()).ReadBackupDetails(ctx, dID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -38,7 +38,6 @@ type (
var ( var (
_ Reducer = &ExchangeRestore{} _ Reducer = &ExchangeRestore{}
_ printabler = &ExchangeRestore{}
_ pathCategorier = &ExchangeRestore{} _ pathCategorier = &ExchangeRestore{}
) )
@ -110,11 +109,6 @@ func (sr ExchangeRestore) SplitByResourceOwner(users []string) []ExchangeRestore
return ss return ss
} }
// Printable creates the minimized display of a selector, formatted for human readability.
func (s exchange) Printable() Printable {
return toPrintable[ExchangeScope](s.Selector)
}
// PathCategories produces the aggregation of discrete users described by each type of scope. // PathCategories produces the aggregation of discrete users described by each type of scope.
func (s exchange) PathCategories() selectorPathCategories { func (s exchange) PathCategories() selectorPathCategories {
return selectorPathCategories{ return selectorPathCategories{

View File

@ -160,10 +160,6 @@ func stubSelector(resourceOwners []string) mockSel {
} }
} }
func (s mockSel) Printable() Printable {
return toPrintable[mockScope](s.Selector)
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// helper funcs // helper funcs
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -37,7 +37,6 @@ type (
var ( var (
_ Reducer = &OneDriveRestore{} _ Reducer = &OneDriveRestore{}
_ printabler = &OneDriveRestore{}
_ pathCategorier = &OneDriveRestore{} _ pathCategorier = &OneDriveRestore{}
) )
@ -109,11 +108,6 @@ func (s OneDriveRestore) SplitByResourceOwner(users []string) []OneDriveRestore
return ss return ss
} }
// Printable creates the minimized display of a selector, formatted for human readability.
func (s oneDrive) Printable() Printable {
return toPrintable[OneDriveScope](s.Selector)
}
// PathCategories produces the aggregation of discrete users described by each type of scope. // PathCategories produces the aggregation of discrete users described by each type of scope.
func (s oneDrive) PathCategories() selectorPathCategories { func (s oneDrive) PathCategories() selectorPathCategories {
return selectorPathCategories{ return selectorPathCategories{

View File

@ -393,33 +393,6 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
} }
} }
func (suite *SelectorScopesSuite) TestAddToSet() {
t := suite.T()
set := []string{}
set = addToSet(set, []string{})
assert.Len(t, set, 0)
set = addToSet(set, []string{"a"})
assert.Len(t, set, 1)
assert.Equal(t, set[0], "a")
set = addToSet(set, []string{"a"})
assert.Len(t, set, 1)
set = addToSet(set, []string{"a", "b"})
assert.Len(t, set, 2)
assert.Equal(t, set[0], "a")
assert.Equal(t, set[1], "b")
set = addToSet(set, []string{"c", "d"})
assert.Len(t, set, 4)
assert.Equal(t, set[0], "a")
assert.Equal(t, set[1], "b")
assert.Equal(t, set[2], "c")
assert.Equal(t, set[3], "d")
}
func (suite *SelectorScopesSuite) TestClean() { func (suite *SelectorScopesSuite) TestClean() {
table := []struct { table := []struct {
name string name string

View File

@ -3,7 +3,6 @@ package selectors
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -318,128 +317,6 @@ func selectorAsIface[T any](s Selector) (T, error) {
return t, err return t, err
} }
// ---------------------------------------------------------------------------
// Printing Selectors for Human Reading
// ---------------------------------------------------------------------------
type Printable struct {
ResourceOwners []string `json:"resourceOwners"`
Service string `json:"service"`
Excludes map[string][]string `json:"excludes,omitempty"`
Filters map[string][]string `json:"filters,omitempty"`
Includes map[string][]string `json:"includes,omitempty"`
}
type printabler interface {
Printable() Printable
}
// ToPrintable creates the minimized display of a selector, formatted for human readability.
func (s Selector) ToPrintable() Printable {
p, err := selectorAsIface[printabler](s)
if err != nil {
return Printable{}
}
return p.Printable()
}
// toPrintable creates the minimized display of a selector, formatted for human readability.
func toPrintable[T scopeT](s Selector) Printable {
return Printable{
ResourceOwners: s.DiscreteResourceOwners(),
Service: s.Service.String(),
Excludes: toResourceTypeMap[T](s.Excludes),
Filters: toResourceTypeMap[T](s.Filters),
Includes: toResourceTypeMap[T](s.Includes),
}
}
// Resources generates a tabular-readable output of the resources in Printable.
// Only the first (arbitrarily picked) resource is displayed. All others are
// simply counted. If no inclusions exist, uses Filters. If no filters exist,
// defaults to "None".
// Resource refers to the top-level entity in the service. User for Exchange,
// Site for sharepoint, etc.
func (p Printable) Resources() string {
s := resourcesShortFormat(p.ResourceOwners)
if len(s) == 0 {
s = "None"
}
if s == AnyTgt {
s = "All"
}
return s
}
// returns a string with the resources in the map. Shortened to the first resource key,
// plus, if more exist, " (len-1 more)"
func resourcesShortFormat(ros []string) string {
switch len(ros) {
case 0:
return ""
case 1:
return ros[0]
default:
return fmt.Sprintf("%s (%d more)", ros[0], len(ros)-1)
}
}
// Transforms the slice to a single map.
// Keys are each service's rootCat value.
// Values are the set of all scopeKeyDataTypes for the resource.
func toResourceTypeMap[T scopeT](s []scope) map[string][]string {
if len(s) == 0 {
return nil
}
r := make(map[string][]string)
for _, sc := range s {
t := T(sc)
res := sc[t.categorizer().rootCat().String()]
k := res.Target
if res.Target == AnyTgt {
k = All
}
for _, sk := range split(k) {
r[sk] = addToSet(r[sk], split(sc[scopeKeyDataType].Target))
}
}
return r
}
// returns v if set is empty,
// unions v with set, otherwise.
func addToSet(set []string, v []string) []string {
if len(set) == 0 {
return v
}
for _, vv := range v {
var matched bool
for _, s := range set {
if vv == s {
matched = true
break
}
}
if !matched {
set = append(set, vv)
}
}
return set
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// helpers // helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -1,7 +1,6 @@
package selectors package selectors
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -32,127 +31,6 @@ func (suite *SelectorSuite) TestBadCastErr() {
assert.Error(suite.T(), err) assert.Error(suite.T(), err)
} }
func (suite *SelectorSuite) TestPrintable() {
t := suite.T()
sel := stubSelector(Any())
p := sel.Printable()
assert.Equal(t, sel.Service.String(), p.Service)
assert.Equal(t, 1, len(p.Excludes))
assert.Equal(t, 1, len(p.Filters))
assert.Equal(t, 1, len(p.Includes))
}
func (suite *SelectorSuite) TestPrintable_IncludedResources() {
table := []struct {
name string
resourceOwners []string
expect func(string) bool
reason string
}{
{
name: "distinct",
resourceOwners: []string{"foo", "smarf", "fnords"},
expect: func(s string) bool {
return strings.HasSuffix(s, "(2 more)")
},
reason: "should end with (2 more)",
},
{
name: "distinct",
resourceOwners: nil,
expect: func(s string) bool {
return strings.HasSuffix(s, "None")
},
reason: "no resource owners should produce None",
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := stubSelector(test.resourceOwners)
stubWithResource := func(resource string) scope {
ss := stubScope("")
ss[rootCatStub.String()] = filterize(scopeConfig{}, resource)
return scope(ss)
}
sel.Includes = []scope{}
sel.Filters = []scope{}
for _, ro := range test.resourceOwners {
sel.Includes = append(sel.Includes, stubWithResource(ro))
sel.Filters = append(sel.Filters, stubWithResource(ro))
}
})
}
}
func (suite *SelectorSuite) TestToResourceTypeMap() {
table := []struct {
name string
input []scope
expect map[string][]string
}{
{
name: "single scope",
input: []scope{scope(stubScope(""))},
expect: map[string][]string{
"All": {rootCatStub.String()},
},
},
{
name: "disjoint resources",
input: []scope{
scope(stubScope("")),
{
rootCatStub.String(): filterize(scopeConfig{}, "smarf"),
scopeKeyDataType: filterize(scopeConfig{}, unknownCatStub.String()),
},
},
expect: map[string][]string{
"All": {rootCatStub.String()},
"smarf": {unknownCatStub.String()},
},
},
{
name: "multiple resources",
input: []scope{
scope(stubScope("")),
{
rootCatStub.String(): filterize(scopeConfig{}, join("smarf", "fnords")),
scopeKeyDataType: filterize(scopeConfig{}, unknownCatStub.String()),
},
},
expect: map[string][]string{
"All": {rootCatStub.String()},
"smarf": {unknownCatStub.String()},
"fnords": {unknownCatStub.String()},
},
},
{
name: "disjoint types",
input: []scope{
scope(stubScope("")),
{
rootCatStub.String(): filterize(scopeConfig{}, AnyTgt),
scopeKeyDataType: filterize(scopeConfig{}, "other"),
},
},
expect: map[string][]string{
"All": {rootCatStub.String(), "other"},
},
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
rtm := toResourceTypeMap[mockScope](test.input)
assert.Equal(t, test.expect, rtm)
})
}
}
func (suite *SelectorSuite) TestResourceOwnersIn() { func (suite *SelectorSuite) TestResourceOwnersIn() {
rootCat := rootCatStub.String() rootCat := rootCatStub.String()

View File

@ -35,7 +35,6 @@ type (
var ( var (
_ Reducer = &SharePointRestore{} _ Reducer = &SharePointRestore{}
_ printabler = &SharePointRestore{}
_ pathCategorier = &SharePointRestore{} _ pathCategorier = &SharePointRestore{}
) )
@ -107,11 +106,6 @@ func (s SharePointRestore) SplitByResourceOwner(users []string) []SharePointRest
return ss return ss
} }
// Printable creates the minimized display of a selector, formatted for human readability.
func (s sharePoint) Printable() Printable {
return toPrintable[SharePointScope](s.Selector)
}
// PathCategories produces the aggregation of discrete users described by each type of scope. // PathCategories produces the aggregation of discrete users described by each type of scope.
func (s sharePoint) PathCategories() selectorPathCategories { func (s sharePoint) PathCategories() selectorPathCategories {
return selectorPathCategories{ return selectorPathCategories{

View File

@ -156,7 +156,7 @@ Your first backup may take some time if your mailbox is large.
There will be progress indicators as the backup and, on completion, you should see output similar to: There will be progress indicators as the backup and, on completion, you should see output similar to:
```text ```text
Started At ID Status Selectors Started At ID Status Resource Owner
2022-10-20T18:28:53Z d8cd833a-fc63-4872-8981-de5c08e0661b Completed (0 errors) alice@contoso.com 2022-10-20T18:28:53Z d8cd833a-fc63-4872-8981-de5c08e0661b Completed (0 errors) alice@contoso.com
``` ```
@ -195,7 +195,7 @@ docker run --env-file $HOME/.corso/corso.env \\
</Tabs> </Tabs>
```text ```text
Started At ID Status Selectors Started At ID Status Resource Owner
2022-10-20T18:28:53Z d8cd833a-fc63-4872-8981-de5c08e0661b Completed (0 errors) alice@contoso.com 2022-10-20T18:28:53Z d8cd833a-fc63-4872-8981-de5c08e0661b Completed (0 errors) alice@contoso.com
2022-10-20T18:40:45Z 391ceeb3-b44d-4365-9a8e-8a8e1315b565 Completed (0 errors) alice@contoso.com 2022-10-20T18:40:45Z 391ceeb3-b44d-4365-9a8e-8a8e1315b565 Completed (0 errors) alice@contoso.com
... ...