Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-19893] Outlook user status sync intial implementation #18

Merged
merged 19 commits into from
Jan 20, 2020

Conversation

mickmister
Copy link
Contributor

@mickmister mickmister commented Dec 19, 2019

Summary

This PR implements the user status sync feature, between the Outlook Calendar and Mattermost.

Ticket Link

https://mattermost.atlassian.net/browse/MM-19893

@mickmister mickmister added the 2: Dev Review Requires review by a core committer label Dec 19, 2019
server/api/availability.go Outdated Show resolved Hide resolved
server/api/availability.go Outdated Show resolved Hide resolved
server/job/recurring_job.go Outdated Show resolved Hide resolved
server/plugin/plugin.go Outdated Show resolved Hide resolved
server/remote/common.go Show resolved Hide resolved
AvailabilityViewInterval int `json:"availabilityViewInterval"`
}

func (c *client) GetSchedule(remoteUserID string, schedules []string, startTime, endTime *remote.DateTime, availabilityViewInterval int) ([]*remote.ScheduleInformation, error) {
Copy link
Contributor Author

@mickmister mickmister Dec 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though we are using app-level creds, we still need to provide a user id to the request. The body of the request contains several users' information, but the url needs to have one in it as well.

Our options are

POST /me/calendar/getSchedule 
POST /users/{id|userPrincipalName}/calendar/getSchedule

me doesn't work for the app-level user, and I don't think it has a user id. This endpoint specifically supports app-level users, so I'm not sure why we need to provide a user id in the url, or what the point of the of id is if the users we are searching for are in the body.

https://docs.microsoft.com/en-us/graph/api/calendar-getschedule

We are currently using the first user's id in the kvstore.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about using (and mapping) the BotAccount. We can still use special-case the credentials to invoke graph, but require that a user is specified.

server/remote/msgraph/get_schedule_batched.go Outdated Show resolved Hide resolved
server/remote/msgraph/remote.go Show resolved Hide resolved
Copy link
Contributor

@levb levb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister this is awesome! great job figuring things out. Unfortunately, this is a lot of code and I can't do a thorough-enough review of everything. I wanted to comment on a couple style/code structure things that jumped out at me right away, will add more later.

plugin.json Outdated Show resolved Hide resolved
server/api/api.go Outdated Show resolved Hide resolved
server/api/api.go Outdated Show resolved Hide resolved
server/api/availability.go Outdated Show resolved Hide resolved
server/api/availability.go Outdated Show resolved Hide resolved
)

type availabilityJob struct {
api API
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to avoid this pattern in notification.go - the thinking was that api is for an "acting user", has (at least) the current user's MattermostUserID etc. associated with it.

As I was working on solar lottery, i realized that this totally makes sense, because the api should just be operating in the context of the bot user. In fact, I am thinking that maybe the Bot User is the right concept to use for the "application-level" client. Can talk offline about it, no immediate change request here.

}

func (j *availabilityJob) Run() {
c := cron.New()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need (a separate ticket) for HA-proofing this by using a distributed lock to run the cron job on one server only. (failover??? does the lock expire?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://mattermost.atlassian.net/browse/MM-21649

Removed cron dependency in favor of using a ticker select loop, so we have more control.

server/api/availability.go Outdated Show resolved Hide resolved
server/api/availability.go Outdated Show resolved Hide resolved
AvailabilityViewInterval int `json:"availabilityViewInterval"`
}

func (c *client) GetSchedule(remoteUserID string, schedules []string, startTime, endTime *remote.DateTime, availabilityViewInterval int) ([]*remote.ScheduleInformation, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about using (and mapping) the BotAccount. We can still use special-case the credentials to invoke graph, but require that a user is specified.

server/remote/msgraph/get_schedule_batched.go Outdated Show resolved Hide resolved
server/remote/msgraph/get_schedule_batched.go Outdated Show resolved Hide resolved
server/remote/schedule.go Outdated Show resolved Hide resolved
server/store/user_store.go Outdated Show resolved Hide resolved
@levb levb requested review from crspeller and removed request for jfrerich January 6, 2020 16:33
Copy link
Member

@crspeller crspeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple comments. Will do a more through review when this has been cleaned up.

server/remote/schedule.go Show resolved Hide resolved

// 0= free, 1= tentative, 2= busy, 3= out of office, 4= working elsewhere.
// example "0010", which means free for first and second block, tentative for third, and free for fourth
AvailabilityView string `json:"availabilityView,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0/5 Maybe define a type for this? type Availability string

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types aliases are in fashion these days

server/remote/msgraph/get_schedule_batched.go Outdated Show resolved Hide resolved
numRequestsInBatch := 1
// numRequestsInBatch := 20

for i := 0; i < numRequestsInBatch; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for testing? I am not sure what this function is trying to accomplish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was a quick and dirty way to test the sizes of batches the MSGraph API accepts, because it is not documented. This will be cleaned up in this PR.

@mickmister
Copy link
Contributor Author

This is ready for review. I've created tickets to track further work in the project's epic https://mattermost.atlassian.net/browse/MM-19881

levb
levb previously approved these changes Jan 11, 2020
Copy link
Contributor

@levb levb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits and style suggestions, all optional.

server/api/api.go Outdated Show resolved Hide resolved
server/api/availability.go Outdated Show resolved Hide resolved
server/config/config.go Outdated Show resolved Hide resolved
server/store/user_store.go Outdated Show resolved Hide resolved
server/store/store.go Outdated Show resolved Hide resolved
server/remote/schedule.go Show resolved Hide resolved
server/remote/msgraph/call.go Outdated Show resolved Hide resolved
server/remote/client.go Outdated Show resolved Hide resolved
server/plugin/plugin.go Outdated Show resolved Hide resolved
* 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
@mickmister
Copy link
Contributor Author

mickmister commented Jan 13, 2020

@levb I've addressed your feedback in the latest commits.

@mickmister mickmister requested a review from levb January 13, 2020 01:09
@@ -65,6 +74,7 @@ type Dependencies struct {
Poster bot.Poster
Remote remote.Remote
IsAuthorizedAdmin func(userId string) (bool, error)
API plugin.API
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably solve this now. Also maybe change to PluginAPI because it doesn't read well with the whole package being called api.

return api.setUserStatusFromAvailability(api.mattermostUserID, status.Status, av), nil
}

func (api *api) SyncStatusForAllUsers() (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we split this into SyncStatusForUsers(users) and SyncStatusForAllUsers? Then we could make the implementation of SyncStatusForSingleUser just calling SyncStatusForUsers with one user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the SyncStatusForUsers method and have the other two methods calling that one, to reduce code duplication.

const JOB_INTERVAL = 5 * time.Minute

type StatusSyncJob struct {
api API
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing because we usually call the plugin API this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that once the other is renamed to PluginAPI and p is not replicated in the code, api becomes self-explanatory; but I'd love a better name; maybe app (for everything, package and all) despite all of its negative properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate ticket for renaming package if we plan on doing so?

@@ -207,3 +213,26 @@ func (p *Plugin) loadTemplates(bundlePath string) error {
p.Templates = templates
return nil
}

func (p *Plugin) initUserStatusSyncJob() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to get run once for every node. Can we put a comment here to make sure that is known?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started naming methods that are not ready for production more explicitly, Debug... for instance (or maybe POC in this case?). 0/5 Intention-revealing names are better than comments.

* extract PluginAPI interface
* refactor sync status code
* write test for user status sync
* comment on POC_initStatusSyncJob
* type alias for remote AvailabilityView string
@mickmister
Copy link
Contributor Author

@levb @crspeller Please re-review when you have the chance

Copy link
Member

@crspeller crspeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister Should there be a QA review label on this or do we have an agreement to test on master?

@mickmister
Copy link
Contributor Author

@crspeller So far, we have not gotten @DHaussermann involved with testing. I'll meet with him and get him set up to test master.

@levb levb added 4: Reviews Complete All reviewers have approved the pull request and removed 2: Dev Review Requires review by a core committer labels Jan 20, 2020
@levb levb merged commit 6040591 into master Jan 20, 2020
@levb levb deleted the MM-19893-availability branch January 20, 2020 21:39
Kshitij-Katiyar added a commit that referenced this pull request Oct 14, 2024
* [MM-843]: Added server testcase for kvstore/plugin_store.go file

* [MM-843]: removed ununsed vars
raghavaggarwal2308 added a commit that referenced this pull request Oct 22, 2024
* [MM-843]: Added server testcase for kvstore/plugin_store.go file (#18)

* [MM-843]: Added server testcase for kvstore/plugin_store.go file

* [MM-843]: removed ununsed vars

* updated go.mod and go.sum entries

* refactored mock plugin setup

---------

Co-authored-by: Doug Lauder <[email protected]>
Co-authored-by: Raghav Aggarwal <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants