From 604059183d51ab707541d101d349c0912da4b2ca Mon Sep 17 00:00:00 2001 From: Michael Kochell Date: Mon, 20 Jan 2020 16:39:31 -0500 Subject: [PATCH] [MM-19893] Outlook user status sync intial implementation (#18) * `availability` slash command updates user status based on getSchedule * app-only working. batching needs to be done now * batch request works * get rid of AppLevelClient struct/interface * remove dead code * lint * move app-level user auth token logic into its own file * lint * make job cancelable through system console. rename status sync api methods * rename AppLevelClient to SuperuserClient * batch requests properly to handle > 400 users * change AllUsers name to UserIndex. handle getSchedule error. * clean up batch response unmarshaling * PR feedback * rename NewClient to MakeClient * rename EnableStatusSyncJob to EnableStatusSync * rename CallURLEncodedForm to CallFormPost * rename allUsers to userIndex * Move availability view constants to remote package * Implement UserIndex methods to access as a map * Remove call to status sync job in OnActivate * Remove redundant Call method on remote client interface * rename var * reorder methods * update mocks * update for PR feedback: * extract PluginAPI interface * refactor sync status code * write test for user status sync * comment on POC_initStatusSyncJob * type alias for remote AvailabilityView string --- Makefile | 4 + assets/profile.png | Bin 2881 -> 19737 bytes go.mod | 1 + go.sum | 2 + plugin.json | 7 + server/api/api.go | 15 +- server/api/availability.go | 146 ++++++++++++++++++ server/api/availability_test.go | 138 +++++++++++++++++ server/api/mock_api/mock_availability.go | 79 ++++++++++ server/api/mock_api/mock_calendar.go | 74 +++++++++ server/api/notification.go | 2 +- server/api/oauth2.go | 2 +- server/api/plugin_api.go | 22 +++ server/api/status_sync_job.go | 71 +++++++++ server/config/config.go | 2 + server/plugin/command/availability.go | 25 +++ server/plugin/command/command.go | 10 +- server/plugin/plugin.go | 35 ++++- server/remote/client.go | 9 +- server/remote/common.go | 3 +- server/remote/mock_remote/mock_client.go | 102 +++++++++++- server/remote/mock_remote/mock_remote.go | 26 +++- server/remote/msgraph/batch_request.go | 63 ++++++++ server/remote/msgraph/call.go | 35 +++-- server/remote/msgraph/get_me.go | 2 + .../remote/msgraph/get_notification_data.go | 2 +- server/remote/msgraph/get_schedule_batched.go | 132 ++++++++++++++++ .../msgraph/get_schedule_batched_test.go | 86 +++++++++++ server/remote/msgraph/get_super_user_token.go | 40 +++++ server/remote/msgraph/remote.go | 26 +++- server/remote/remote.go | 3 +- server/remote/schedule.go | 35 +++++ server/remote/user.go | 1 + server/store/mock_store/mock_event_store.go | 77 +++++++++ server/store/mock_store/mock_user_store.go | 15 ++ server/store/store.go | 3 + server/store/user_store.go | 90 +++++++++++ server/testdata/batch_requests.json | 68 ++++++++ server/testdata/get_me_response.json | 8 + .../testdata/get_schedule_response_batch.json | 38 +++++ .../testdata/get_schedule_response_busy.json | 39 +++++ .../testdata/get_schedule_response_free.json | 24 +++ .../get_schedule_response_invalid_email.json | 12 ++ .../testdata/webhook_event_ notification.json | 18 +++ server/utils/bot/mock_bot/mock_logger.go | 116 ++++++++++++++ .../mock_plugin_api/mock_plugin_api.go | 79 ++++++++++ server/utils/plugin_api/plugin_api.go | 14 ++ 47 files changed, 1761 insertions(+), 40 deletions(-) create mode 100644 server/api/availability.go create mode 100644 server/api/availability_test.go create mode 100644 server/api/mock_api/mock_availability.go create mode 100644 server/api/plugin_api.go create mode 100644 server/api/status_sync_job.go create mode 100644 server/plugin/command/availability.go create mode 100644 server/remote/msgraph/batch_request.go create mode 100644 server/remote/msgraph/get_schedule_batched.go create mode 100644 server/remote/msgraph/get_schedule_batched_test.go create mode 100644 server/remote/msgraph/get_super_user_token.go create mode 100644 server/remote/schedule.go create mode 100644 server/store/mock_store/mock_event_store.go create mode 100644 server/testdata/batch_requests.json create mode 100644 server/testdata/get_me_response.json create mode 100644 server/testdata/get_schedule_response_batch.json create mode 100644 server/testdata/get_schedule_response_busy.json create mode 100644 server/testdata/get_schedule_response_free.json create mode 100644 server/testdata/get_schedule_response_invalid_email.json create mode 100644 server/testdata/webhook_event_ notification.json create mode 100644 server/utils/bot/mock_bot/mock_logger.go create mode 100644 server/utils/plugin_api/mock_plugin_api/mock_plugin_api.go create mode 100644 server/utils/plugin_api/plugin_api.go diff --git a/Makefile b/Makefile index 909c1a87..afdff7d3 100644 --- a/Makefile +++ b/Makefile @@ -87,10 +87,14 @@ ifneq ($(HAS_SERVER),) mockgen -destination server/api/mock_api/mock_subscriptions.go github.com/mattermost/mattermost-plugin-mscalendar/server/api Subscriptions mockgen -destination server/api/mock_api/mock_calendar.go github.com/mattermost/mattermost-plugin-mscalendar/server/api Calendar mockgen -destination server/api/mock_api/mock_client.go github.com/mattermost/mattermost-plugin-mscalendar/server/api Client + mockgen -destination server/api/mock_api/mock_availability.go github.com/mattermost/mattermost-plugin-mscalendar/server/api Availability mockgen -destination server/remote/mock_remote/mock_remote.go github.com/mattermost/mattermost-plugin-mscalendar/server/remote Remote mockgen -destination server/remote/mock_remote/mock_client.go github.com/mattermost/mattermost-plugin-mscalendar/server/remote Client mockgen -destination server/utils/bot/mock_bot/mock_poster.go github.com/mattermost/mattermost-plugin-mscalendar/server/utils/bot Poster mockgen -destination server/utils/bot/mock_bot/mock_admin.go github.com/mattermost/mattermost-plugin-mscalendar/server/utils/bot Admin + mockgen -destination server/utils/bot/mock_bot/mock_logger.go github.com/mattermost/mattermost-plugin-mscalendar/server/utils/bot Logger + mockgen -destination server/utils/plugin_api/mock_plugin_api/mock_plugin_api.go github.com/mattermost/mattermost-plugin-mscalendar/server/utils/plugin_api PluginAPI + mockgen -destination server/store/mock_store/mock_event_store.go github.com/mattermost/mattermost-plugin-mscalendar/server/store EventStore mockgen -destination server/store/mock_store/mock_oauth2_store.go github.com/mattermost/mattermost-plugin-mscalendar/server/store OAuth2StateStore mockgen -destination server/store/mock_store/mock_subscription_store.go github.com/mattermost/mattermost-plugin-mscalendar/server/store SubscriptionStore mockgen -destination server/store/mock_store/mock_user_store.go github.com/mattermost/mattermost-plugin-mscalendar/server/store UserStore diff --git a/assets/profile.png b/assets/profile.png index 592a5f111ac8b9b0dd2de79d044b0bc6b8af6206..eb2fea2edb2ba25c8fe97f2b80ea167ea6327beb 100644 GIT binary patch literal 19737 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 F_z$iiP!|9I literal 2881 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