Skip to content

Commit

Permalink
[MM-420] Add feature to support multiple orgs in plugin settings (mat…
Browse files Browse the repository at this point in the history
…termost#773)

* [MM-420] Add feature to support multiple orgs in plugin settings

* [MM-420] Fix ci

* [MM-420] Review fixes: Update logic of excluding members from flag, update function definition and minor reveiw fixes

* [MM-420] Update function to get organization and move it to configuration file

* [MM-420] Update check org function

* [MM-420] Fix lint error

* [MM-420]: review fixes

* Update package-lock file to fix CI errors

* Add default value for organizations

* Fix issue reported by QA: RHS not updating properly in case of multiple orgs

* fix lint error

* Review fix: Update help text

Co-authored-by: Doug Lauder <[email protected]>

---------

Co-authored-by: kshitij katiyar <[email protected]>
Co-authored-by: raghavaggarwal2308 <[email protected]>
Co-authored-by: Doug Lauder <[email protected]>
  • Loading branch information
4 people authored Jul 30, 2024
1 parent 133f527 commit d1a6d8d
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 133 deletions.
4 changes: 2 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
},
{
"key": "GithubOrg",
"display_name": "GitHub Organization:",
"display_name": "GitHub Organizations:",
"type": "text",
"help_text": "(Optional) Set to lock the plugin to a single GitHub organization."
"help_text": "(Optional) Set to lock the plugin to one or more GitHub organizations. Provide multiple orgs using a comma-separated list."
},
{
"key": "EnterpriseBaseURL",
Expand Down
67 changes: 38 additions & 29 deletions server/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,15 +480,15 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter,
}

config := p.getConfiguration()

orgList := p.configuration.getOrganizations()
p.client.Frontend.PublishWebSocketEvent(
wsEventConnect,
map[string]interface{}{
"connected": true,
"github_username": userInfo.GitHubUsername,
"github_client_id": config.GitHubOAuthClientID,
"enterprise_base_url": config.EnterpriseBaseURL,
"organization": config.GitHubOrg,
"organizations": orgList,
"configuration": config.ClientConfiguration(),
},
&model.WebsocketBroadcast{UserId: state.UserID},
Expand Down Expand Up @@ -565,15 +565,16 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request
GitHubUsername string `json:"github_username"`
GitHubClientID string `json:"github_client_id"`
EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"`
Organization string `json:"organization"`
Organizations []string `json:"organizations"`
UserSettings *UserSettings `json:"user_settings"`
ClientConfiguration map[string]interface{} `json:"configuration"`
}

orgList := p.configuration.getOrganizations()
resp := &ConnectedResponse{
Connected: false,
EnterpriseBaseURL: config.EnterpriseBaseURL,
Organization: config.GitHubOrg,
Organizations: orgList,
ClientConfiguration: p.getConfiguration().ClientConfiguration(),
}

Expand Down Expand Up @@ -645,11 +646,10 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request
}

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

githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
username := c.GHInfo.GitHubUsername
query := getMentionSearchQuery(username, config.GitHubOrg)
orgList := p.configuration.getOrganizations()
query := getMentionSearchQuery(username, orgList)

result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
if err != nil {
Expand All @@ -662,7 +662,6 @@ func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Requ

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")
Expand Down Expand Up @@ -797,20 +796,24 @@ func getRepoOwnerAndNameFromURL(url string) (string, string) {
}

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

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

searchTerm := r.FormValue("term")
query := getIssuesSearchQuery(config.GitHubOrg, searchTerm)
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 issues")
p.writeJSON(w, make([]*github.Issue, 0))
return
orgsList := p.configuration.getOrganizations()
allIssues := []*github.Issue{}
for _, org := range orgsList {
query := getIssuesSearchQuery(org, searchTerm)
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 issues")
p.writeJSON(w, make([]*github.Issue, 0))
return
}

allIssues = append(allIssues, result.Issues...)
}

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

func (p *Plugin) getPermaLink(postID string) string {
Expand Down Expand Up @@ -1226,12 +1229,11 @@ func getRepositoryListByOrg(c context.Context, org string, githubClient *github.

func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.Request) {
githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)

org := p.getConfiguration().GitHubOrg

var allRepos []*github.Repository
var err error
var statusCode int

opt := github.ListOptions{PerPage: 50}

if org == "" {
Expand All @@ -1242,19 +1244,26 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.
return
}
} else {
allRepos, statusCode, err = getRepositoryListByOrg(c.Ctx, org, githubClient, opt)
if err != nil {
if statusCode == http.StatusNotFound {
allRepos, err = getRepositoryList(c.Ctx, org, githubClient, opt)
if err != nil {
c.Log.WithError(err).Warnf("Failed to list repositories")
orgsList := p.configuration.getOrganizations()
for _, org := range orgsList {
orgRepos, statusCode, err := getRepositoryListByOrg(c.Ctx, org, githubClient, opt)
if err != nil {
if statusCode == http.StatusNotFound {
orgRepos, err = getRepositoryList(c.Ctx, org, githubClient, opt)
if err != nil {
c.Log.WithError(err).Warnf("Failed to list repositories", "Organization", org)
p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
return
}
} else {
c.Log.WithError(err).Warnf("Failed to list repositories", "Organization", org)
p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
return
}
} else {
c.Log.WithError(err).Warnf("Failed to list repositories")
p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
return
}

if len(orgRepos) > 0 {
allRepos = append(allRepos, orgRepos...)
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions server/plugin/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,20 @@ func generateSecret() (string, error) {

return s, nil
}

func (c *Configuration) getOrganizations() []string {
if c.GitHubOrg == "" {
return []string{}
}

list := strings.Split(c.GitHubOrg, ",")
allOrgs := []string{}
for _, org := range list {
org = strings.TrimSpace(strings.ToLower(org))
if org != "" {
allOrgs = append(allOrgs, org)
}
}

return allOrgs
}
28 changes: 28 additions & 0 deletions server/plugin/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,31 @@ func TestSetDefaults(t *testing.T) {
})
}
}

func TestGetOrganizations(t *testing.T) {
tcs := []struct {
Organizations string
ExpectedOrgList []string
}{
{
Organizations: "org-1,org-2",
ExpectedOrgList: []string{"org-1", "org-2"},
},
{
Organizations: "org-1,org-2,",
ExpectedOrgList: []string{"org-1", "org-2"},
},
{
Organizations: "org-1, org-2 ",
ExpectedOrgList: []string{"org-1", "org-2"},
},
}

for _, tc := range tcs {
config := Configuration{
GitHubOrg: tc.Organizations,
}
orgList := config.getOrganizations()
assert.Equal(t, tc.ExpectedOrgList, orgList)
}
}
29 changes: 16 additions & 13 deletions server/plugin/graphql/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,26 @@ import (

// 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
client *githubv4.Client
org string
username string
logger pluginapi.LogService
getOrganizations func() []string
}

// 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 {
func NewClient(logger pluginapi.LogService, getOrganizations func() []string, 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,
username: username,
client: githubv4.NewClient(httpClient),
logger: logger,
org: orgName,
getOrganizations: getOrganizations,
}
} else {
baseURL, err := url.Parse(enterpriseBaseURL)
Expand All @@ -43,10 +45,11 @@ func NewClient(logger pluginapi.LogService, token oauth2.Token, username, orgNam
baseURL.Path = path.Join(baseURL.Path, "api", "graphql")

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

Expand Down
103 changes: 52 additions & 51 deletions server/plugin/graphql/lhs_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,73 +20,74 @@ const (
)

func (c *Client) GetLHSData(ctx context.Context) ([]*github.Issue, []*github.Issue, []*github.Issue, error) {
params := map[string]interface{}{
queryParamOpenPRQueryArg: githubv4.String(fmt.Sprintf("author:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamReviewPRQueryArg: githubv4.String(fmt.Sprintf("review-requested:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamAssigneeQueryArg: githubv4.String(fmt.Sprintf("assignee:%s is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamReviewsCursor: (*githubv4.String)(nil),
queryParamAssignmentsCursor: (*githubv4.String)(nil),
queryParamOpenPRsCursor: (*githubv4.String)(nil),
}

if c.org != "" {
params[queryParamOpenPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamOpenPRQueryArg]))
params[queryParamReviewPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamReviewPRQueryArg]))
params[queryParamAssigneeQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamAssigneeQueryArg]))
}

orgsList := c.getOrganizations()
var resultReview, resultAssignee, resultOpenPR []*github.Issue
allReviewRequestsFetched, allAssignmentsFetched, allOpenPRsFetched := false, false, false

for {
if allReviewRequestsFetched && allAssignmentsFetched && allOpenPRsFetched {
break
for _, org := range orgsList {
params := map[string]interface{}{
queryParamOpenPRQueryArg: githubv4.String(fmt.Sprintf("author:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamReviewPRQueryArg: githubv4.String(fmt.Sprintf("review-requested:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamAssigneeQueryArg: githubv4.String(fmt.Sprintf("assignee:%s is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
queryParamReviewsCursor: (*githubv4.String)(nil),
queryParamAssignmentsCursor: (*githubv4.String)(nil),
queryParamOpenPRsCursor: (*githubv4.String)(nil),
}

if err := c.executeQuery(ctx, &mainQuery, params); err != nil {
return nil, nil, nil, errors.Wrap(err, "Not able to excute the query")
}
params[queryParamOpenPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", org, params[queryParamOpenPRQueryArg]))
params[queryParamReviewPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", org, params[queryParamReviewPRQueryArg]))
params[queryParamAssigneeQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", org, params[queryParamAssigneeQueryArg]))

allReviewRequestsFetched, allAssignmentsFetched, allOpenPRsFetched := false, false, false

if !allReviewRequestsFetched {
for i := range mainQuery.ReviewRequests.Nodes {
resp := mainQuery.ReviewRequests.Nodes[i]
pr := getPR(&resp)
resultReview = append(resultReview, pr)
for {
if allReviewRequestsFetched && allAssignmentsFetched && allOpenPRsFetched {
break
}

if !mainQuery.ReviewRequests.PageInfo.HasNextPage {
allReviewRequestsFetched = true
if err := c.executeQuery(ctx, &mainQuery, params); err != nil {
return nil, nil, nil, errors.Wrap(err, "Not able to excute the query")
}

params[queryParamReviewsCursor] = githubv4.NewString(mainQuery.ReviewRequests.PageInfo.EndCursor)
}
if !allReviewRequestsFetched {
for i := range mainQuery.ReviewRequests.Nodes {
resp := mainQuery.ReviewRequests.Nodes[i]
pr := getPR(&resp)
resultReview = append(resultReview, pr)
}

if !allAssignmentsFetched {
for i := range mainQuery.Assignments.Nodes {
resp := mainQuery.Assignments.Nodes[i]
issue := newIssueFromAssignmentResponse(&resp)
resultAssignee = append(resultAssignee, issue)
}
if !mainQuery.ReviewRequests.PageInfo.HasNextPage {
allReviewRequestsFetched = true
}

if !mainQuery.Assignments.PageInfo.HasNextPage {
allAssignmentsFetched = true
params[queryParamReviewsCursor] = githubv4.NewString(mainQuery.ReviewRequests.PageInfo.EndCursor)
}

params[queryParamAssignmentsCursor] = githubv4.NewString(mainQuery.Assignments.PageInfo.EndCursor)
}
if !allAssignmentsFetched {
for i := range mainQuery.Assignments.Nodes {
resp := mainQuery.Assignments.Nodes[i]
issue := newIssueFromAssignmentResponse(&resp)
resultAssignee = append(resultAssignee, issue)
}

if !allOpenPRsFetched {
for i := range mainQuery.OpenPullRequests.Nodes {
resp := mainQuery.OpenPullRequests.Nodes[i]
pr := getPR(&resp)
resultOpenPR = append(resultOpenPR, pr)
}
if !mainQuery.Assignments.PageInfo.HasNextPage {
allAssignmentsFetched = true
}

if !mainQuery.OpenPullRequests.PageInfo.HasNextPage {
allOpenPRsFetched = true
params[queryParamAssignmentsCursor] = githubv4.NewString(mainQuery.Assignments.PageInfo.EndCursor)
}

params[queryParamOpenPRsCursor] = githubv4.NewString(mainQuery.OpenPullRequests.PageInfo.EndCursor)
if !allOpenPRsFetched {
for i := range mainQuery.OpenPullRequests.Nodes {
resp := mainQuery.OpenPullRequests.Nodes[i]
pr := getPR(&resp)
resultOpenPR = append(resultOpenPR, pr)
}

if !mainQuery.OpenPullRequests.PageInfo.HasNextPage {
allOpenPRsFetched = true
}

params[queryParamOpenPRsCursor] = githubv4.NewString(mainQuery.OpenPullRequests.PageInfo.EndCursor)
}
}
}

Expand Down
Loading

0 comments on commit d1a6d8d

Please sign in to comment.