GC: Restore: SharePoint: Page Logic (#2225)
## Description Restore logic for restoring a SharePoint Page to M365 given a valid `[]byte`. Delete API also included Tests included <!-- Insert PR description--> ## 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) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * related to #2169<issue> ## Test Plan <!-- How will this be tested prior to merging.--> - [x] ⚡ Unit test Must be tested locally due to CI Library issues, See #2086. Clean-up is handled within the tests.
This commit is contained in:
parent
ac8fe1e9c1
commit
d82b5cacdf
@ -1,15 +1,15 @@
|
||||
package api
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||
discover "github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestBetaService(t *testing.T, credentials account.M365Config) *api.BetaService {
|
||||
func createTestBetaService(t *testing.T, credentials account.M365Config) *discover.BetaService {
|
||||
adapter, err := graph.CreateAdapter(
|
||||
credentials.AzureTenantID,
|
||||
credentials.AzureClientID,
|
||||
@ -17,5 +17,5 @@ func createTestBetaService(t *testing.T, credentials account.M365Config) *api.Be
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return api.NewBetaService(adapter)
|
||||
return discover.NewBetaService(adapter)
|
||||
}
|
||||
|
||||
@ -2,18 +2,26 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
discover "github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph/betasdk/sites"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
D "github.com/alcionai/corso/src/internal/diagnostics"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
)
|
||||
|
||||
// GetSitePages retrieves a collection of Pages related to the give Site.
|
||||
// Returns error if error experienced during the call
|
||||
func GetSitePage(
|
||||
ctx context.Context,
|
||||
serv *api.BetaService,
|
||||
serv *discover.BetaService,
|
||||
siteID string,
|
||||
pages []string,
|
||||
) ([]models.SitePageable, error) {
|
||||
@ -33,7 +41,7 @@ func GetSitePage(
|
||||
}
|
||||
|
||||
// fetchPages utility function to return the tuple of item
|
||||
func FetchPages(ctx context.Context, bs *api.BetaService, siteID string) ([]Tuple, error) {
|
||||
func FetchPages(ctx context.Context, bs *discover.BetaService, siteID string) ([]Tuple, error) {
|
||||
var (
|
||||
builder = bs.Client().SitesById(siteID).Pages()
|
||||
opts = fetchPageOptions()
|
||||
@ -80,6 +88,21 @@ func fetchPageOptions() *sites.ItemPagesRequestBuilderGetRequestConfiguration {
|
||||
return options
|
||||
}
|
||||
|
||||
// DeleteSitePage removes the selected page from the SharePoint Site
|
||||
// https://learn.microsoft.com/en-us/graph/api/sitepage-delete?view=graph-rest-beta
|
||||
func DeleteSitePage(
|
||||
ctx context.Context,
|
||||
serv *discover.BetaService,
|
||||
siteID, pageID string,
|
||||
) error {
|
||||
err := serv.Client().SitesById(siteID).PagesById(pageID).Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return support.ConnectorStackErrorTraceWrap(err, "deleting page: "+pageID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// retrievePageOptions returns options to expand
|
||||
func retrieveSitePageOptions() *sites.ItemPagesSitePageItemRequestBuilderGetRequestConfiguration {
|
||||
fields := []string{"canvasLayout"}
|
||||
@ -91,3 +114,113 @@ func retrieveSitePageOptions() *sites.ItemPagesSitePageItemRequestBuilderGetRequ
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func RestoreSitePage(
|
||||
ctx context.Context,
|
||||
service *discover.BetaService,
|
||||
itemData data.Stream,
|
||||
siteID, destName string,
|
||||
) (details.ItemInfo, error) {
|
||||
ctx, end := D.Span(ctx, "gc:sharepoint:restorePage", D.Label("item_uuid", itemData.UUID()))
|
||||
defer end()
|
||||
|
||||
var (
|
||||
dii = details.ItemInfo{}
|
||||
pageID = itemData.UUID()
|
||||
pageName = pageID
|
||||
)
|
||||
|
||||
byteArray, err := io.ReadAll(itemData.ToReader())
|
||||
if err != nil {
|
||||
return dii, errors.Wrap(err, "reading sharepoint page bytes from stream")
|
||||
}
|
||||
|
||||
// Hydrate Page
|
||||
page, err := support.CreatePageFromBytes(byteArray)
|
||||
if err != nil {
|
||||
return dii, errors.Wrapf(err, "creating Page object %s", pageID)
|
||||
}
|
||||
|
||||
pageNamePtr := page.GetName()
|
||||
if pageNamePtr != nil {
|
||||
pageName = *pageNamePtr
|
||||
}
|
||||
|
||||
newName := fmt.Sprintf("%s_%s", destName, pageName)
|
||||
page.SetName(&newName)
|
||||
|
||||
// Restore is a 2-Step Process in Graph API
|
||||
// 1. Create the Page on the site
|
||||
// 2. Publish the site
|
||||
// See: https://learn.microsoft.com/en-us/graph/api/sitepage-create?view=graph-rest-beta
|
||||
restoredPage, err := service.Client().SitesById(siteID).Pages().Post(ctx, page, nil)
|
||||
if err != nil {
|
||||
sendErr := support.ConnectorStackErrorTraceWrap(
|
||||
err,
|
||||
"creating page from ID: %s"+pageName+" API Error Details",
|
||||
)
|
||||
|
||||
return dii, sendErr
|
||||
}
|
||||
|
||||
pageID = *restoredPage.GetId()
|
||||
// Publish page to make visible
|
||||
// See https://learn.microsoft.com/en-us/graph/api/sitepage-publish?view=graph-rest-beta
|
||||
if restoredPage.GetWebUrl() == nil {
|
||||
return dii, fmt.Errorf("creating page %s incomplete. Field `webURL` not populated", pageID)
|
||||
}
|
||||
|
||||
err = service.Client().
|
||||
SitesById(siteID).
|
||||
PagesById(pageID).
|
||||
Publish().
|
||||
Post(ctx, nil)
|
||||
if err != nil {
|
||||
return dii, support.ConnectorStackErrorTraceWrap(
|
||||
err,
|
||||
"publishing page ID: "+*restoredPage.GetId()+" API Error Details",
|
||||
)
|
||||
}
|
||||
|
||||
dii.SharePoint = PageInfo(restoredPage, int64(len(byteArray)))
|
||||
// Storing new pageID in unused field.
|
||||
dii.SharePoint.ParentPath = pageID
|
||||
|
||||
return dii, nil
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// Helpers
|
||||
// ==============================
|
||||
// PageInfo extracts useful metadata into struct for book keeping
|
||||
func PageInfo(page models.SitePageable, size int64) *details.SharePointInfo {
|
||||
var (
|
||||
name, webURL string
|
||||
created, modified time.Time
|
||||
)
|
||||
|
||||
if page.GetTitle() != nil {
|
||||
name = *page.GetTitle()
|
||||
}
|
||||
|
||||
if page.GetWebUrl() != nil {
|
||||
webURL = *page.GetWebUrl()
|
||||
}
|
||||
|
||||
if page.GetCreatedDateTime() != nil {
|
||||
created = *page.GetCreatedDateTime()
|
||||
}
|
||||
|
||||
if page.GetLastModifiedDateTime() != nil {
|
||||
modified = *page.GetLastModifiedDateTime()
|
||||
}
|
||||
|
||||
return &details.SharePointInfo{
|
||||
ItemType: details.SharePointItem,
|
||||
ItemName: name,
|
||||
Created: created,
|
||||
Modified: modified,
|
||||
WebURL: webURL,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,28 @@
|
||||
package api
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common"
|
||||
discover "github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||
"github.com/alcionai/corso/src/internal/connector/mockconnector"
|
||||
"github.com/alcionai/corso/src/internal/connector/sharepoint"
|
||||
"github.com/alcionai/corso/src/internal/connector/sharepoint/api"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
)
|
||||
|
||||
type SharePointPageSuite struct {
|
||||
suite.Suite
|
||||
siteID string
|
||||
creds account.M365Config
|
||||
siteID string
|
||||
creds account.M365Config
|
||||
service *discover.BetaService
|
||||
}
|
||||
|
||||
func (suite *SharePointPageSuite) SetupSuite() {
|
||||
@ -27,6 +35,7 @@ func (suite *SharePointPageSuite) SetupSuite() {
|
||||
require.NoError(t, err)
|
||||
|
||||
suite.creds = m365
|
||||
suite.service = createTestBetaService(t, suite.creds)
|
||||
}
|
||||
|
||||
func TestSharePointPageSuite(t *testing.T) {
|
||||
@ -42,9 +51,7 @@ func (suite *SharePointPageSuite) TestFetchPages() {
|
||||
defer flush()
|
||||
|
||||
t := suite.T()
|
||||
service := createTestBetaService(t, suite.creds)
|
||||
|
||||
pgs, err := FetchPages(ctx, service, suite.siteID)
|
||||
pgs, err := api.FetchPages(ctx, suite.service, suite.siteID)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, pgs)
|
||||
assert.NotZero(t, len(pgs))
|
||||
@ -59,13 +66,47 @@ func (suite *SharePointPageSuite) TestGetSitePage() {
|
||||
defer flush()
|
||||
|
||||
t := suite.T()
|
||||
service := createTestBetaService(t, suite.creds)
|
||||
tuples, err := FetchPages(ctx, service, suite.siteID)
|
||||
tuples, err := api.FetchPages(ctx, suite.service, suite.siteID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, tuples)
|
||||
|
||||
jobs := []string{tuples[0].ID}
|
||||
pages, err := GetSitePage(ctx, service, suite.siteID, jobs)
|
||||
pages, err := api.GetSitePage(ctx, suite.service, suite.siteID, jobs)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, pages)
|
||||
}
|
||||
|
||||
func (suite *SharePointPageSuite) TestRestoreSinglePage() {
|
||||
ctx, flush := tester.NewContext()
|
||||
defer flush()
|
||||
|
||||
t := suite.T()
|
||||
|
||||
destName := "Corso_Restore_" + common.FormatNow(common.SimpleTimeTesting)
|
||||
testName := "MockPage"
|
||||
|
||||
// Create Test Page
|
||||
//nolint:lll
|
||||
byteArray := mockconnector.GetMockPage("Byte Test")
|
||||
|
||||
pageData := sharepoint.NewItem(
|
||||
testName,
|
||||
io.NopCloser(bytes.NewReader(byteArray)),
|
||||
)
|
||||
|
||||
info, err := api.RestoreSitePage(
|
||||
ctx,
|
||||
suite.service,
|
||||
pageData,
|
||||
suite.siteID,
|
||||
destName,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, info)
|
||||
|
||||
// Clean Up
|
||||
pageID := info.SharePoint.ParentPath
|
||||
err = api.DeleteSitePage(ctx, suite.service, suite.siteID, pageID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -106,6 +106,15 @@ type Item struct {
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func NewItem(name string, d io.ReadCloser) *Item {
|
||||
item := &Item{
|
||||
id: name,
|
||||
data: d,
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func (sd *Item) UUID() string {
|
||||
return sd.id
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user