Skip to content

Commit 8394af6

Browse files
feat: Sync Mattermost OOO status to GitHub
Adds a feature to automatically sync a user's Mattermost "Out of Office" status to their GitHub status. When a user sets their status to "Out of Office" in Mattermost, their GitHub status will be set to "Busy" with the message "Out of office". When the user's status is no longer "Out of Office", their original GitHub status will be restored. This is implemented using the `UserStatusHasChanged` hook in the Mattermost plugin API and the `changeUserStatus` GraphQL mutation in the GitHub API. The user's original GitHub status is stored in the plugin's key-value store.
1 parent 041c9e0 commit 8394af6

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

server/plugin/graphql/client.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,52 @@ func (c *Client) executeQuery(ctx context.Context, qry interface{}, params map[s
6464

6565
return nil
6666
}
67+
68+
type changeUserStatusMutation struct {
69+
ChangeUserStatus struct {
70+
Status struct {
71+
Message githubv4.String
72+
Emoji githubv4.String
73+
}
74+
} `graphql:"changeUserStatus(input: $input)"`
75+
}
76+
77+
func (c *Client) UpdateUserStatus(ctx context.Context, emoji, message string, busy bool) (string, error) {
78+
var mutation changeUserStatusMutation
79+
input := githubv4.ChangeUserStatusInput{
80+
Emoji: githubv4.NewString(githubv4.String(emoji)),
81+
Message: githubv4.NewString(githubv4.String(message)),
82+
LimitedAvailability: githubv4.NewBoolean(githubv4.Boolean(busy)),
83+
}
84+
85+
err := c.client.Mutate(ctx, &mutation, input, nil)
86+
if err != nil {
87+
return "", err
88+
}
89+
90+
return string(mutation.ChangeUserStatus.Status.Message), nil
91+
}
92+
93+
type getUserStatusQuery struct {
94+
User struct {
95+
Status struct {
96+
Message githubv4.String
97+
Emoji githubv4.String
98+
LimitedAvailability githubv4.Boolean
99+
}
100+
} `graphql:"user(login: $login)"`
101+
}
102+
103+
func (c *Client) GetUserStatus(ctx context.Context, login string) (string, string, bool, error) {
104+
var query getUserStatusQuery
105+
variables := map[string]interface{}{
106+
"login": githubv4.String(login),
107+
}
108+
109+
err := c.client.Query(ctx, &query, variables)
110+
if err != nil {
111+
return "", "", false, err
112+
}
113+
114+
return string(query.User.Status.Message), string(query.User.Status.Emoji), bool(query.User.Status.LimitedAvailability), nil
115+
}

server/plugin/plugin.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,12 @@ type UserSettings struct {
627627
Notifications bool `json:"notifications"`
628628
}
629629

630+
type GithubStatus struct {
631+
Message string `json:"message"`
632+
Emoji string `json:"emoji"`
633+
Busy bool `json:"busy"`
634+
}
635+
630636
func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error {
631637
config := p.getConfiguration()
632638

@@ -1192,3 +1198,59 @@ func (p *Plugin) handleRevokedToken(info *GitHubUserInfo) {
11921198
p.disconnectGitHubAccount(info.UserID)
11931199
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")
11941200
}
1201+
1202+
func (p *Plugin) UserStatusHasChanged(c *plugin.Context, userStatus *model.Status) {
1203+
userInfo, apiErr := p.getGitHubUserInfo(userStatus.UserId)
1204+
if apiErr != nil {
1205+
return
1206+
}
1207+
1208+
if userStatus.Status == "ooo" {
1209+
graphQLClient := p.graphQLConnect(userInfo)
1210+
message, emoji, busy, err := graphQLClient.GetUserStatus(context.Background(), userInfo.GitHubUsername)
1211+
if err != nil {
1212+
p.client.Log.Error("failed to get user status", "error", err)
1213+
return
1214+
}
1215+
1216+
githubStatus := &GithubStatus{
1217+
Message: message,
1218+
Emoji: emoji,
1219+
Busy: busy,
1220+
}
1221+
1222+
githubStatusJSON, err := json.Marshal(githubStatus)
1223+
if err != nil {
1224+
p.client.Log.Error("failed to marshal github status", "error", err)
1225+
return
1226+
}
1227+
1228+
p.store.Set(userInfo.UserID+"_github_status", githubStatusJSON)
1229+
1230+
_, err = graphQLClient.UpdateUserStatus(context.Background(), ":house_with_garden:", "Out of office", true)
1231+
if err != nil {
1232+
p.client.Log.Error("failed to update user status", "error", err)
1233+
return
1234+
}
1235+
1236+
p.CreateBotDMPost(userInfo.UserID, "Your GitHub status has been updated to Out of office.", "custom_git_ooo_ephemeral")
1237+
} else {
1238+
var oldStatus []byte
1239+
if err := p.store.Get(userInfo.UserID+"_github_status", &oldStatus); err == nil && len(oldStatus) > 0 {
1240+
var githubStatus GithubStatus
1241+
if err := json.Unmarshal(oldStatus, &githubStatus); err != nil {
1242+
p.client.Log.Error("failed to unmarshal github status", "error", err)
1243+
return
1244+
}
1245+
1246+
graphQLClient := p.graphQLConnect(userInfo)
1247+
_, err := graphQLClient.UpdateUserStatus(context.Background(), githubStatus.Emoji, githubStatus.Message, githubStatus.Busy)
1248+
if err != nil {
1249+
p.client.Log.Error("failed to update user status", "error", err)
1250+
return
1251+
}
1252+
p.store.Delete(userInfo.UserID + "_github_status")
1253+
p.CreateBotDMPost(userInfo.UserID, "Your GitHub status has been restored.", "custom_git_ooo_ephemeral")
1254+
}
1255+
}
1256+
}

0 commit comments

Comments
 (0)