From 48cb751ee06406e9764e4d118d303fcc4c0ccea7 Mon Sep 17 00:00:00 2001 From: Keepers Date: Mon, 17 Oct 2022 18:17:13 -0600 Subject: [PATCH] lookup contact folder as fallback (#1174) ## Description In the event that a user only has a primary contact folder, and no subfolders, the contact folder legacy iter needs to fall back to checking for the contacts default folder in an isolated query, because that folder isn't provided as part of the contacts folders get request. ## Type of change - [x] :bug: Bugfix ## Issue(s) * #1113 ## Test Plan - [x] :muscle: Manual - [x] :green_heart: E2E --- .../exchange/exchange_service_test.go | 4 ++ .../connector/exchange/iterators_test.go | 6 +++ .../connector/exchange/query_options.go | 36 ++++++++++--- .../connector/exchange/service_functions.go | 10 ++-- .../connector/exchange/service_query.go | 18 ++++++- src/internal/connector/graph_connector.go | 52 +++++++++++-------- 6 files changed, 90 insertions(+), 36 deletions(-) diff --git a/src/internal/connector/exchange/exchange_service_test.go b/src/internal/connector/exchange/exchange_service_test.go index 4aa4243bb..c7389618f 100644 --- a/src/internal/connector/exchange/exchange_service_test.go +++ b/src/internal/connector/exchange/exchange_service_test.go @@ -291,6 +291,10 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() { name: "GraphQuery: Get All ContactFolders", function: GetAllContactFolderNamesForUser, }, + { + name: "GraphQuery: Get Default ContactFolder", + function: GetDefaultContactFolderForUser, + }, { name: "GraphQuery: Get All Events for User", function: GetAllEventsForUser, diff --git a/src/internal/connector/exchange/iterators_test.go b/src/internal/connector/exchange/iterators_test.go index beb5695ec..174999c8f 100644 --- a/src/internal/connector/exchange/iterators_test.go +++ b/src/internal/connector/exchange/iterators_test.go @@ -123,6 +123,12 @@ func (suite *ExchangeIteratorSuite) TestIterativeFunctions() { iterativeFunction: IterateFilterContainersForCollections, scope: contactScope[0], transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue, + }, { + name: "Default Contacts Folder", + queryFunction: GetDefaultContactFolderForUser, + iterativeFunction: IterateSelectAllContactsForCollections, + scope: contactScope[0], + transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue, }, } for _, test := range tests { diff --git a/src/internal/connector/exchange/query_options.go b/src/internal/connector/exchange/query_options.go index eee6a2d44..6e2e626fd 100644 --- a/src/internal/connector/exchange/query_options.go +++ b/src/internal/connector/exchange/query_options.go @@ -6,8 +6,9 @@ import ( msuser "github.com/microsoftgraph/msgraph-sdk-go/users" mscalendars "github.com/microsoftgraph/msgraph-sdk-go/users/item/calendars" mscontactfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders" - mscontactbyid "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item" - mscontactfolderitem "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item/contacts" + mscontactfolderitem "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item" + mscontactfolderchild "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item/childfolders" + mscontactfolderitemcontact "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item/contacts" mscontacts "github.com/microsoftgraph/msgraph-sdk-go/users/item/contacts" msevents "github.com/microsoftgraph/msgraph-sdk-go/users/item/events" msfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/mailfolders" @@ -236,7 +237,7 @@ func optionsForContactFolders(moreOps []string) ( } func optionsForContactFolderByID(moreOps []string) ( - *mscontactbyid.ContactFolderItemRequestBuilderGetRequestConfiguration, + *mscontactfolderitem.ContactFolderItemRequestBuilderGetRequestConfiguration, error, ) { selecting, err := buildOptions(moreOps, folders) @@ -244,10 +245,10 @@ func optionsForContactFolderByID(moreOps []string) ( return nil, err } - requestParameters := &mscontactbyid.ContactFolderItemRequestBuilderGetQueryParameters{ + requestParameters := &mscontactfolderitem.ContactFolderItemRequestBuilderGetQueryParameters{ Select: selecting, } - options := &mscontactbyid.ContactFolderItemRequestBuilderGetRequestConfiguration{ + options := &mscontactfolderitem.ContactFolderItemRequestBuilderGetRequestConfiguration{ QueryParameters: requestParameters, } @@ -298,16 +299,16 @@ func optionsForMailFoldersItem( // TODO: Remove after Issue #828; requires updating msgraph to v0.34 func optionsForContactFoldersItem( moreOps []string, -) (*mscontactfolderitem.ContactsRequestBuilderGetRequestConfiguration, error) { +) (*mscontactfolderitemcontact.ContactsRequestBuilderGetRequestConfiguration, error) { selecting, err := buildOptions(moreOps, contacts) if err != nil { return nil, err } - requestParameters := &mscontactfolderitem.ContactsRequestBuilderGetQueryParameters{ + requestParameters := &mscontactfolderitemcontact.ContactsRequestBuilderGetQueryParameters{ Select: selecting, } - options := &mscontactfolderitem.ContactsRequestBuilderGetRequestConfiguration{ + options := &mscontactfolderitemcontact.ContactsRequestBuilderGetRequestConfiguration{ QueryParameters: requestParameters, } @@ -332,6 +333,25 @@ func optionsForEvents(moreOps []string) (*msevents.EventsRequestBuilderGetReques return options, nil } +// optionsForContactChildFolders builds a contacts child folders request. +func optionsForContactChildFolders( + moreOps []string, +) (*mscontactfolderchild.ChildFoldersRequestBuilderGetRequestConfiguration, error) { + selecting, err := buildOptions(moreOps, contacts) + if err != nil { + return nil, err + } + + requestParameters := &mscontactfolderchild.ChildFoldersRequestBuilderGetQueryParameters{ + Select: selecting, + } + options := &mscontactfolderchild.ChildFoldersRequestBuilderGetRequestConfiguration{ + QueryParameters: requestParameters, + } + + return options, nil +} + // optionsForContacts transforms options into select query for MailContacts // @return is the first call in Contacts().GetWithRequestConfigurationAndResponseHandler(options, handler) func optionsForContacts(moreOps []string) (*mscontacts.ContactsRequestBuilderGetRequestConfiguration, error) { diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index 33a87bae2..e1ba50e63 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -346,10 +346,12 @@ func GetContainerID( } // SetupExchangeCollectionVars is a helper function returns a sets -// Exchange.Type specific functions based on scope +// Exchange.Type specific functions based on scope. +// The []GraphQuery slice provides fallback queries in the event that +// initial queries provide zero results. func SetupExchangeCollectionVars(scope selectors.ExchangeScope) ( absser.ParsableFactory, - GraphQuery, + []GraphQuery, GraphIterateFunc, error, ) { @@ -359,14 +361,14 @@ func SetupExchangeCollectionVars(scope selectors.ExchangeScope) ( if scope.IncludesCategory(selectors.ExchangeContact) { return models.CreateContactFolderCollectionResponseFromDiscriminatorValue, - GetAllContactFolderNamesForUser, + []GraphQuery{GetAllContactFolderNamesForUser, GetDefaultContactFolderForUser}, IterateSelectAllContactsForCollections, nil } if scope.IncludesCategory(selectors.ExchangeEvent) { return models.CreateCalendarCollectionResponseFromDiscriminatorValue, - GetAllCalendarNamesForUser, + []GraphQuery{GetAllCalendarNamesForUser}, IterateSelectAllEventsFromCalendars, nil } diff --git a/src/internal/connector/exchange/service_query.go b/src/internal/connector/exchange/service_query.go index 5b3698255..6dd0f2b64 100644 --- a/src/internal/connector/exchange/service_query.go +++ b/src/internal/connector/exchange/service_query.go @@ -51,9 +51,25 @@ func GetAllCalendarNamesForUser(ctx context.Context, gs graph.Service, user stri return gs.Client().UsersById(user).Calendars().Get(ctx, options) } +// GetDefaultContactFolderForUser is a GraphQuery function for getting the ContactFolderId +// and display names for the default "Contacts" folder. +// Only returns the default Contact Folder +func GetDefaultContactFolderForUser(ctx context.Context, gs graph.Service, user string) (absser.Parsable, error) { + options, err := optionsForContactChildFolders([]string{"displayName", "parentFolderId"}) + if err != nil { + return nil, err + } + + return gs.Client(). + UsersById(user). + ContactFoldersById(rootFolderAlias). + ChildFolders(). + Get(ctx, options) +} + // GetAllContactFolderNamesForUser is a GraphQuery function for getting ContactFolderId // and display names for contacts. All other information is omitted. -// Does not return the primary Contact Folder +// Does not return the default Contact Folder func GetAllContactFolderNamesForUser(ctx context.Context, gs graph.Service, user string) (absser.Parsable, error) { options, err := optionsForContactFolders([]string{"displayName", "parentFolderId"}) if err != nil { diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index 7db42db61..4b5a89c0c 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -334,40 +334,46 @@ func (gc *GraphConnector) legacyFetchItems( resolver graph.ContainerResolver, ) (map[string]*exchange.Collection, error) { var ( - errs error + errs error + errUpdater = func(id string, err error) { + errs = support.WrapAndAppend(id, err, errs) + } collections = map[string]*exchange.Collection{} ) - transformer, query, gIter, err := exchange.SetupExchangeCollectionVars(scope) + transformer, queries, gIter, err := exchange.SetupExchangeCollectionVars(scope) if err != nil { return nil, support.WrapAndAppend(gc.Service().Adapter().GetBaseUrl(), err, nil) } - response, err := query(ctx, &gc.graphService, qp.User) - if err != nil { - return nil, errors.Wrapf( - err, - "user %s M365 query: %s", - qp.User, support.ConnectorStackErrorTrace(err)) - } + // queries is assumed to provide fallbacks in case of empty results. Any + // non-zero collection production will break out of the loop. + for _, query := range queries { + response, err := query(ctx, &gc.graphService, qp.User) + if err != nil { + return nil, errors.Wrapf( + err, + "user %s M365 query: %s", + qp.User, support.ConnectorStackErrorTrace(err)) + } - pageIterator, err := msgraphgocore.NewPageIterator(response, &gc.graphService.adapter, transformer) - if err != nil { - return nil, err - } + pageIterator, err := msgraphgocore.NewPageIterator(response, &gc.graphService.adapter, transformer) + if err != nil { + return nil, err + } - errUpdater := func(id string, err error) { - errs = support.WrapAndAppend(id, err, errs) - } + // callbackFunc iterates through all M365 object target and fills exchange.Collection.jobs[] + // with corresponding item M365IDs. New collections are created for each directory. + // Each directory used the M365 Identifier. The use of ID stops collisions betweens users + callbackFunc := gIter(ctx, qp, errUpdater, collections, gc.UpdateStatus, resolver) - // callbackFunc iterates through all M365 object target and fills exchange.Collection.jobs[] - // with corresponding item M365IDs. New collections are created for each directory. - // Each directory used the M365 Identifier. The use of ID stops collisions betweens users - callbackFunc := gIter(ctx, qp, errUpdater, collections, gc.UpdateStatus, resolver) - iterateError := pageIterator.Iterate(ctx, callbackFunc) + if err := pageIterator.Iterate(ctx, callbackFunc); err != nil { + return nil, support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), err, errs) + } - if iterateError != nil { - errs = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), iterateError, errs) + if len(collections) > 0 { + break + } } return collections, errs