From 8df00bd386e43c646396785a2630bf22d93c40de Mon Sep 17 00:00:00 2001 From: Keepers Date: Thu, 19 Jan 2023 15:30:14 -0700 Subject: [PATCH] standardizes the calendar resolver interface (#2162) ## Description Adds a little extra process into the calendar resolver so that it mimics the mail and contact resolvers. This will allow us to collapse the three resolvers into a more common handler or interface. We can take this change or drop it. I added the code in exploration of the event test failures, and figured I'd throw it out just in case. ## Does this PR need a docs update or release note? - [x] :no_entry: No ## Type of change - [x] :broom: Tech Debt/Cleanup ## Issue(s) * #2022 ## Test Plan - [x] :green_heart: E2E --- src/internal/connector/exchange/api/events.go | 31 ++++++++--- .../connector/exchange/api/options.go | 54 ++++++++----------- .../exchange/event_calendar_cache.go | 43 ++++++++++++++- .../exchange/folder_resolver_test.go | 1 + .../connector/exchange/mail_folder_cache.go | 30 +++++------ .../connector/exchange/service_functions.go | 4 +- .../connector/exchange/service_restore.go | 4 +- 7 files changed, 108 insertions(+), 59 deletions(-) diff --git a/src/internal/connector/exchange/api/events.go b/src/internal/connector/exchange/api/events.go index b4988d28d..0aafaf3f2 100644 --- a/src/internal/connector/exchange/api/events.go +++ b/src/internal/connector/exchange/api/events.go @@ -58,6 +58,28 @@ func (c Events) DeleteCalendar( return c.stable.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil) } +func (c Events) GetContainerByID( + ctx context.Context, + userID, containerID string, +) (graph.Container, error) { + service, err := c.service() + if err != nil { + return nil, err + } + + ofc, err := optionsForCalendarsByID([]string{"name", "owner"}) + if err != nil { + return nil, errors.Wrap(err, "options for event calendar") + } + + cal, err := service.Client().UsersById(userID).CalendarsById(containerID).Get(ctx, ofc) + if err != nil { + return nil, err + } + + return graph.CalendarDisplayable{Calendarable: cal}, nil +} + // GetItem retrieves an Eventable item. func (c Events) GetItem( ctx context.Context, @@ -183,14 +205,9 @@ func (c Events) GetAddedAndRemovedItemIDs( errs *multierror.Error ) - options, err := optionsForEventsByCalendarDelta([]string{"id"}) - if err != nil { - return nil, nil, DeltaUpdate{}, err - } - if len(oldDelta) > 0 { builder := users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, service.Adapter()) - pgr := &eventPager{service, builder, options} + pgr := &eventPager{service, builder, nil} added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr) // note: happy path, not the error condition @@ -217,7 +234,7 @@ func (c Events) GetAddedAndRemovedItemIDs( // works as intended (until, at least, we want to _not_ call the beta anymore). rawURL := fmt.Sprintf(eventBetaDeltaURLTemplate, user, calendarID) builder := users.NewItemCalendarsItemEventsDeltaRequestBuilder(rawURL, service.Adapter()) - pgr := &eventPager{service, builder, options} + pgr := &eventPager{service, builder, nil} added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr) if err != nil { diff --git a/src/internal/connector/exchange/api/options.go b/src/internal/connector/exchange/api/options.go index 99d41487c..49debf334 100644 --- a/src/internal/connector/exchange/api/options.go +++ b/src/internal/connector/exchange/api/options.go @@ -21,18 +21,6 @@ var ( "owner": {}, } - fieldsForEvents = map[string]struct{}{ - "calendar": {}, - "end": {}, - "id": {}, - "isOnlineMeeting": {}, - "isReminderOn": {}, - "responseStatus": {}, - "responseRequested": {}, - "showAs": {}, - "subject": {}, - } - fieldsForFolders = map[string]struct{}{ "childFolderCount": {}, "displayName": {}, @@ -112,6 +100,28 @@ func optionsForCalendars(moreOps []string) ( return options, nil } +// optionsForCalendarsByID places allowed options for exchange.Calendar object +// @param moreOps should reflect elements from fieldsForCalendars +// @return is first call in Calendars().GetWithRequestConfigurationAndResponseHandler +func optionsForCalendarsByID(moreOps []string) ( + *users.ItemCalendarsCalendarItemRequestBuilderGetRequestConfiguration, + error, +) { + selecting, err := buildOptions(moreOps, fieldsForCalendars) + if err != nil { + return nil, err + } + // should be a CalendarsRequestBuilderGetRequestConfiguration + requestParams := &users.ItemCalendarsCalendarItemRequestBuilderGetQueryParameters{ + Select: selecting, + } + options := &users.ItemCalendarsCalendarItemRequestBuilderGetRequestConfiguration{ + QueryParameters: requestParams, + } + + return options, nil +} + // optionsForContactFolders places allowed options for exchange.ContactFolder object // @return is first call in ContactFolders().GetWithRequestConfigurationAndResponseHandler func optionsForContactFolders(moreOps []string) ( @@ -213,26 +223,6 @@ func optionsForContactFoldersItemDelta( return options, nil } -// optionsForEvents ensures a valid option inputs for `exchange.Events` when selected from within a Calendar -func optionsForEventsByCalendarDelta( - moreOps []string, -) (*users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration, error) { - selecting, err := buildOptions(moreOps, fieldsForEvents) - if err != nil { - return nil, err - } - - requestParameters := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetQueryParameters{ - Select: selecting, - } - - options := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration{ - QueryParameters: requestParameters, - } - - return options, nil -} - // optionsForContactChildFolders builds a contacts child folders request. func optionsForContactChildFolders( moreOps []string, diff --git a/src/internal/connector/exchange/event_calendar_cache.go b/src/internal/connector/exchange/event_calendar_cache.go index 2b4e1b22d..e497a272a 100644 --- a/src/internal/connector/exchange/event_calendar_cache.go +++ b/src/internal/connector/exchange/event_calendar_cache.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/alcionai/corso/src/internal/connector/graph" + "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/pkg/path" ) @@ -14,9 +15,43 @@ var _ graph.ContainerResolver = &eventCalendarCache{} type eventCalendarCache struct { *containerResolver enumer containersEnumerator + getter containerGetter userID string } +// init ensures that the structure's fields are initialized. +// Fields Initialized when cache == nil: +// [mc.cache] +func (ecc *eventCalendarCache) init( + ctx context.Context, +) error { + if ecc.containerResolver == nil { + ecc.containerResolver = newContainerResolver() + } + + return ecc.populateEventRoot(ctx) +} + +// populateEventRoot manually fetches directories that are not returned during Graph for msgraph-sdk-go v. 40+ +// DefaultCalendar is the traditional "Calendar". +// Action ensures that cache will stop at appropriate level. +// @error iff the struct is not properly instantiated +func (ecc *eventCalendarCache) populateEventRoot(ctx context.Context) error { + container := DefaultCalendar + + f, err := ecc.getter.GetContainerByID(ctx, ecc.userID, container) + if err != nil { + return errors.Wrap(err, "fetching calendar "+support.ConnectorStackErrorTrace(err)) + } + + temp := graph.NewCacheFolder(f, path.Builder{}.Append(container)) + if err := ecc.addFolder(temp); err != nil { + return errors.Wrap(err, "initializing calendar resolver") + } + + return nil +} + // Populate utility function for populating eventCalendarCache. // Executes 1 additional Graph Query // @param baseID: ignored. Present to conform to interface @@ -25,8 +60,8 @@ func (ecc *eventCalendarCache) Populate( baseID string, baseContainerPath ...string, ) error { - if ecc.containerResolver == nil { - ecc.containerResolver = newContainerResolver() + if err := ecc.init(ctx); err != nil { + return errors.Wrap(err, "initializing") } err := ecc.enumer.EnumerateContainers(ctx, ecc.userID, "", ecc.addFolder) @@ -34,6 +69,10 @@ func (ecc *eventCalendarCache) Populate( return errors.Wrap(err, "enumerating containers") } + if err := ecc.populatePaths(ctx); err != nil { + return errors.Wrap(err, "establishing calendar paths") + } + return nil } diff --git a/src/internal/connector/exchange/folder_resolver_test.go b/src/internal/connector/exchange/folder_resolver_test.go index 36bf0ffc5..ea7ccb995 100644 --- a/src/internal/connector/exchange/folder_resolver_test.go +++ b/src/internal/connector/exchange/folder_resolver_test.go @@ -51,6 +51,7 @@ func (suite *CacheResolverSuite) TestPopulate() { return &eventCalendarCache{ userID: tester.M365UserID(t), enumer: ac.Events(), + getter: ac.Events(), } } diff --git a/src/internal/connector/exchange/mail_folder_cache.go b/src/internal/connector/exchange/mail_folder_cache.go index 06d4b1285..565f10736 100644 --- a/src/internal/connector/exchange/mail_folder_cache.go +++ b/src/internal/connector/exchange/mail_folder_cache.go @@ -22,14 +22,25 @@ type mailFolderCache struct { userID string } +// init ensures that the structure's fields are initialized. +// Fields Initialized when cache == nil: +// [mc.cache] +func (mc *mailFolderCache) init( + ctx context.Context, +) error { + if mc.containerResolver == nil { + mc.containerResolver = newContainerResolver() + } + + return mc.populateMailRoot(ctx) +} + // populateMailRoot manually fetches directories that are not returned during Graph for msgraph-sdk-go v. 40+ // rootFolderAlias is the top-level directory for exchange.Mail. // DefaultMailFolder is the traditional "Inbox" for exchange.Mail // Action ensures that cache will stop at appropriate level. // @error iff the struct is not properly instantiated -func (mc *mailFolderCache) populateMailRoot( - ctx context.Context, -) error { +func (mc *mailFolderCache) populateMailRoot(ctx context.Context) error { for _, fldr := range []string{rootFolderAlias, DefaultMailFolder} { var directory string @@ -76,16 +87,3 @@ func (mc *mailFolderCache) Populate( return nil } - -// init ensures that the structure's fields are initialized. -// Fields Initialized when cache == nil: -// [mc.cache] -func (mc *mailFolderCache) init( - ctx context.Context, -) error { - if mc.containerResolver == nil { - mc.containerResolver = newContainerResolver() - } - - return mc.populateMailRoot(ctx) -} diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index d646fb132..9d996f01c 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -66,9 +66,11 @@ func PopulateExchangeContainerResolver( cacheRoot = DefaultContactFolder case path.EventsCategory: + ecc := ac.Events() res = &eventCalendarCache{ userID: qp.ResourceOwner, - enumer: ac.Events(), + getter: ecc, + enumer: ecc, } cacheRoot = DefaultCalendar diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index 3f88f6efe..d385dab81 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -507,9 +507,11 @@ func CreateContainerDestinaion( case path.EventsCategory: if directoryCache == nil { + ace := ac.Events() ecc := &eventCalendarCache{ userID: user, - enumer: ac.Events(), + getter: ace, + enumer: ace, } caches[category] = ecc newCache = true