-
Notifications
You must be signed in to change notification settings - Fork 23
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
Conversation
AvailabilityViewInterval int `json:"availabilityViewInterval"` | ||
} | ||
|
||
func (c *client) GetSchedule(remoteUserID string, schedules []string, startTime, endTime *remote.DateTime, availabilityViewInterval int) ([]*remote.ScheduleInformation, error) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
server/api/availability.go
Outdated
) | ||
|
||
type availabilityJob struct { | ||
api API |
There was a problem hiding this comment.
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.
server/api/availability.go
Outdated
} | ||
|
||
func (j *availabilityJob) Run() { | ||
c := cron.New() |
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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.
AvailabilityViewInterval int `json:"availabilityViewInterval"` | ||
} | ||
|
||
func (c *client) GetSchedule(remoteUserID string, schedules []string, startTime, endTime *remote.DateTime, availabilityViewInterval int) ([]*remote.ScheduleInformation, error) { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
Outdated
|
||
// 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"` |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
numRequestsInBatch := 1 | ||
// numRequestsInBatch := 20 | ||
|
||
for i := 0; i < numRequestsInBatch; i++ { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this 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.
* 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
@levb I've addressed your feedback in the latest commits. |
server/api/api.go
Outdated
@@ -65,6 +74,7 @@ type Dependencies struct { | |||
Poster bot.Poster | |||
Remote remote.Remote | |||
IsAuthorizedAdmin func(userId string) (bool, error) | |||
API plugin.API |
There was a problem hiding this comment.
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.
server/api/availability.go
Outdated
return api.setUserStatusFromAvailability(api.mattermostUserID, status.Status, av), nil | ||
} | ||
|
||
func (api *api) SyncStatusForAllUsers() (string, error) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
server/plugin/plugin.go
Outdated
@@ -207,3 +213,26 @@ func (p *Plugin) loadTemplates(bundlePath string) error { | |||
p.Templates = templates | |||
return nil | |||
} | |||
|
|||
func (p *Plugin) initUserStatusSyncJob() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@levb @crspeller Please re-review when you have the chance |
There was a problem hiding this 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?
@crspeller So far, we have not gotten @DHaussermann involved with testing. I'll meet with him and get him set up to test master. |
* [MM-843]: Added server testcase for kvstore/plugin_store.go file * [MM-843]: removed ununsed vars
* [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]>
Summary
This PR implements the user status sync feature, between the Outlook Calendar and Mattermost.
Ticket Link
https://mattermost.atlassian.net/browse/MM-19893