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