adds ListInfo details to SharepointInfo details (#4916)

adds ListInfo details to SharepointInfo details
Changes previously approved in PR: https://github.com/alcionai/corso/pull/4851

#### Does this PR need a docs update or release note?
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature

#### Issue(s)
#4754 

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Hitesh Pattanayak 2023-12-22 12:20:34 +05:30 committed by GitHub
parent c3b15a00bd
commit 50fd28aab2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 31 deletions

View File

@ -942,7 +942,7 @@ var defaultOneDriveLocationIDer = func(driveID string, elems ...string) details.
} }
var defaultSharePointLocationIDer = func(driveID string, elems ...string) details.LocationIDer { var defaultSharePointLocationIDer = func(driveID string, elems ...string) details.LocationIDer {
return details.NewSharePointLocationIDer(driveID, elems...) return details.NewSharePointLocationIDer(path.LibrariesCategory, driveID, elems...)
} }
func (h mockBackupHandler[T]) IsAllPass() bool { func (h mockBackupHandler[T]) IsAllPass() bool {

View File

@ -149,7 +149,8 @@ func (h siteBackupHandler) NewLocationIDer(
driveID string, driveID string,
elems ...string, elems ...string,
) details.LocationIDer { ) details.LocationIDer {
return details.NewSharePointLocationIDer(driveID, elems...) _, cat := h.ServiceCat()
return details.NewSharePointLocationIDer(cat, driveID, elems...)
} }
func (h siteBackupHandler) GetItemPermission( func (h siteBackupHandler) GetItemPermission(

View File

@ -296,7 +296,7 @@ var defaultOneDriveLocationIDer = func(driveID string, elems ...string) details.
} }
var defaultSharePointLocationIDer = func(driveID string, elems ...string) details.LocationIDer { var defaultSharePointLocationIDer = func(driveID string, elems ...string) details.LocationIDer {
return details.NewSharePointLocationIDer(driveID, elems...) return details.NewSharePointLocationIDer(path.LibrariesCategory, driveID, elems...)
} }
func (h BackupHandler[T]) IsAllPass() bool { func (h BackupHandler[T]) IsAllPass() bool {

View File

@ -116,7 +116,7 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
expectVs: []string{"deadbeef", "sender", "Parent", "subject", nowStr}, expectVs: []string{"deadbeef", "sender", "Parent", "subject", nowStr},
}, },
{ {
name: "sharepoint info", name: "sharepoint library info",
entry: Entry{ entry: Entry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
@ -125,6 +125,7 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{ SharePoint: &SharePointInfo{
ItemName: "itemName", ItemName: "itemName",
ItemType: SharePointLibrary,
ParentPath: "parentPath", ParentPath: "parentPath",
Size: 1000, Size: 1000,
WebURL: "https://not.a.real/url", WebURL: "https://not.a.real/url",
@ -147,6 +148,66 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
nowStr, nowStr,
}, },
}, },
{
name: "sharepoint list info for genericList template",
entry: Entry{
RepoRef: "reporef",
ShortRef: "deadbeef",
LocationRef: "locationref",
ItemRef: "itemref",
ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{
ItemType: SharePointList,
List: &ListInfo{
Name: "list1",
ItemCount: 50,
Template: "genericList",
Created: now,
Modified: now,
WebURL: "https://10rqc2.sharepoint.com/sites/site-4754-small-lists/Lists/list1",
},
},
},
},
expectHs: []string{"ID", "List", "Items", "Created", "Modified"},
expectVs: []string{
"deadbeef",
"list1",
"50",
nowStr,
nowStr,
},
},
{
name: "sharepoint list info for documentLibrary template",
entry: Entry{
RepoRef: "reporef",
ShortRef: "deadbeef",
LocationRef: "locationref",
ItemRef: "itemref",
ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{
ItemType: SharePointList,
List: &ListInfo{
Name: "Shared%20Documents",
ItemCount: 50,
Template: "documentLibrary",
Created: now,
Modified: now,
WebURL: "https://10rqc2.sharepoint.com/sites/site-4754-small-lists/Lists/Shared%20Documents",
},
},
},
},
expectHs: []string{"ID", "List", "Items", "Created", "Modified"},
expectVs: []string{
"deadbeef",
"Shared%20Documents",
"50",
nowStr,
nowStr,
},
},
{ {
name: "oneDrive info", name: "oneDrive info",
entry: Entry{ entry: Entry{
@ -846,6 +907,34 @@ var pathItemsTable = []struct {
expectRepoRefs: []string{"abcde", "12345", "foo.meta"}, expectRepoRefs: []string{"abcde", "12345", "foo.meta"},
expectLocationRefs: []string{"locationref", "locationref2", "locationref.dirmeta"}, expectLocationRefs: []string{"locationref", "locationref2", "locationref.dirmeta"},
}, },
{
name: "multiple entries with not recoverables",
ents: []Entry{
{
RepoRef: "abcde",
LocationRef: "locationref",
},
{
RepoRef: "foo.meta",
LocationRef: "locationref.dirmeta",
ItemRef: "itemref.meta",
ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{ItemType: SharePointList},
},
},
{
RepoRef: "invalid-template-list-file",
LocationRef: "locationref-invalid-template-list-file",
ItemRef: "itemref-template-list-file",
ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{ItemType: SharePointList},
NotRecoverable: true,
},
},
},
expectRepoRefs: []string{"abcde", "foo.meta"},
expectLocationRefs: []string{"locationref", "locationref.dirmeta"},
},
} }
func (suite *DetailsUnitSuite) TestDetailsModel_Path() { func (suite *DetailsUnitSuite) TestDetailsModel_Path() {
@ -1277,12 +1366,16 @@ func (suite *DetailsUnitSuite) TestUnarshalTo() {
func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() { func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() {
const ( const (
rrString = "tenant-id/%s/user-id/%s/drives/drive-id/root:/some/folder/stuff/item" rrString = "tenant-id/%s/user-id/%s/drives/drive-id/root:/some/folder/stuff/item"
driveID = "driveID" listsRrString = "tenant-id/%s/site-id/%s/lists/list-id/list-id"
driveID = "driveID"
listID = "listID"
expectedUniqueLocFmt = "%s/" + driveID + "/root:/some/folder/stuff" expectedUniqueLocFmt = "%s/" + driveID + "/root:/some/folder/stuff"
expectedExchangeUniqueLocFmt = "%s/root:/some/folder/stuff" expectedExchangeUniqueLocFmt = "%s/root:/some/folder/stuff"
expectedDetailsLoc = "root:/some/folder/stuff" expectedDetailsLoc = "root:/some/folder/stuff"
expectedListUniqueLocFmt = "%s/" + listID
expectedListDetailsLoc = listID
) )
table := []struct { table := []struct {
@ -1379,6 +1472,21 @@ func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() {
backupVersion: version.OneDrive7LocationRef, backupVersion: version.OneDrive7LocationRef,
expectedErr: require.Error, expectedErr: require.Error,
}, },
{
name: "SharePoint List With LocationRef",
service: path.SharePointService.String(),
category: path.ListsCategory.String(),
itemInfo: ItemInfo{
SharePoint: &SharePointInfo{
ItemType: SharePointList,
List: &ListInfo{},
},
},
backupVersion: version.OneDrive7LocationRef,
hasLocRef: true,
expectedErr: require.NoError,
expectedUniqueLoc: fmt.Sprintf(expectedListUniqueLocFmt, path.ListsCategory),
},
{ {
name: "Exchange Email With LocationRef Old Version", name: "Exchange Email With LocationRef Old Version",
service: path.ExchangeService.String(), service: path.ExchangeService.String(),
@ -1479,13 +1587,23 @@ func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T() t := suite.T()
rr := ""
detailsLoc := ""
if test.category == path.ListsCategory.String() {
rr = fmt.Sprintf(listsRrString, test.service, test.category)
detailsLoc = expectedListDetailsLoc
} else {
rr = fmt.Sprintf(rrString, test.service, test.category)
detailsLoc = expectedDetailsLoc
}
entry := Entry{ entry := Entry{
RepoRef: fmt.Sprintf(rrString, test.service, test.category), RepoRef: rr,
ItemInfo: test.itemInfo, ItemInfo: test.itemInfo,
} }
if test.hasLocRef { if test.hasLocRef {
entry.LocationRef = expectedDetailsLoc entry.LocationRef = detailsLoc
} }
loc, err := entry.ToLocationIDer(test.backupVersion) loc, err := entry.ToLocationIDer(test.backupVersion)
@ -1502,7 +1620,7 @@ func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() {
"unique location") "unique location")
assert.Equal( assert.Equal(
t, t,
expectedDetailsLoc, detailsLoc,
loc.InDetails().String(), loc.InDetails().String(),
"details location") "details location")
}) })

View File

@ -74,7 +74,8 @@ type ItemInfo struct {
OneDrive *OneDriveInfo `json:"oneDrive,omitempty"` OneDrive *OneDriveInfo `json:"oneDrive,omitempty"`
Groups *GroupsInfo `json:"groups,omitempty"` Groups *GroupsInfo `json:"groups,omitempty"`
// Optional item extension data // Optional item extension data
Extension *ExtensionData `json:"extension,omitempty"` Extension *ExtensionData `json:"extension,omitempty"`
NotRecoverable bool `json:"notRecoverable,omitempty"`
} }
// typedInfo should get embedded in each sesrvice type to track // typedInfo should get embedded in each sesrvice type to track

View File

@ -67,7 +67,9 @@ func (dm DetailsModel) Paths() []string {
r := make([]string, 0, len(dm.Entries)) r := make([]string, 0, len(dm.Entries))
for _, ent := range dm.Entries { for _, ent := range dm.Entries {
if ent.Folder != nil || ent.isMetaFile() { if ent.Folder != nil ||
ent.isMetaFile() ||
ent.NotRecoverable {
continue continue
} }
@ -85,7 +87,9 @@ func (dm DetailsModel) Items() entrySet {
for i := 0; i < len(dm.Entries); i++ { for i := 0; i < len(dm.Entries); i++ {
ent := dm.Entries[i] ent := dm.Entries[i]
if ent.Folder != nil || ent.isMetaFile() { if ent.Folder != nil ||
ent.isMetaFile() ||
ent.NotRecoverable {
continue continue
} }

View File

@ -1,6 +1,7 @@
package details package details
import ( import (
"fmt"
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
@ -13,16 +14,24 @@ import (
// NewSharePointLocationIDer builds a LocationIDer for the drive and folder // NewSharePointLocationIDer builds a LocationIDer for the drive and folder
// path. The path denoted by the folders should be unique within the drive. // path. The path denoted by the folders should be unique within the drive.
func NewSharePointLocationIDer( func NewSharePointLocationIDer(
category path.CategoryType,
driveID string, driveID string,
escapedFolders ...string, escapedFolders ...string,
) uniqueLoc { ) uniqueLoc {
pb := path.Builder{}. pb := path.Builder{}.Append(category.String())
Append(path.LibrariesCategory.String(), driveID). prefixElems := 1
Append(escapedFolders...)
if len(driveID) > 0 { // for library category
pb = pb.Append(driveID)
prefixElems = 2
}
pb = pb.Append(escapedFolders...)
return uniqueLoc{ return uniqueLoc{
pb: pb, pb: pb,
prefixElems: 2, prefixElems: prefixElems,
} }
} }
@ -39,26 +48,55 @@ type SharePointInfo struct {
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
WebURL string `json:"webUrl,omitempty"` WebURL string `json:"webUrl,omitempty"`
SiteID string `json:"siteID,omitempty"` SiteID string `json:"siteID,omitempty"`
List *ListInfo `json:"list,omitempty"`
}
type ListInfo struct {
Name string `json:"name,omitempty"`
ItemCount int64 `json:"itemCount,omitempty"`
Template string `json:"template,omitempty"`
WebURL string `json:"webUrl,omitempty"`
Created time.Time `json:"created,omitempty"`
Modified time.Time `json:"modified,omitempty"`
} }
// Headers returns the human-readable names of properties in a SharePointInfo // Headers returns the human-readable names of properties in a SharePointInfo
// for printing out to a terminal in a columnar display. // for printing out to a terminal in a columnar display.
func (i SharePointInfo) Headers() []string { func (i SharePointInfo) Headers() []string {
return []string{"ItemName", "Library", "ParentPath", "Size", "Owner", "Created", "Modified"} switch i.ItemType {
case SharePointLibrary:
return []string{"ItemName", "Library", "ParentPath", "Size", "Owner", "Created", "Modified"}
case SharePointList:
return []string{"List", "Items", "Created", "Modified"}
}
return []string{}
} }
// Values returns the values matching the Headers list for printing // Values returns the values matching the Headers list for printing
// out to a terminal in a columnar display. // out to a terminal in a columnar display.
func (i SharePointInfo) Values() []string { func (i SharePointInfo) Values() []string {
return []string{ switch i.ItemType {
i.ItemName, case SharePointLibrary:
i.DriveName, return []string{
i.ParentPath, i.ItemName,
humanize.Bytes(uint64(i.Size)), i.DriveName,
i.Owner, i.ParentPath,
dttm.FormatToTabularDisplay(i.Created), humanize.Bytes(uint64(i.Size)),
dttm.FormatToTabularDisplay(i.Modified), i.Owner,
dttm.FormatToTabularDisplay(i.Created),
dttm.FormatToTabularDisplay(i.Modified),
}
case SharePointList:
return []string{
i.List.Name,
fmt.Sprintf("%d", i.List.ItemCount),
dttm.FormatToTabularDisplay(i.List.Created),
dttm.FormatToTabularDisplay(i.List.Modified),
}
} }
return []string{}
} }
func (i *SharePointInfo) UpdateParentPath(newLocPath *path.Builder) { func (i *SharePointInfo) UpdateParentPath(newLocPath *path.Builder) {
@ -66,11 +104,14 @@ func (i *SharePointInfo) UpdateParentPath(newLocPath *path.Builder) {
} }
func (i *SharePointInfo) uniqueLocation(baseLoc *path.Builder) (*uniqueLoc, error) { func (i *SharePointInfo) uniqueLocation(baseLoc *path.Builder) (*uniqueLoc, error) {
if len(i.DriveID) == 0 { loc := uniqueLoc{}
return nil, clues.New("empty drive ID")
}
loc := NewSharePointLocationIDer(i.DriveID, baseLoc.Elements()...) switch i.ItemType {
case SharePointLibrary, OneDriveItem:
loc = NewSharePointLocationIDer(path.LibrariesCategory, i.DriveID, baseLoc.Elements()...)
case SharePointList:
loc = NewSharePointLocationIDer(path.ListsCategory, "", baseLoc.Elements()...)
}
return &loc, nil return &loc, nil
} }
@ -78,8 +119,11 @@ func (i *SharePointInfo) uniqueLocation(baseLoc *path.Builder) (*uniqueLoc, erro
func (i *SharePointInfo) updateFolder(f *FolderInfo) error { func (i *SharePointInfo) updateFolder(f *FolderInfo) error {
// TODO(ashmrtn): Change to just SharePointLibrary when the code that // TODO(ashmrtn): Change to just SharePointLibrary when the code that
// generates the item type is fixed. // generates the item type is fixed.
if i.ItemType == OneDriveItem || i.ItemType == SharePointLibrary { switch i.ItemType {
case OneDriveItem, SharePointLibrary:
return updateFolderWithinDrive(SharePointLibrary, i.DriveName, i.DriveID, f) return updateFolderWithinDrive(SharePointLibrary, i.DriveName, i.DriveID, f)
case SharePointList:
return nil
} }
return clues.New("unsupported non-SharePoint ItemType").With("item_type", i.ItemType) return clues.New("unsupported non-SharePoint ItemType").With("item_type", i.ItemType)

View File

@ -6,12 +6,13 @@ import (
"io" "io"
"testing" "testing"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go" kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/alcionai/corso/src/internal/common/ptr"
) )
func ODataErr(code string) *odataerrors.ODataError { func ODataErr(code string) *odataerrors.ODataError {