Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
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 a GitHub organizations. Provide multiple orgs using a comma-separated list."
raghavaggarwal2308 marked this conversation as resolved.
Show resolved Hide resolved
},
{
"key": "EnterpriseBaseURL",
Expand Down
64 changes: 36 additions & 28 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,19 +796,22 @@ 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")
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")
}

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 @@ -1225,12 +1227,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 @@ -1241,19 +1242,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
Loading