ProduceBackupCollections for SharePoint
This commit is contained in:
parent
2ba349797f
commit
203f7bd18d
@ -10,6 +10,7 @@ import (
|
||||
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||
"github.com/alcionai/corso/src/internal/m365/service/exchange"
|
||||
"github.com/alcionai/corso/src/internal/m365/service/groups"
|
||||
"github.com/alcionai/corso/src/internal/m365/service/onedrive"
|
||||
"github.com/alcionai/corso/src/internal/m365/service/sharepoint"
|
||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||
@ -116,6 +117,18 @@ func (ctrl *Controller) ProduceBackupCollections(
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
case path.GroupsService:
|
||||
colls, ssmb, canUsePreviousBackup, err = groups.ProduceBackupCollections(
|
||||
ctx,
|
||||
bpc,
|
||||
ctrl.AC,
|
||||
ctrl.credentials,
|
||||
ctrl.UpdateStatus,
|
||||
errs)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, nil, false, clues.Wrap(clues.New(service.String()), "service not supported").WithClues(ctx)
|
||||
}
|
||||
@ -176,6 +189,10 @@ func verifyBackupInputs(sels selectors.Selector, siteIDs []string) error {
|
||||
// Exchange and OneDrive user existence now checked in checkServiceEnabled.
|
||||
return nil
|
||||
|
||||
case selectors.ServiceGroups:
|
||||
// TODO(meain): can use a proper check in checkServiceEnabled.
|
||||
return nil
|
||||
|
||||
case selectors.ServiceSharePoint:
|
||||
ids = siteIDs
|
||||
}
|
||||
@ -197,7 +214,8 @@ func checkServiceEnabled(
|
||||
service path.ServiceType,
|
||||
resource string,
|
||||
) (bool, bool, error) {
|
||||
if service == path.SharePointService {
|
||||
if service == path.SharePointService || service == path.GroupsService {
|
||||
// TODO(meain): use proper check for groups
|
||||
// No "enabled" check required for sharepoint
|
||||
return true, true, nil
|
||||
}
|
||||
|
||||
@ -465,3 +465,86 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Lists() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CreateGroupsCollection tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type GroupsCollectionIntgSuite struct {
|
||||
tester.Suite
|
||||
connector *Controller
|
||||
user string
|
||||
}
|
||||
|
||||
func TestGroupsCollectionIntgSuite(t *testing.T) {
|
||||
suite.Run(t, &GroupsCollectionIntgSuite{
|
||||
Suite: tester.NewIntegrationSuite(
|
||||
t,
|
||||
[][]string{tconfig.M365AcctCredEnvs},
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *GroupsCollectionIntgSuite) SetupSuite() {
|
||||
ctx, flush := tester.NewContext(suite.T())
|
||||
defer flush()
|
||||
|
||||
suite.connector = newController(ctx, suite.T(), resource.Sites, path.GroupsService)
|
||||
suite.user = tconfig.M365UserID(suite.T())
|
||||
|
||||
tester.LogTimeOfTest(suite.T())
|
||||
}
|
||||
|
||||
func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint() {
|
||||
t := suite.T()
|
||||
|
||||
ctx, flush := tester.NewContext(t)
|
||||
defer flush()
|
||||
|
||||
var (
|
||||
groupID = tconfig.M365GroupID(t)
|
||||
ctrl = newController(ctx, t, resource.Groups, path.GroupsService)
|
||||
groupIDs = []string{groupID}
|
||||
)
|
||||
|
||||
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, groupID, nil)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
sel := selectors.NewGroupsBackup(groupIDs)
|
||||
// TODO(meain): make use of selectors
|
||||
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
||||
|
||||
sel.SetDiscreteOwnerIDName(id, name)
|
||||
|
||||
bpc := inject.BackupProducerConfig{
|
||||
LastBackupVersion: version.NoBackup,
|
||||
Options: control.DefaultOptions(),
|
||||
ProtectedResource: inMock.NewProvider(id, name),
|
||||
Selector: sel.Selector,
|
||||
}
|
||||
|
||||
collections, excludes, canUsePreviousBackup, err := ctrl.ProduceBackupCollections(
|
||||
ctx,
|
||||
bpc,
|
||||
fault.New(true))
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
assert.True(t, canUsePreviousBackup, "can use previous backup")
|
||||
// No excludes yet as this isn't an incremental backup.
|
||||
assert.True(t, excludes.Empty())
|
||||
|
||||
// we don't know an exact count of drives this will produce,
|
||||
// but it should be more than one.
|
||||
assert.Greater(t, len(collections), 1)
|
||||
|
||||
for _, coll := range collections {
|
||||
for object := range coll.Items(ctx, fault.New(true)) {
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := buf.ReadFrom(object.ToReader())
|
||||
assert.NoError(t, err, "reading item", clues.ToCore(err))
|
||||
}
|
||||
}
|
||||
|
||||
status := ctrl.Wait()
|
||||
assert.NotZero(t, status.Successes)
|
||||
t.Log(status.String())
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ func (c *Collections) Get(
|
||||
service, category := c.handler.ServiceCat()
|
||||
md, err := graph.MakeMetadataCollection(
|
||||
c.tenantID,
|
||||
c.resourceOwner,
|
||||
c.resourceOwner, // TODO(meain): path fixes: group id
|
||||
service,
|
||||
category,
|
||||
[]graph.MetadataCollectionEntry{
|
||||
|
||||
192
src/internal/m365/collection/drive/group_handler.go
Normal file
192
src/internal/m365/collection/drive/group_handler.go
Normal file
@ -0,0 +1,192 @@
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
"github.com/alcionai/corso/src/pkg/selectors"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
var _ BackupHandler = &groupBackupHandler{}
|
||||
|
||||
type groupBackupHandler struct {
|
||||
groupID string
|
||||
ac api.Drives
|
||||
scope selectors.GroupsScope
|
||||
}
|
||||
|
||||
func NewGroupBackupHandler(groupID string, ac api.Drives, scope selectors.GroupsScope) groupBackupHandler {
|
||||
return groupBackupHandler{groupID, ac, scope}
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) Get(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
headers map[string]string,
|
||||
) (*http.Response, error) {
|
||||
return h.ac.Get(ctx, url, headers)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) PathPrefix(
|
||||
tenantID, resourceOwner, driveID string,
|
||||
) (path.Path, error) {
|
||||
return path.Build(
|
||||
tenantID,
|
||||
resourceOwner,
|
||||
path.GroupsService,
|
||||
path.LibrariesCategory, // TODO(meain)
|
||||
false,
|
||||
odConsts.DrivesPathDir,
|
||||
driveID,
|
||||
odConsts.RootPathDir)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) CanonicalPath(
|
||||
folders *path.Builder,
|
||||
tenantID, resourceOwner string,
|
||||
) (path.Path, error) {
|
||||
// TODO(meain): resourceOwner
|
||||
return folders.ToDataLayerGroupPath(tenantID, h.groupID, path.LibrariesCategory, false)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) ServiceCat() (path.ServiceType, path.CategoryType) {
|
||||
return path.GroupsService, path.LibrariesCategory
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) NewDrivePager(
|
||||
resourceOwner string,
|
||||
fields []string,
|
||||
) api.DrivePager {
|
||||
return h.ac.NewSiteDrivePager(resourceOwner, fields)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) NewItemPager(
|
||||
driveID, link string,
|
||||
fields []string,
|
||||
) api.DriveItemDeltaEnumerator {
|
||||
return h.ac.NewDriveItemDeltaPager(driveID, link, fields)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) AugmentItemInfo(
|
||||
dii details.ItemInfo,
|
||||
item models.DriveItemable,
|
||||
size int64,
|
||||
parentPath *path.Builder,
|
||||
) details.ItemInfo {
|
||||
return augmentGroupItemInfo(dii, item, size, parentPath)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) FormatDisplayPath(
|
||||
driveName string,
|
||||
pb *path.Builder,
|
||||
) string {
|
||||
return "/" + driveName + "/" + pb.String()
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) NewLocationIDer(
|
||||
driveID string,
|
||||
elems ...string,
|
||||
) details.LocationIDer {
|
||||
return details.NewSharePointLocationIDer(driveID, elems...)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) GetItemPermission(
|
||||
ctx context.Context,
|
||||
driveID, itemID string,
|
||||
) (models.PermissionCollectionResponseable, error) {
|
||||
return h.ac.GetItemPermission(ctx, driveID, itemID)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) GetItem(
|
||||
ctx context.Context,
|
||||
driveID, itemID string,
|
||||
) (models.DriveItemable, error) {
|
||||
return h.ac.GetItem(ctx, driveID, itemID)
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) IsAllPass() bool {
|
||||
// TODO(meain)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h groupBackupHandler) IncludesDir(dir string) bool {
|
||||
// TODO(meain)
|
||||
// return h.scope.Matches(selectors.SharePointGroupFolder, dir)
|
||||
return true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Common
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func augmentGroupItemInfo(
|
||||
dii details.ItemInfo,
|
||||
item models.DriveItemable,
|
||||
size int64,
|
||||
parentPath *path.Builder,
|
||||
) details.ItemInfo {
|
||||
var driveName, driveID, creatorEmail string
|
||||
|
||||
// TODO: we rely on this info for details/restore lookups,
|
||||
// so if it's nil we have an issue, and will need an alternative
|
||||
// way to source the data.
|
||||
|
||||
if item.GetCreatedBy() != nil && item.GetCreatedBy().GetUser() != nil {
|
||||
// User is sometimes not available when created via some
|
||||
// external applications (like backup/restore solutions)
|
||||
additionalData := item.GetCreatedBy().GetUser().GetAdditionalData()
|
||||
|
||||
ed, ok := additionalData["email"]
|
||||
if !ok {
|
||||
ed = additionalData["displayName"]
|
||||
}
|
||||
|
||||
if ed != nil {
|
||||
creatorEmail = *ed.(*string)
|
||||
}
|
||||
}
|
||||
|
||||
// gsi := item.GetSharepointIds()
|
||||
// if gsi != nil {
|
||||
// siteID = ptr.Val(gsi.GetSiteId())
|
||||
// weburl = ptr.Val(gsi.GetSiteUrl())
|
||||
|
||||
// if len(weburl) == 0 {
|
||||
// weburl = constructWebURL(item.GetAdditionalData())
|
||||
// }
|
||||
// }
|
||||
|
||||
if item.GetParentReference() != nil {
|
||||
driveID = ptr.Val(item.GetParentReference().GetDriveId())
|
||||
driveName = strings.TrimSpace(ptr.Val(item.GetParentReference().GetName()))
|
||||
}
|
||||
|
||||
var pps string
|
||||
if parentPath != nil {
|
||||
pps = parentPath.String()
|
||||
}
|
||||
|
||||
dii.Groups = &details.GroupsInfo{
|
||||
Created: ptr.Val(item.GetCreatedDateTime()),
|
||||
DriveID: driveID,
|
||||
DriveName: driveName,
|
||||
ItemName: ptr.Val(item.GetName()),
|
||||
ItemType: details.SharePointLibrary,
|
||||
Modified: ptr.Val(item.GetLastModifiedDateTime()),
|
||||
Owner: creatorEmail,
|
||||
ParentPath: pps,
|
||||
Size: size,
|
||||
}
|
||||
|
||||
dii.Extension = &details.ExtensionData{}
|
||||
|
||||
return dii
|
||||
}
|
||||
@ -25,10 +25,9 @@ import (
|
||||
func CollectLibraries(
|
||||
ctx context.Context,
|
||||
bpc inject.BackupProducerConfig,
|
||||
ad api.Drives,
|
||||
bh drive.BackupHandler,
|
||||
tenantID string,
|
||||
ssmb *prefixmatcher.StringSetMatchBuilder,
|
||||
scope selectors.SharePointScope,
|
||||
su support.StatusUpdater,
|
||||
errs *fault.Bus,
|
||||
) ([]data.BackupCollection, bool, error) {
|
||||
@ -37,7 +36,7 @@ func CollectLibraries(
|
||||
var (
|
||||
collections = []data.BackupCollection{}
|
||||
colls = drive.NewCollections(
|
||||
drive.NewLibraryBackupHandler(ad, scope),
|
||||
bh,
|
||||
tenantID,
|
||||
bpc.ProtectedResource.ID(),
|
||||
su,
|
||||
|
||||
@ -170,6 +170,8 @@ func getResourceClient(rc resource.Category, ac api.Client) (*resourceClient, er
|
||||
return &resourceClient{enum: rc, getter: ac.Users()}, nil
|
||||
case resource.Sites:
|
||||
return &resourceClient{enum: rc, getter: ac.Sites()}, nil
|
||||
case resource.Groups:
|
||||
return &resourceClient{enum: rc, getter: ac.Groups()}, nil
|
||||
default:
|
||||
return nil, clues.New("unrecognized owner resource enum").With("resource_enum", rc)
|
||||
}
|
||||
|
||||
@ -6,4 +6,5 @@ const (
|
||||
UnknownResource Category = ""
|
||||
Users Category = "users"
|
||||
Sites Category = "sites"
|
||||
Groups Category = "groups"
|
||||
)
|
||||
|
||||
@ -5,8 +5,12 @@ import (
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/idname"
|
||||
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/site"
|
||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
@ -56,7 +60,41 @@ func ProduceBackupCollections(
|
||||
var dbcs []data.BackupCollection
|
||||
|
||||
switch scope.Category().PathType() {
|
||||
case path.LibrariesCategory: // TODO
|
||||
case path.LibrariesCategory:
|
||||
// TODO(meain): Private channels get a separate SharePoint
|
||||
// site. We should also back those up and not just the
|
||||
// default one.
|
||||
resp, err := ac.Stable.
|
||||
Client().
|
||||
Groups().
|
||||
ByGroupId(bpc.ProtectedResource.ID()).
|
||||
Sites().
|
||||
BySiteId("root").
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, nil, false, clues.Wrap(err, "getting root site for group")
|
||||
}
|
||||
|
||||
pr := idname.NewProvider(ptr.Val(resp.GetId()), ptr.Val(resp.GetName()))
|
||||
sbpc := inject.BackupProducerConfig{
|
||||
LastBackupVersion: bpc.LastBackupVersion,
|
||||
Options: bpc.Options,
|
||||
ProtectedResource: pr,
|
||||
Selector: bpc.Selector,
|
||||
}
|
||||
|
||||
dbcs, canUsePreviousBackup, err = site.CollectLibraries(
|
||||
ctx,
|
||||
sbpc,
|
||||
drive.NewGroupBackupHandler(bpc.ProtectedResource.ID(), ac.Drives(), scope),
|
||||
creds.AzureTenantID,
|
||||
ssmb,
|
||||
su,
|
||||
errs)
|
||||
if err != nil {
|
||||
el.AddRecoverable(ctx, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
collections = append(collections, dbcs...)
|
||||
@ -70,7 +108,7 @@ func ProduceBackupCollections(
|
||||
collections,
|
||||
creds.AzureTenantID,
|
||||
bpc.ProtectedResource.ID(),
|
||||
path.UnknownService, // path.GroupsService
|
||||
path.GroupsService,
|
||||
categories,
|
||||
su,
|
||||
errs)
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/site"
|
||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
@ -79,10 +80,9 @@ func ProduceBackupCollections(
|
||||
spcs, canUsePreviousBackup, err = site.CollectLibraries(
|
||||
ctx,
|
||||
bpc,
|
||||
ac.Drives(),
|
||||
drive.NewLibraryBackupHandler(ac.Drives(), scope),
|
||||
creds.AzureTenantID,
|
||||
ssmb,
|
||||
scope,
|
||||
su,
|
||||
errs)
|
||||
if err != nil {
|
||||
|
||||
@ -30,6 +30,7 @@ const (
|
||||
TestCfgGroupID = "m365groupid"
|
||||
TestCfgUserID = "m365userid"
|
||||
TestCfgSecondaryUserID = "secondarym365userid"
|
||||
TestCfgSecondaryGroupID = "secondarym365groupid"
|
||||
TestCfgTertiaryUserID = "tertiarym365userid"
|
||||
TestCfgLoadTestUserID = "loadtestm365userid"
|
||||
TestCfgLoadTestOrgUsers = "loadtestm365orgusers"
|
||||
|
||||
@ -241,6 +241,8 @@ func (pb Builder) ToStreamStorePath(
|
||||
metadataService = OneDriveMetadataService
|
||||
case SharePointService:
|
||||
metadataService = SharePointMetadataService
|
||||
case GroupsService:
|
||||
metadataService = GroupsMetadataService
|
||||
}
|
||||
|
||||
return &dataLayerResourcePath{
|
||||
@ -282,6 +284,8 @@ func (pb Builder) ToServiceCategoryMetadataPath(
|
||||
metadataService = OneDriveMetadataService
|
||||
case SharePointService:
|
||||
metadataService = SharePointMetadataService
|
||||
case GroupsService:
|
||||
metadataService = GroupsMetadataService
|
||||
}
|
||||
|
||||
return &dataLayerResourcePath{
|
||||
@ -346,6 +350,14 @@ func (pb Builder) ToDataLayerSharePointPath(
|
||||
return pb.ToDataLayerPath(tenant, site, SharePointService, category, isItem)
|
||||
}
|
||||
|
||||
func (pb Builder) ToDataLayerGroupPath(
|
||||
tenant, group string,
|
||||
category CategoryType,
|
||||
isItem bool,
|
||||
) (Path, error) {
|
||||
return pb.ToDataLayerPath(tenant, group, GroupsService, category, isItem)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and PII Concealer Compliance
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -73,6 +73,9 @@ var serviceCategories = map[ServiceType]map[CategoryType]struct{}{
|
||||
ListsCategory: {},
|
||||
PagesCategory: {},
|
||||
},
|
||||
GroupsService: {
|
||||
LibrariesCategory: {}, // TODO(meain)
|
||||
},
|
||||
}
|
||||
|
||||
func validateServiceAndCategoryStrings(s, c string) (ServiceType, CategoryType, error) {
|
||||
|
||||
@ -13,10 +13,12 @@ var piiSafePathElems = pii.MapWithPlurals(
|
||||
UnknownService.String(),
|
||||
ExchangeService.String(),
|
||||
OneDriveService.String(),
|
||||
GroupsService.String(),
|
||||
SharePointService.String(),
|
||||
ExchangeMetadataService.String(),
|
||||
OneDriveMetadataService.String(),
|
||||
SharePointMetadataService.String(),
|
||||
GroupsMetadataService.String(),
|
||||
|
||||
// categories
|
||||
UnknownCategory.String(),
|
||||
|
||||
@ -328,6 +328,13 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
|
||||
expectedService: path.SharePointMetadataService,
|
||||
check: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "Passes",
|
||||
service: path.GroupsService,
|
||||
category: path.LibrariesCategory,
|
||||
expectedService: path.GroupsMetadataService,
|
||||
check: assert.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
"github.com/alcionai/corso/src/pkg/backup/identity"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
"github.com/alcionai/corso/src/pkg/filters"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
)
|
||||
|
||||
@ -204,8 +205,8 @@ func (s *groups) Scopes() []GroupsScope {
|
||||
// -------------------
|
||||
// Scope Factories
|
||||
|
||||
// Produces one or more Groups site scopes.
|
||||
// One scope is created per site entry.
|
||||
// Produces one or more Groups scopes.
|
||||
// One scope is created per group 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]
|
||||
@ -214,38 +215,57 @@ func (s *groups) AllData() []GroupsScope {
|
||||
|
||||
scopes = append(
|
||||
scopes,
|
||||
makeScope[GroupsScope](GroupsTODOContainer, Any()))
|
||||
makeScope[GroupsScope](GroupsLibraryFolder, Any()))
|
||||
|
||||
return scopes
|
||||
}
|
||||
|
||||
// TODO produces one or more Groups TODO scopes.
|
||||
// Library produces one or more Group library scopes, where the library
|
||||
// matches upon a given drive by ID or Name. In order to ensure library selection
|
||||
// this should always be embedded within the Filter() set; include(Library()) will
|
||||
// select all items in the library without further filtering.
|
||||
// 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]
|
||||
// Any empty slice defaults to [selectors.None]
|
||||
func (s *groups) TODO(lists []string, opts ...option) []GroupsScope {
|
||||
// If any slice is empty, it defaults to [selectors.None]
|
||||
func (s *groups) Library(library string) []GroupsScope {
|
||||
return []GroupsScope{
|
||||
makeInfoScope[GroupsScope](
|
||||
GroupsLibraryItem,
|
||||
SharePointInfoLibraryDrive, // TODO(meain)
|
||||
[]string{library},
|
||||
filters.Equal),
|
||||
}
|
||||
}
|
||||
|
||||
// LibraryFolders produces one or more SharePoint libraryFolder 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 *groups) LibraryFolders(libraryFolders []string, opts ...option) []GroupsScope {
|
||||
var (
|
||||
scopes = []GroupsScope{}
|
||||
os = append([]option{pathComparator()}, opts...)
|
||||
)
|
||||
|
||||
scopes = append(scopes, makeScope[GroupsScope](GroupsTODOContainer, lists, os...))
|
||||
scopes = append(
|
||||
scopes,
|
||||
makeScope[GroupsScope](GroupsLibraryFolder, libraryFolders, os...))
|
||||
|
||||
return scopes
|
||||
}
|
||||
|
||||
// ListTODOItemsItems produces one or more Groups TODO item scopes.
|
||||
// LibraryItems produces one or more Groups library 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]
|
||||
// options are only applied to the list scopes.
|
||||
func (s *groups) TODOItems(lists, items []string, opts ...option) []GroupsScope {
|
||||
// options are only applied to the library scopes.
|
||||
func (s *groups) LibraryItems(libraries, items []string, opts ...option) []GroupsScope {
|
||||
scopes := []GroupsScope{}
|
||||
|
||||
scopes = append(
|
||||
scopes,
|
||||
makeScope[GroupsScope](GroupsTODOItem, items, defaultItemOptions(s.Cfg)...).
|
||||
set(GroupsTODOContainer, lists, opts...))
|
||||
makeScope[GroupsScope](GroupsLibraryItem, items, defaultItemOptions(s.Cfg)...).
|
||||
set(GroupsLibraryFolder, libraries, opts...))
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -270,21 +290,27 @@ const (
|
||||
GroupsCategoryUnknown groupsCategory = ""
|
||||
|
||||
// types of data in Groups
|
||||
GroupsGroup groupsCategory = "GroupsGroup"
|
||||
GroupsTODOContainer groupsCategory = "GroupsTODOContainer"
|
||||
GroupsTODOItem groupsCategory = "GroupsTODOItem"
|
||||
GroupsGroup groupsCategory = "GroupsGroup"
|
||||
|
||||
// sharepoint
|
||||
GroupsLibraryFolder groupsCategory = "GroupsLibraryFolder"
|
||||
GroupsLibraryItem groupsCategory = "GroupsLibraryItem"
|
||||
|
||||
// messages
|
||||
// GroupsTeamChannel groupsCategory = "GroupsTeamChannel"
|
||||
// GroupsTeamChannelMessages groupsCategory = "GroupsTeamChannelMessages"
|
||||
|
||||
// details.itemInfo comparables
|
||||
|
||||
// library drive selection
|
||||
GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive"
|
||||
GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive" // TODO(meain)
|
||||
)
|
||||
|
||||
// groupsLeafProperties describes common metadata of the leaf categories
|
||||
var groupsLeafProperties = map[categorizer]leafProperty{
|
||||
GroupsTODOItem: { // the root category must be represented, even though it isn't a leaf
|
||||
pathKeys: []categorizer{GroupsTODOContainer, GroupsTODOItem},
|
||||
pathType: path.UnknownCategory,
|
||||
GroupsLibraryItem: {
|
||||
pathKeys: []categorizer{GroupsLibraryFolder, GroupsLibraryItem},
|
||||
pathType: path.LibrariesCategory,
|
||||
},
|
||||
GroupsGroup: { // the root category must be represented, even though it isn't a leaf
|
||||
pathKeys: []categorizer{GroupsGroup},
|
||||
@ -303,8 +329,8 @@ func (c groupsCategory) String() string {
|
||||
// Ex: ServiceUser.leafCat() => ServiceUser
|
||||
func (c groupsCategory) leafCat() categorizer {
|
||||
switch c {
|
||||
case GroupsTODOContainer, GroupsInfoSiteLibraryDrive:
|
||||
return GroupsTODOItem
|
||||
case GroupsLibraryFolder, GroupsLibraryItem, GroupsInfoSiteLibraryDrive:
|
||||
return GroupsLibraryItem
|
||||
}
|
||||
|
||||
return c
|
||||
@ -334,7 +360,7 @@ func (c groupsCategory) isLeaf() bool {
|
||||
// pathValues transforms the two paths to maps of identified properties.
|
||||
//
|
||||
// Example:
|
||||
// [tenantID, service, siteID, category, folder, itemID]
|
||||
// [tenantID, service, groupID, site, siteID, category, folder, itemID]
|
||||
// => {spFolder: folder, spItemID: itemID}
|
||||
func (c groupsCategory) pathValues(
|
||||
repo path.Path,
|
||||
@ -348,13 +374,13 @@ func (c groupsCategory) pathValues(
|
||||
)
|
||||
|
||||
switch c {
|
||||
case GroupsTODOContainer, GroupsTODOItem:
|
||||
case GroupsLibraryFolder, GroupsLibraryItem:
|
||||
if ent.Groups == nil {
|
||||
return nil, clues.New("no Groups ItemInfo in details")
|
||||
}
|
||||
|
||||
folderCat, itemCat = GroupsTODOContainer, GroupsTODOItem
|
||||
rFld = ent.Groups.ParentPath
|
||||
folderCat, itemCat = GroupsLibraryFolder, GroupsLibraryItem
|
||||
rFld = ent.Groups.ParentPath // TODO(meain)
|
||||
|
||||
default:
|
||||
return nil, clues.New("unrecognized groupsCategory").With("category", c)
|
||||
@ -451,7 +477,7 @@ func (s GroupsScope) set(cat groupsCategory, v []string, opts ...option) GroupsS
|
||||
os := []option{}
|
||||
|
||||
switch cat {
|
||||
case GroupsTODOContainer:
|
||||
case GroupsLibraryFolder:
|
||||
os = append(os, pathComparator())
|
||||
}
|
||||
|
||||
@ -462,10 +488,10 @@ func (s GroupsScope) set(cat groupsCategory, v []string, opts ...option) GroupsS
|
||||
func (s GroupsScope) setDefaults() {
|
||||
switch s.Category() {
|
||||
case GroupsGroup:
|
||||
s[GroupsTODOContainer.String()] = passAny
|
||||
s[GroupsTODOItem.String()] = passAny
|
||||
case GroupsTODOContainer:
|
||||
s[GroupsTODOItem.String()] = passAny
|
||||
s[GroupsLibraryFolder.String()] = passAny
|
||||
s[GroupsLibraryItem.String()] = passAny
|
||||
case GroupsLibraryFolder:
|
||||
s[GroupsLibraryItem.String()] = passAny
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,7 +511,7 @@ func (s groups) Reduce(
|
||||
deets,
|
||||
s.Selector,
|
||||
map[path.CategoryType]groupsCategory{
|
||||
path.UnknownCategory: GroupsTODOItem,
|
||||
path.LibrariesCategory: GroupsLibraryItem,
|
||||
},
|
||||
errs)
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ var serviceToPathType = map[service]path.ServiceType{
|
||||
ServiceExchange: path.ExchangeService,
|
||||
ServiceOneDrive: path.OneDriveService,
|
||||
ServiceSharePoint: path.SharePointService,
|
||||
ServiceGroups: path.GroupsService,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/common/str"
|
||||
"github.com/alcionai/corso/src/internal/common/tform"
|
||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||
@ -27,7 +28,7 @@ func (c Client) Groups() Groups {
|
||||
return Groups{c}
|
||||
}
|
||||
|
||||
// On creation of each Teams team a corrsponding group gets created.
|
||||
// On creation of each Teams team a corresponding group gets created.
|
||||
// The group acts as the protected resource, and all teams data like events,
|
||||
// drive and mail messages are owned by that group.
|
||||
|
||||
@ -167,3 +168,14 @@ func IsTeam(ctx context.Context, mg models.Groupable) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetIDAndName looks up the group matching the given ID, and returns
|
||||
// its canonical ID and the name.
|
||||
func (c Groups) GetIDAndName(ctx context.Context, groupID string) (string, string, error) {
|
||||
s, err := c.GetByID(ctx, groupID)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return ptr.Val(s.GetId()), ptr.Val(s.GetDisplayName()), nil
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ func (suite *GroupsIntgSuite) TestGetAll() {
|
||||
Groups().
|
||||
GetAll(ctx, fault.New(true))
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, len(groups), "must find at least one group")
|
||||
require.NotZero(t, len(groups), "must have at least one group")
|
||||
}
|
||||
|
||||
func (suite *GroupsIntgSuite) TestGroups_GetByID() {
|
||||
@ -122,34 +122,19 @@ func (suite *GroupsIntgSuite) TestGroups_GetByID() {
|
||||
expectErr func(*testing.T, error)
|
||||
}{
|
||||
{
|
||||
name: "3 part id",
|
||||
name: "valid id",
|
||||
id: groupID,
|
||||
expectErr: func(t *testing.T, err error) {
|
||||
assert.NoError(t, err, clues.ToCore(err))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed id",
|
||||
name: "invalid id",
|
||||
id: uuid.NewString(),
|
||||
expectErr: func(t *testing.T, err error) {
|
||||
assert.Error(t, err, clues.ToCore(err))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "random id",
|
||||
id: uuid.NewString() + "," + uuid.NewString(),
|
||||
expectErr: func(t *testing.T, err error) {
|
||||
assert.Error(t, err, clues.ToCore(err))
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "malformed url",
|
||||
id: "barunihlda",
|
||||
expectErr: func(t *testing.T, err error) {
|
||||
assert.Error(t, err, clues.ToCore(err))
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user