From 8d28b2e72115cde68f5ae8ff423d9dd211624acd Mon Sep 17 00:00:00 2001 From: kshitij katiyar <90389917+Kshitij-Katiyar@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:53:13 +0530 Subject: [PATCH] [GH-500]: Fixed github issue "Emoji synchronisation for PR/issue on github" (#632) --- server/plugin/plugin.go | 181 +++++++++++++++++++++++++++++++++++++++ server/plugin/webhook.go | 36 ++++++++ 2 files changed, 217 insertions(+) diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index cb593b095..49df2b4d0 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -89,6 +89,8 @@ type Plugin struct { webhookBroker *WebhookBroker oauthBroker *OAuthBroker + + emojiMap map[string]string } // NewPlugin returns an instance of a Plugin. @@ -111,9 +113,34 @@ func NewPlugin() *Plugin { "issue": p.handleIssue, } + p.createGithubEmojiMap() return p } +func (p *Plugin) createGithubEmojiMap() { + baseGithubEmojiMap := map[string]string{ + "+1": "+1", + "-1": "-1", + "thumbsup": "+1", + "thumbsdown": "-1", + "laughing": "laugh", + "confused": "confused", + "heart": "heart", + "tada": "hooray", + "rocket": "rocket", + "eyes": "eyes", + } + + p.emojiMap = map[string]string{} + for systemEmoji := range model.SystemEmojis { + for mmBase, ghBase := range baseGithubEmojiMap { + if strings.HasPrefix(systemEmoji, mmBase) { + p.emojiMap[systemEmoji] = ghBase + } + } + } +} + func (p *Plugin) GetGitHubClient(ctx context.Context, userID string) (*github.Client, error) { userInfo, apiErr := p.getGitHubUserInfo(userID) if apiErr != nil { @@ -251,6 +278,160 @@ func (p *Plugin) OnDeactivate() error { return nil } +func (p *Plugin) getPostPropsForReaction(reaction *model.Reaction) (org, repo string, id float64, objectType string, ok bool) { + post, err := p.client.Post.GetPost(reaction.PostId) + if err != nil { + p.API.LogDebug("Error fetching post for reaction", "error", err.Error()) + return org, repo, id, objectType, false + } + + // Getting the Github repository from notification post props + repo, ok = post.GetProp(postPropGithubRepo).(string) + if !ok || repo == "" { + return org, repo, id, objectType, false + } + + orgRepo := strings.Split(repo, "/") + if len(orgRepo) != 2 { + p.API.LogDebug("Invalid organization repository") + return org, repo, id, objectType, false + } + + org, repo = orgRepo[0], orgRepo[1] + + // Getting the Github object id from notification post props + id, ok = post.GetProp(postPropGithubObjectID).(float64) + if !ok || id == 0 { + return org, repo, id, objectType, false + } + + // Getting the Github object type from notification post props + objectType, ok = post.GetProp(postPropGithubObjectType).(string) + if !ok || objectType == "" { + return org, repo, id, objectType, false + } + + return org, repo, id, objectType, true +} + +func (p *Plugin) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) { + githubEmoji := p.emojiMap[reaction.EmojiName] + if githubEmoji == "" { + p.API.LogWarn("Emoji is not supported by Github", "Emoji", reaction.EmojiName) + return + } + + owner, repo, id, objectType, ok := p.getPostPropsForReaction(reaction) + if !ok { + return + } + + info, appErr := p.getGitHubUserInfo(reaction.UserId) + if appErr != nil { + if appErr.ID != apiErrorIDNotConnected { + p.API.LogDebug("Error in getting user info", "error", appErr.Error()) + } + return + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + defer cancel() + ghClient := p.githubConnectUser(ctx, info) + switch objectType { + case githubObjectTypeIssueComment: + if _, _, err := ghClient.Reactions.CreateIssueCommentReaction(context.Background(), owner, repo, int64(id), githubEmoji); err != nil { + p.API.LogDebug("Error occurred while creating issue comment reaction", "error", err.Error()) + return + } + case githubObjectTypeIssue: + if _, _, err := ghClient.Reactions.CreateIssueReaction(context.Background(), owner, repo, int(id), githubEmoji); err != nil { + p.API.LogDebug("Error occurred while creating issue reaction", "error", err.Error()) + return + } + case githubObjectTypePRReviewComment: + if _, _, err := ghClient.Reactions.CreatePullRequestCommentReaction(context.Background(), owner, repo, int64(id), githubEmoji); err != nil { + p.API.LogDebug("Error occurred while creating PR review comment reaction", "error", err.Error()) + return + } + default: + return + } +} + +func (p *Plugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.Reaction) { + githubEmoji := p.emojiMap[reaction.EmojiName] + if githubEmoji == "" { + p.API.LogWarn("Emoji is not supported by Github", "Emoji", reaction.EmojiName) + return + } + + owner, repo, id, objectType, ok := p.getPostPropsForReaction(reaction) + if !ok { + return + } + + info, appErr := p.getGitHubUserInfo(reaction.UserId) + if appErr != nil { + if appErr.ID != apiErrorIDNotConnected { + p.API.LogDebug("Error in getting user info", "error", appErr.Error()) + } + return + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + defer cancel() + ghClient := p.githubConnectUser(ctx, info) + switch objectType { + case githubObjectTypeIssueComment: + reactions, _, err := ghClient.Reactions.ListIssueCommentReactions(context.Background(), owner, repo, int64(id), &github.ListOptions{}) + if err != nil { + p.API.LogDebug("Error getting issue comment reaction list", "error", err.Error()) + return + } + + for _, reactionObj := range reactions { + if info.UserID == reaction.UserId && p.emojiMap[reaction.EmojiName] == reactionObj.GetContent() { + if _, err = ghClient.Reactions.DeleteIssueCommentReaction(context.Background(), owner, repo, int64(id), reactionObj.GetID()); err != nil { + p.API.LogDebug("Error occurred while removing issue comment reaction", "error", err.Error()) + } + return + } + } + case githubObjectTypeIssue: + reactions, _, err := ghClient.Reactions.ListIssueReactions(context.Background(), owner, repo, int(id), &github.ListOptions{}) + if err != nil { + p.API.LogDebug("Error getting issue reaction list", "error", err.Error()) + return + } + + for _, reactionObj := range reactions { + if info.UserID == reaction.UserId && p.emojiMap[reaction.EmojiName] == reactionObj.GetContent() { + if _, err = ghClient.Reactions.DeleteIssueReaction(context.Background(), owner, repo, int(id), reactionObj.GetID()); err != nil { + p.API.LogDebug("Error occurred while removing issue reaction", "error", err.Error()) + } + return + } + } + case githubObjectTypePRReviewComment: + reactions, _, err := ghClient.Reactions.ListPullRequestCommentReactions(context.Background(), owner, repo, int64(id), &github.ListOptions{}) + if err != nil { + p.API.LogDebug("Error getting PR review comment reaction list", "error", err.Error()) + return + } + + for _, reactionObj := range reactions { + if info.UserID == reaction.UserId && p.emojiMap[reaction.EmojiName] == reactionObj.GetContent() { + if _, err = ghClient.Reactions.DeletePullRequestCommentReaction(context.Background(), owner, repo, int64(id), reactionObj.GetID()); err != nil { + p.API.LogDebug("Error occurred while removing PR review comment reaction", "error", err.Error()) + } + return + } + } + default: + return + } +} + func (p *Plugin) OnInstall(c *plugin.Context, event model.OnInstallEvent) error { // Don't start wizard if OAuth is configured if p.getConfiguration().IsOAuthConfigured() { diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 39d967c82..78b371ebd 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -28,6 +28,14 @@ const ( actionCreated = "created" actionDeleted = "deleted" actionEdited = "edited" + + postPropGithubRepo = "gh_repo" + postPropGithubObjectID = "gh_object_id" + postPropGithubObjectType = "gh_object_type" + + githubObjectTypeIssue = "issue" + githubObjectTypeIssueComment = "issue_comment" + githubObjectTypePRReviewComment = "pr_review_comment" ) // RenderConfig holds various configuration options to be used in a template @@ -387,6 +395,13 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { } } + repoName := strings.ToLower(repo.GetFullName()) + prNumber := event.GetPullRequest().Number + + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, prNumber) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssue) + if !contained && label != "" { continue } @@ -555,6 +570,13 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { Message: renderedMessage, } + repoName := strings.ToLower(repo.GetFullName()) + issueNumber := issue.Number + + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, issueNumber) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssue) + label := sub.Label() contained := false @@ -731,6 +753,13 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { Type: "custom_git_comment", } + repoName := strings.ToLower(repo.GetFullName()) + commentID := event.GetComment().GetID() + + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, commentID) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssueComment) + labels := make([]string, len(event.GetIssue().Labels)) for i, v := range event.GetIssue().Labels { labels[i] = v.GetName() @@ -864,6 +893,13 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi Message: newReviewMessage, } + repoName := strings.ToLower(repo.GetFullName()) + commentID := event.GetComment().GetID() + + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, commentID) + post.AddProp(postPropGithubObjectType, githubObjectTypePRReviewComment) + labels := make([]string, len(event.GetPullRequest().Labels)) for i, v := range event.GetPullRequest().Labels { labels[i] = v.GetName()