OneDrive Folder and Item selectors (#985)

## Description

Adds support for folder and item selection

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

* #627 

## Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
Vaibhav Kamra 2022-09-28 19:44:08 -07:00 committed by GitHub
parent 011e24583f
commit b7f5ed73c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 184 additions and 21 deletions

View File

@ -1,6 +1,8 @@
package selectors
import (
"context"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/path"
)
@ -141,19 +143,6 @@ func (s *oneDrive) Filter(scopes ...[]OneDriveScope) {
s.Filters = appendScopes(s.Filters, scopes...)
}
// Produces one or more oneDrive user scopes.
// One scope is created per user entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *oneDrive) Users(users []string) []OneDriveScope {
scopes := []OneDriveScope{}
scopes = append(scopes, makeScope[OneDriveScope](OneDriveUser, users, users))
return scopes
}
// Scopes retrieves the list of oneDriveScopes in the selector.
func (s *oneDrive) Scopes() []OneDriveScope {
return scopes[OneDriveScope](s.Selector)
@ -166,6 +155,53 @@ func (s *oneDrive) DiscreteScopes(userPNs []string) []OneDriveScope {
return discreteScopes[OneDriveScope](s.Selector, OneDriveUser, userPNs)
}
// -------------------
// Scope Factories
// Produces one or more OneDrive user scopes.
// One scope is created per user entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *oneDrive) Users(users []string) []OneDriveScope {
scopes := []OneDriveScope{}
scopes = append(scopes, makeScope[OneDriveScope](OneDriveFolder, users, Any()))
return scopes
}
// Folders produces one or more OneDrive folder scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *oneDrive) Folders(users, folders []string) []OneDriveScope {
scopes := []OneDriveScope{}
scopes = append(
scopes,
makeScope[OneDriveScope](OneDriveFolder, users, folders),
)
return scopes
}
// Items produces one or more OneDrive item scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *oneDrive) Items(users, folders, items []string) []OneDriveScope {
scopes := []OneDriveScope{}
scopes = append(
scopes,
makeScope[OneDriveScope](OneDriveItem, users, items).
set(OneDriveFolder, folders),
)
return scopes
}
// ---------------------------------------------------------------------------
// Categories
// ---------------------------------------------------------------------------
@ -180,13 +216,16 @@ var _ categorizer = OneDriveCategoryUnknown
const (
OneDriveCategoryUnknown oneDriveCategory = ""
// types of data identified by OneDrive
OneDriveUser oneDriveCategory = "OneDriveUser"
OneDriveUser oneDriveCategory = "OneDriveUser"
OneDriveItem oneDriveCategory = "OneDriveItem"
OneDriveFolder oneDriveCategory = "OneDriveFolder"
)
// oneDrivePathSet describes the category type keys used in OneDrive paths.
// The order of each slice is important, and should match the order in which
// these types appear in the canonical Path for each type.
var oneDrivePathSet = map[categorizer][]categorizer{
OneDriveItem: {OneDriveUser, OneDriveFolder, OneDriveItem},
OneDriveUser: {OneDriveUser}, // the root category must be represented
}
@ -200,6 +239,11 @@ func (c oneDriveCategory) String() string {
// Ex: ServiceTypeFolder.leafCat() => ServiceTypeItem
// Ex: ServiceUser.leafCat() => ServiceUser
func (c oneDriveCategory) leafCat() categorizer {
switch c {
case OneDriveFolder, OneDriveItem:
return OneDriveItem
}
return c
}
@ -213,9 +257,10 @@ func (c oneDriveCategory) unknownCat() categorizer {
return OneDriveCategoryUnknown
}
// isLeaf is true if the category is a mail, event, or contact category.
// isLeaf is true if the category is a OneDriveItem category.
func (c oneDriveCategory) isLeaf() bool {
return c == c.leafCat()
// return c == c.leafCat()??
return c == OneDriveItem
}
// pathValues transforms a path to a map of identified properties.
@ -225,7 +270,9 @@ func (c oneDriveCategory) isLeaf() bool {
// => {odUser: userPN, odFolder: folder, odFileID: fileID}
func (c oneDriveCategory) pathValues(p path.Path) map[categorizer]string {
return map[categorizer]string{
OneDriveUser: p.ResourceOwner(),
OneDriveUser: p.ResourceOwner(),
OneDriveFolder: p.Folder(), // TODO: Should we filter out the DriveID here?
OneDriveItem: p.Item(),
}
}
@ -290,13 +337,19 @@ func (s OneDriveScope) Get(cat oneDriveCategory) []string {
}
// sets a value by category to the scope. Only intended for internal use.
// func (s OneDriveScope) set(cat oneDriveCategory, v string) OneDriveScope {
// return set(s, cat, v)
// }
func (s OneDriveScope) set(cat oneDriveCategory, v []string) OneDriveScope {
return set(s, cat, v)
}
// setDefaults ensures that user scopes express `AnyTgt` for their child category types.
func (s OneDriveScope) setDefaults() {
// no-op while no child scope types below user are identified
switch s.Category() {
case OneDriveUser:
s[OneDriveFolder.String()] = passAny
s[OneDriveItem.String()] = passAny
case OneDriveFolder:
s[OneDriveItem.String()] = passAny
}
}
// matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo
@ -333,3 +386,20 @@ func (s OneDriveScope) matchesInfo(dii details.ItemInfo) bool {
return false
}
// ---------------------------------------------------------------------------
// Backup Details Filtering
// ---------------------------------------------------------------------------
// Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector.
func (s oneDrive) Reduce(ctx context.Context, deets *details.Details) *details.Details {
return reduce[OneDriveScope](
ctx,
deets,
s.Selector,
map[path.CategoryType]oneDriveCategory{
path.FilesCategory: OneDriveItem,
},
)
}

View File

@ -1,11 +1,15 @@
package selectors
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/path"
)
type OneDriveSelectorSuite struct {
@ -174,3 +178,92 @@ func (suite *OneDriveSelectorSuite) TestToOneDriveRestore() {
assert.Equal(t, or.Service, ServiceOneDrive)
assert.NotZero(t, or.Scopes())
}
func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
var (
file = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderA/folderB", "file")
file2 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderA/folderC", "file2")
file3 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderD/folderE", "file3")
)
deets := &details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.DetailsEntry{
{
RepoRef: file,
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
},
},
},
{
RepoRef: file2,
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
},
},
},
{
RepoRef: file3,
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
},
},
},
},
},
}
arr := func(s ...string) []string {
return s
}
table := []struct {
name string
deets *details.Details
makeSelector func() *OneDriveRestore
expect []string
}{
{
"all",
deets,
func() *OneDriveRestore {
odr := NewOneDriveRestore()
odr.Include(odr.Users(Any()))
return odr
},
arr(file, file2, file3),
},
{
"only match file",
deets,
func() *OneDriveRestore {
odr := NewOneDriveRestore()
odr.Include(odr.Items(Any(), Any(), []string{"file2"}))
return odr
},
arr(file2),
},
{
"only match folder",
deets,
func() *OneDriveRestore {
odr := NewOneDriveRestore()
odr.Include(odr.Folders([]string{"uid"}, []string{"folderA/folderB", "folderA/folderC"}))
return odr
},
arr(file, file2),
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := test.makeSelector()
results := sel.Reduce(context.Background(), test.deets)
paths := results.Paths()
assert.Equal(t, test.expect, paths)
})
}
}