Skip to content

Commit

Permalink
implement lists restore handler (#4854)
Browse files Browse the repository at this point in the history
adds lists restore handler to:
- create list
- create list items
- delete list

#### 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
  • Loading branch information
HiteshRepo authored Dec 15, 2023
1 parent bad10a4 commit 494bace
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 122 deletions.
3 changes: 2 additions & 1 deletion src/internal/m365/collection/site/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ func (suite *SharePointCollectionSuite) TestListCollection_Restore() {

destName := testdata.DefaultRestoreConfig("").Location

deets, err := restoreListItem(ctx, service, listData, suite.siteID, destName)
lrh := NewListsRestoreHandler(suite.siteID, suite.ac.Lists())
deets, err := restoreListItem(ctx, lrh, listData, suite.siteID, destName)
assert.NoError(t, err, clues.ToCore(err))
t.Logf("List created: %s\n", deets.SharePoint.ItemName)

Expand Down
20 changes: 20 additions & 0 deletions src/internal/m365/collection/site/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,23 @@ type getItemByIDer interface {
type getItemser interface {
GetItems(ctx context.Context, cc api.CallConfig) ([]models.Listable, error)
}

type restoreHandler interface {
PostLister
DeleteLister
}

type PostLister interface {
PostList(
ctx context.Context,
listName string,
storedListData []byte,
) (models.Listable, error)
}

type DeleteLister interface {
DeleteList(
ctx context.Context,
listID string,
) error
}
29 changes: 29 additions & 0 deletions src/internal/m365/collection/site/lists_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,32 @@ func (bh listsBackupHandler) GetItemByID(ctx context.Context, itemID string) (mo
func (bh listsBackupHandler) GetItems(ctx context.Context, cc api.CallConfig) ([]models.Listable, error) {
return bh.ac.GetLists(ctx, bh.protectedResource, cc)
}

var _ restoreHandler = &listsRestoreHandler{}

type listsRestoreHandler struct {
ac api.Lists
protectedResource string
}

func NewListsRestoreHandler(protectedResource string, ac api.Lists) listsRestoreHandler {
return listsRestoreHandler{
ac: ac,
protectedResource: protectedResource,
}
}

func (rh listsRestoreHandler) PostList(
ctx context.Context,
listName string,
storedListData []byte,
) (models.Listable, error) {
return rh.ac.PostList(ctx, rh.protectedResource, listName, storedListData)
}

func (rh listsRestoreHandler) DeleteList(
ctx context.Context,
listID string,
) error {
return rh.ac.DeleteList(ctx, rh.protectedResource, listID)
}
26 changes: 22 additions & 4 deletions src/internal/m365/collection/site/mock/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,33 @@ import (
)

type ListHandler struct {
ListItem models.Listable
Err error
List models.Listable
Err error
}

func (lh *ListHandler) GetItemByID(ctx context.Context, itemID string) (models.Listable, error) {
ls := models.NewList()

lh.ListItem = ls
lh.ListItem.SetId(ptr.To(itemID))
lh.List = ls
lh.List.SetId(ptr.To(itemID))

return ls, lh.Err
}

type ListRestoreHandler struct {
List models.Listable
Err error
}

func (lh *ListRestoreHandler) PostList(
ctx context.Context,
listName string,
storedListBytes []byte,
) (models.Listable, error) {
ls := models.NewList()

lh.List = ls
lh.List.SetDisplayName(ptr.To(listName))

return lh.List, lh.Err
}
60 changes: 10 additions & 50 deletions src/internal/m365/collection/site/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import (
"runtime/trace"

"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models"

"github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/internal/m365/collection/drive"
Expand Down Expand Up @@ -42,6 +40,7 @@ func ConsumeRestoreCollections(
) (*support.ControllerOperationStatus, error) {
var (
lrh = drive.NewSiteRestoreHandler(ac, rcc.Selector.PathService())
listsRh = NewListsRestoreHandler(rcc.ProtectedResource.ID(), ac.Lists())
restoreMetrics support.CollectionMetrics
caches = drive.NewRestoreCaches(backupDriveIDNames)
el = errs.Local()
Expand Down Expand Up @@ -89,7 +88,7 @@ func ConsumeRestoreCollections(
case path.ListsCategory:
metrics, err = RestoreListCollection(
ictx,
ac.Stable,
listsRh,
dc,
rcc.RestoreConfig.Location,
deets,
Expand Down Expand Up @@ -135,7 +134,7 @@ func ConsumeRestoreCollections(
// Restored List can be verified within the Site contents.
func restoreListItem(
ctx context.Context,
service graph.Servicer,
rh restoreHandler,
itemData data.Item,
siteID, destName string,
) (details.ItemInfo, error) {
Expand All @@ -149,65 +148,26 @@ func restoreListItem(
listName = itemData.ID()
)

byteArray, err := io.ReadAll(itemData.ToReader())
bytes, err := io.ReadAll(itemData.ToReader())
if err != nil {
return dii, clues.WrapWC(ctx, err, "reading backup data")
}

oldList, err := api.BytesToListable(byteArray)
if err != nil {
return dii, clues.WrapWC(ctx, err, "creating item")
}

if name, ok := ptr.ValOK(oldList.GetDisplayName()); ok {
listName = name
}
newName := fmt.Sprintf("%s_%s", destName, listName)

var (
newName = fmt.Sprintf("%s_%s", destName, listName)
newList = api.ToListable(oldList, newName)
contents = make([]models.ListItemable, 0)
)

for _, itm := range oldList.GetItems() {
temp := api.CloneListItem(itm)
contents = append(contents, temp)
}

newList.SetItems(contents)

// Restore to List base to M365 back store
restoredList, err := service.Client().Sites().BySiteId(siteID).Lists().Post(ctx, newList, nil)
restoredList, err := rh.PostList(ctx, newName, bytes)
if err != nil {
return dii, graph.Wrap(ctx, err, "restoring list")
}

// Uploading of ListItems is conducted after the List is restored
// Reference: https://learn.microsoft.com/en-us/graph/api/listitem-create?view=graph-rest-1.0&tabs=http
if len(contents) > 0 {
for _, lItem := range contents {
_, err := service.Client().
Sites().
BySiteId(siteID).
Lists().
ByListId(ptr.Val(restoredList.GetId())).
Items().
Post(ctx, lItem, nil)
if err != nil {
return dii, graph.Wrap(ctx, err, "restoring list items").
With("restored_list_id", ptr.Val(restoredList.GetId()))
}
}
return dii, clues.WrapWC(ctx, err, "restoring lists")
}

dii.SharePoint = ListToSPInfo(restoredList, int64(len(byteArray)))
dii.SharePoint = ListToSPInfo(restoredList, int64(len(bytes)))

return dii, nil
}

func RestoreListCollection(
ctx context.Context,
service graph.Servicer,
rh restoreHandler,
dc data.RestoreCollection,
restoreContainerName string,
deets *details.Builder,
Expand Down Expand Up @@ -243,7 +203,7 @@ func RestoreListCollection(

itemInfo, err := restoreListItem(
ctx,
service,
rh,
itemData,
siteID,
restoreContainerName)
Expand Down
21 changes: 13 additions & 8 deletions src/internal/m365/service/sharepoint/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,13 @@ func (h *sharepointHandler) ConsumeRestoreCollections(
lrh = drive.NewSiteRestoreHandler(
h.apiClient,
rcc.Selector.PathService())
listsRh = site.NewListsRestoreHandler(
rcc.ProtectedResource.ID(),
h.apiClient.Lists())
restoreMetrics support.CollectionMetrics
caches = drive.NewRestoreCaches(h.backupDriveIDNames)
el = errs.Local()
)

err := caches.Populate(ctx, lrh, rcc.ProtectedResource.ID())
if err != nil {
return nil, nil, clues.Wrap(err, "initializing restore caches")
}
el = errs.Local()
)

// Reorder collections so that the parents directories are created
// before the child directories; a requirement for permissions.
Expand All @@ -81,6 +79,13 @@ func (h *sharepointHandler) ConsumeRestoreCollections(

switch dc.FullPath().Category() {
case path.LibrariesCategory:
caches := drive.NewRestoreCaches(h.backupDriveIDNames)

err = caches.Populate(ctx, lrh, rcc.ProtectedResource.ID())
if err != nil {
return nil, nil, clues.Wrap(err, "initializing restore caches")
}

metrics, err = drive.RestoreCollection(
ictx,
lrh,
Expand All @@ -95,7 +100,7 @@ func (h *sharepointHandler) ConsumeRestoreCollections(
case path.ListsCategory:
metrics, err = site.RestoreListCollection(
ictx,
h.apiClient.Stable,
listsRh,
dc,
rcc.RestoreConfig.Location,
deets,
Expand Down
63 changes: 63 additions & 0 deletions src/internal/m365/service/sharepoint/restore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sharepoint

import (
"testing"

"github.com/alcionai/clues"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/data/mock"
"github.com/alcionai/corso/src/internal/operations/inject"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)

type SharepointRestoreUnitSuite struct {
tester.Suite
}

func TestSharepointRestoreUnitSuite(t *testing.T) {
suite.Run(t, &SharepointRestoreUnitSuite{Suite: tester.NewUnitSuite(t)})
}

func (suite *SharepointRestoreUnitSuite) TestSharePointHandler_ConsumeRestoreCollections_noErrorOnLists() {
t := suite.T()
siteID := "site-id"

ctx, flush := tester.NewContext(t)
defer flush()

pr := idname.NewProvider(siteID, siteID)
rcc := inject.RestoreConsumerConfig{
ProtectedResource: pr,
}
pth, err := path.Builder{}.
Append("lists").
ToDataLayerPath(
"tenant",
siteID,
path.SharePointService,
path.ListsCategory,
false)
require.NoError(t, err, clues.ToCore(err))

dcs := []data.RestoreCollection{
mock.Collection{Path: pth},
}

sh := NewSharePointHandler(control.DefaultOptions(), api.Client{}, nil)

_, _, err = sh.ConsumeRestoreCollections(
ctx,
rcc,
dcs,
fault.New(false),
nil)
require.NoError(t, err, "Sharepoint lists restore")
}
Loading

0 comments on commit 494bace

Please sign in to comment.