Skip to content

Commit

Permalink
[mattermostGH-613]:Fixed issue "API rate limit exceeded for user ID". (
Browse files Browse the repository at this point in the history
…mattermost#626)

* [MI-2481]:Fixed issue mattermost#613 on github (#12)

* [MI-2481]:Fixed issue mattermost#681 on github

* [MI-2481]:Fixed review fixes

* [MI-2481]:Fixed review fixes

* [MI-2481]:Fixed review fixes

* [MI-2547]:Fixed review fixes for Github issue 613 and PR 626 (#14)

* [MI-2547]:Fixed review fixes for Github issue 613 and PR 626

* [MI-2547]:Fixed review fixes

* [MI-2547]:Fixed review fixes

* [MI-2582]:Fixed review comments for github issue mattermost#613 (#15)

* [MM-613]:Fixed review comments

* [MI-3072]:Converted the LHS APIs into GraphQL (#32)

* [MI-3012]: Converted user PRs API into GraphQL

* [MI-3012]: Fixed review comments

* [MI-3012]:fixed log message

* [MI-3035]: Converted the get assignment API into GraphQL

* [MI-3035]:Fixed self review comments

* [MI-3035]: Fixed review comments

* [MI-3072]:Converted review requested API to graphQL

* [MI-3072]:Combined all the graphQL queries

* [MI-3072]:Fixed CI errors

* [MI-3072]:Fixed review comments

* [MI-3072]:Fixed CI errors

* [MI-3072]:Fixed review comments

* [MI-3072]:Fixed review comments

* [MM-613]:Changed namings

* [MM-613]:Fixed review comments

* [MM-613]:Fixed review comments

* [MM-613]:Fixed panic error

* [MM-613]:Fixed review comments

* [MM-613]:Fixed lint errors

* [MM-613]:Fixed review comment

* [MM-613]:Fixed review comments

* [MM-613]:Fixed review comments
  • Loading branch information
Kshitij-Katiyar authored Jul 19, 2023
1 parent 4ef49d5 commit 3ea161c
Show file tree
Hide file tree
Showing 18 changed files with 507 additions and 260 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ require (
github.com/rudderlabs/analytics-go v3.3.2+incompatible // indirect
github.com/segmentio/backo-go v1.0.1 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/stretchr/objx v0.4.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,14 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9 h1:nCBaIs5/R0HFP5+aPW/SzFUF8z0oKuCXmuDmHWaxzjY=
github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
Expand Down
102 changes: 46 additions & 56 deletions server/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ type PRDetails struct {
Reviews []*github.PullRequestReview `json:"reviews"`
}

type FilteredNotification struct {
github.Notification
HTMLURL string `json:"html_url"`
}

type SidebarContent struct {
PRs []*github.Issue `json:"prs"`
Reviews []*github.Issue `json:"reviews"`
Assignments []*github.Issue `json:"assignments"`
Unreads []*FilteredNotification `json:"unreads"`
}

type Context struct {
Ctx context.Context
UserID string
Expand Down Expand Up @@ -134,22 +146,19 @@ func (p *Plugin) initializeAPI() {

apiRouter.HandleFunc("/user", p.checkAuth(p.attachContext(p.getGitHubUser), ResponseTypeJSON)).Methods(http.MethodPost)
apiRouter.HandleFunc("/todo", p.checkAuth(p.attachUserContext(p.postToDo), ResponseTypeJSON)).Methods(http.MethodPost)
apiRouter.HandleFunc("/reviews", p.checkAuth(p.attachUserContext(p.getReviews), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/yourprs", p.checkAuth(p.attachUserContext(p.getYourPrs), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/prsdetails", p.checkAuth(p.attachUserContext(p.getPrsDetails), ResponseTypePlain)).Methods(http.MethodPost)
apiRouter.HandleFunc("/searchissues", p.checkAuth(p.attachUserContext(p.searchIssues), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/yourassignments", p.checkAuth(p.attachUserContext(p.getYourAssignments), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/createissue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost)
apiRouter.HandleFunc("/createissuecomment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost)
apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/unreads", p.checkAuth(p.attachUserContext(p.getUnreads), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/labels", p.checkAuth(p.attachUserContext(p.getLabels), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/milestones", p.checkAuth(p.attachUserContext(p.getMilestones), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/assignees", p.checkAuth(p.attachUserContext(p.getAssignees), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/repositories", p.checkAuth(p.attachUserContext(p.getRepositories), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/settings", p.checkAuth(p.attachUserContext(p.updateSettings), ResponseTypePlain)).Methods(http.MethodPost)
apiRouter.HandleFunc("/issue", p.checkAuth(p.attachUserContext(p.getIssueByNumber), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/pr", p.checkAuth(p.attachUserContext(p.getPrByNumber), ResponseTypePlain)).Methods(http.MethodGet)
apiRouter.HandleFunc("/lhs-content", p.checkAuth(p.attachUserContext(p.getSidebarContent), ResponseTypePlain)).Methods(http.MethodGet)

apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet)
apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet)
Expand Down Expand Up @@ -651,22 +660,16 @@ func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Requ
p.writeJSON(w, result.Issues)
}

func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Request) {
func (p *Plugin) getUnreadsData(c *UserContext) []*FilteredNotification {
githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)

notifications, _, err := githubClient.Activity.ListNotifications(c.Ctx, &github.NotificationListOptions{})
if err != nil {
c.Log.WithError(err).Warnf("Failed to list notifications")
return
}

type filteredNotification struct {
github.Notification

HTMLUrl string `json:"html_url"`
return nil
}

filteredNotifications := []*filteredNotification{}
filteredNotifications := []*FilteredNotification{}
for _, n := range notifications {
if n.GetReason() == notificationReasonSubscribed {
continue
Expand All @@ -684,45 +687,13 @@ func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Reque
subjectURL = n.GetSubject().GetLatestCommentURL()
}

filteredNotifications = append(filteredNotifications, &filteredNotification{
filteredNotifications = append(filteredNotifications, &FilteredNotification{
Notification: *n,
HTMLUrl: fixGithubNotificationSubjectURL(subjectURL, issueNum),
HTMLURL: fixGithubNotificationSubjectURL(subjectURL, issueNum),
})
}

p.writeJSON(w, filteredNotifications)
}

func (p *Plugin) getReviews(c *UserContext, w http.ResponseWriter, r *http.Request) {
config := p.getConfiguration()

githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
username := c.GHInfo.GitHubUsername

query := getReviewSearchQuery(username, config.GitHubOrg)
result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
if err != nil {
c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for review")
return
}

p.writeJSON(w, result.Issues)
}

func (p *Plugin) getYourPrs(c *UserContext, w http.ResponseWriter, r *http.Request) {
config := p.getConfiguration()

githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
username := c.GHInfo.GitHubUsername

query := getYourPrsSearchQuery(username, config.GitHubOrg)
result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
if err != nil {
c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for PRs")
return
}

p.writeJSON(w, result.Issues)
return filteredNotifications
}

func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -967,20 +938,39 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht
p.writeJSON(w, result)
}

func (p *Plugin) getYourAssignments(c *UserContext, w http.ResponseWriter, r *http.Request) {
config := p.getConfiguration()
func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*github.Issue, assignmentResp []*github.Issue, openPRResp []*github.Issue, err error) {
graphQLClient := p.graphQLConnect(c.GHInfo)

githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Context.Ctx)
if err != nil {
return []*github.Issue{}, []*github.Issue{}, []*github.Issue{}, err
}

username := c.GHInfo.GitHubUsername
query := getYourAssigneeSearchQuery(username, config.GitHubOrg)
result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
return reviewResp, assignmentResp, openPRResp, nil
}

func (p *Plugin) getSidebarData(c *UserContext) (*SidebarContent, error) {
reviewResp, assignmentResp, openPRResp, err := p.getLHSData(c)
if err != nil {
c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for assignments")
return nil, err
}

return &SidebarContent{
PRs: openPRResp,
Assignments: assignmentResp,
Reviews: reviewResp,
Unreads: p.getUnreadsData(c),
}, nil
}

func (p *Plugin) getSidebarContent(c *UserContext, w http.ResponseWriter, r *http.Request) {
sidebarContent, err := p.getSidebarData(c)
if err != nil {
c.Log.WithError(err).Warnf("Failed to search for the sidebar data")
return
}

p.writeJSON(w, result.Issues)
p.writeJSON(w, sidebarContent)
}

func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request) {
Expand Down
2 changes: 1 addition & 1 deletion server/plugin/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestPlugin_ServeHTTP(t *testing.T) {
httpTest: httpTestString,
request: testutils.Request{
Method: http.MethodGet,
URL: "/api/v1/reviews",
URL: "/api/v1/lhs-content",
Body: nil,
},
expectedResponse: testutils.ExpectedResponse{
Expand Down
62 changes: 62 additions & 0 deletions server/plugin/graphql/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package graphql

import (
"context"
"net/url"
"path"

pluginapi "github.com/mattermost/mattermost-plugin-api"
"github.com/pkg/errors"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
)

// Client encapsulates the third party package that communicates with Github GraphQL API
type Client struct {
client *githubv4.Client
org string
username string
logger pluginapi.LogService
}

// NewClient creates and returns Client. The third party package that queries GraphQL is initialized here.
func NewClient(logger pluginapi.LogService, token oauth2.Token, username, orgName, enterpriseBaseURL string) *Client {
ts := oauth2.StaticTokenSource(&token)
httpClient := oauth2.NewClient(context.Background(), ts)
var client Client

if enterpriseBaseURL == "" {
client = Client{
username: username,
client: githubv4.NewClient(httpClient),
logger: logger,
org: orgName,
}
} else {
baseURL, err := url.Parse(enterpriseBaseURL)
if err != nil {
logger.Debug("Not able to parse the URL", "Error", err.Error())
return nil
}

baseURL.Path = path.Join(baseURL.Path, "api", "graphql")

client = Client{
client: githubv4.NewEnterpriseClient(baseURL.String(), httpClient),
username: username,
org: orgName,
logger: logger,
}
}

return &client
}

// executeQuery takes a query struct and sends it to Github GraphQL API via helper package.
func (c *Client) executeQuery(ctx context.Context, qry interface{}, params map[string]interface{}) error {
if err := c.client.Query(ctx, qry, params); err != nil {
return errors.Wrap(err, "error in executing query")
}

return nil
}
91 changes: 91 additions & 0 deletions server/plugin/graphql/lhs_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package graphql

import (
"github.com/shurcooL/githubv4"
)

type (
repositoryQuery struct {
Name githubv4.String
NameWithOwner githubv4.String
URL githubv4.URI
}

authorQuery struct {
Login githubv4.String
}

prSearchNodes struct {
PullRequest struct {
Body githubv4.String
Number githubv4.Int
AuthorAssociation githubv4.String
CreatedAt githubv4.DateTime
UpdatedAt githubv4.DateTime
Repository repositoryQuery
State githubv4.String
Title githubv4.String
Author authorQuery
URL githubv4.URI
} `graphql:"... on PullRequest"`
}
)

type (
assignmentSearchNodes struct {
Issue struct {
Body githubv4.String
Number githubv4.Int
AuthorAssociation githubv4.String
CreatedAt githubv4.DateTime
UpdatedAt githubv4.DateTime
Repository repositoryQuery
State githubv4.String
Title githubv4.String
Author authorQuery
URL githubv4.URI
} `graphql:"... on Issue"`

PullRequest struct {
Body githubv4.String
Number githubv4.Int
AuthorAssociation githubv4.String
CreatedAt githubv4.DateTime
UpdatedAt githubv4.DateTime
Repository repositoryQuery
State githubv4.String
Title githubv4.String
Author authorQuery
URL githubv4.URI
} `graphql:"... on PullRequest"`
}
)

var mainQuery struct {
ReviewRequests struct {
IssueCount int
Nodes []prSearchNodes
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"pullRequest: search(first:100, after:$reviewsCursor, query: $prReviewQueryArg, type: ISSUE)"`

Assignments struct {
IssueCount int
Nodes []assignmentSearchNodes
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"assignee: search(first:100, after:$assignmentsCursor, query: $assigneeQueryArg, type: ISSUE)"`

OpenPullRequests struct {
IssueCount int
Nodes []prSearchNodes
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"graphql: search(first:100, after:$openPrsCursor, query: $prOpenQueryArg, type: ISSUE)"`
}
Loading

0 comments on commit 3ea161c

Please sign in to comment.