Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@
"type": "bool",
"help_text": "When set to 'true' you will get a notification with less details when a draft pull request is created and a notification with complete details when they are marked as ready for review. When set to 'false' no notifications are delivered for draft pull requests.",
"default": false
},
{
"key": "EnableStatusSync",
"display_name": "Enable Status Synchronization:",
"type": "bool",
"help_text": "(Optional) When enabled, users can opt-in to automatically sync their Mattermost 'Out of Office' status to GitHub. When a user sets their status to OOO in Mattermost, their GitHub status will be updated to 'Out of office' with busy indicator. The status is restored when they return. The feature respects manually set GitHub statuses and will not overwrite them.",
"default": false
}
],
"footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/mattermost/mattermost-plugin-github)."
Expand Down
1 change: 1 addition & 0 deletions server/plugin/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Configuration struct {
UsePreregisteredApplication bool `json:"usepreregisteredapplication"`
ShowAuthorInCommitNotification bool `json:"showauthorincommitnotification"`
GetNotificationForDraftPRs bool `json:"getnotificationfordraftprs"`
EnableStatusSync bool `json:"enablestatussync"`
}

func (c *Configuration) ToMap() (map[string]interface{}, error) {
Expand Down
49 changes: 49 additions & 0 deletions server/plugin/graphql/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,52 @@ func (c *Client) executeQuery(ctx context.Context, qry interface{}, params map[s

return nil
}

type changeUserStatusMutation struct {
ChangeUserStatus struct {
Status struct {
Message githubv4.String
Emoji githubv4.String
}
} `graphql:"changeUserStatus(input: $input)"`
}

func (c *Client) UpdateUserStatus(ctx context.Context, emoji, message string, busy bool) (string, error) {
var mutation changeUserStatusMutation
input := githubv4.ChangeUserStatusInput{
Emoji: githubv4.NewString(githubv4.String(emoji)),
Message: githubv4.NewString(githubv4.String(message)),
LimitedAvailability: githubv4.NewBoolean(githubv4.Boolean(busy)),
}

err := c.client.Mutate(ctx, &mutation, input, nil)
if err != nil {
return "", errors.Wrap(err, "UpdateUserStatus mutate failed")
}

return string(mutation.ChangeUserStatus.Status.Message), nil
}

type getUserStatusQuery struct {
User struct {
Status struct {
Message githubv4.String
Emoji githubv4.String
LimitedAvailability githubv4.Boolean
}
} `graphql:"user(login: $login)"`
}

func (c *Client) GetUserStatus(ctx context.Context, login string) (string, string, bool, error) {
var query getUserStatusQuery
variables := map[string]interface{}{
"login": githubv4.String(login),
}

err := c.client.Query(ctx, &query, variables)
if err != nil {
return "", "", false, errors.Wrap(err, "GetUserStatus query failed")
}

return string(query.User.Status.Message), string(query.User.Status.Emoji), bool(query.User.Status.LimitedAvailability), nil
}
78 changes: 78 additions & 0 deletions server/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ type UserSettings struct {
Notifications bool `json:"notifications"`
}

type GithubStatus struct {
Message string `json:"message"`
Emoji string `json:"emoji"`
Busy bool `json:"busy"`
}

func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error {
config := p.getConfiguration()

Expand Down Expand Up @@ -711,6 +717,10 @@ func (p *Plugin) disconnectGitHubAccount(userID string) {
p.client.Log.Warn("Failed to delete github token from KV store", "userID", userID, "error", err.Error())
}

if err := p.store.Delete(userID + "_github_status"); err != nil {
p.client.Log.Warn("Failed to delete github status from KV store", "userID", userID, "error", err.Error())
}

user, err := p.client.User.Get(userID)
if err != nil {
p.client.Log.Warn("Failed to get user props", "userID", userID, "error", err.Error())
Expand Down Expand Up @@ -1192,3 +1202,71 @@ func (p *Plugin) handleRevokedToken(info *GitHubUserInfo) {
p.disconnectGitHubAccount(info.UserID)
p.CreateBotDMPost(info.UserID, "Your Github account was disconnected due to an invalid or revoked authorization token. Reconnect your account using the `/github connect` command.", "custom_git_revoked_token")
}

func (p *Plugin) UserStatusHasChanged(c *plugin.Context, userStatus *model.Status) {
// Check if status sync is enabled in configuration
config := p.getConfiguration()
if !config.EnableStatusSync {
return
}

userInfo, apiErr := p.getGitHubUserInfo(userStatus.UserId)
if apiErr != nil {
return
}

if userStatus.Status == "ooo" {
graphQLClient := p.graphQLConnect(userInfo)
message, emoji, busy, err := graphQLClient.GetUserStatus(context.Background(), userInfo.GitHubUsername)
Copy link
Member

Choose a reason for hiding this comment

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

Can you check here if the Github status is already "out of office" and not overwrite it if true. I don't think we want to clobber a manually set OoO on the Github side.

if err != nil {
p.client.Log.Error("failed to get user status", "error", err)
return
}

// Don't overwrite if GitHub status is already set to "out of office" (busy)
if busy {
p.client.Log.Debug("GitHub status is already set to busy/OOO, skipping update")
return
}

githubStatus := &GithubStatus{
Message: message,
Emoji: emoji,
Busy: busy,
}

githubStatusJSON, err := json.Marshal(githubStatus)
if err != nil {
p.client.Log.Error("failed to marshal github status", "error", err)
return
}

p.store.Set(userInfo.UserID+"_github_status", githubStatusJSON)

_, err = graphQLClient.UpdateUserStatus(context.Background(), ":house_with_garden:", "Out of office", true)
if err != nil {
p.client.Log.Error("failed to update user status", "error", err)
return
}

p.CreateBotDMPost(userInfo.UserID, "Your GitHub status has been updated to Out of office.", "custom_git_status_sync")
} else {
var oldStatus []byte
if err := p.store.Get(userInfo.UserID+"_github_status", &oldStatus); err == nil && len(oldStatus) > 0 {
var githubStatus GithubStatus
if err := json.Unmarshal(oldStatus, &githubStatus); err != nil {
p.client.Log.Error("failed to unmarshal github status", "error", err)
return
}

graphQLClient := p.graphQLConnect(userInfo)
_, err := graphQLClient.UpdateUserStatus(context.Background(), githubStatus.Emoji, githubStatus.Message, githubStatus.Busy)
if err != nil {
p.client.Log.Error("failed to update user status", "error", err)
return
}
p.store.Delete(userInfo.UserID + "_github_status")
Copy link
Member

Choose a reason for hiding this comment

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

@akshat-khosya this is cool feature. One minor issue I see is that a user's status will be left in the KV store if they disconnect their account while in "ooo" status. Can you add something to clear a user's status in the KV store when their account is disconnected?

p.CreateBotDMPost(userInfo.UserID, "Your GitHub status has been restored.", "custom_git_status_sync")
}
}
}