From 3be07852c670bf21af8aa41a014d55ed4bbd9bcb Mon Sep 17 00:00:00 2001 From: Lucas Reis Date: Tue, 10 Oct 2023 03:25:54 -0300 Subject: [PATCH] Add support for Bitbucket Self-Hosted URL (#118) * feat: creates new config to set a BitbucketSelfHostedUrl * feat: creates new plugin method to get the bitbucket baseURL and replaces the locations where the url is used * chore: renaming and refactoring regarding PR review * feat: adding docker-compose setting up bitbucket server * feat: adding support for Apple M1 in the build * feat: adds the API Self-Hosted URL * feat: adds the code to check for the Bitbucket API Self Hosted URL * feat: refactor of the plugin to be less repetitive on getting the configuration * feat: refactor to get back to the shortcut string parameters * test: adding tests for the new methods * add gitpod files * feat: adding a verification for the bitbucket URL if it's valid * feat: adding conditionals to initialize oauth based on bitbucket server configs * feat: PR review * fix: fix lint --------- Co-authored-by: Michael Kochell --- .gitpod/command.sh | 1 + .gitpod/init.sh | 1 + Makefile | 13 +++++-- docker-compose.yml | 14 ++++++++ plugin.json | 17 +++++++++ server/api.go | 5 ++- server/api_test.go | 4 +-- server/command.go | 7 ++-- server/configuration.go | 31 ++++++++++++++++ server/manifest.go | 17 +++++++++ server/plugin.go | 78 +++++++++++++++++++++++++++++++---------- server/plugin_test.go | 73 ++++++++++++++++++++++++++++++++++++++ server/subscriptions.go | 2 +- server/utils.go | 28 +++++++-------- server/utils_test.go | 2 +- server/webhook.go | 2 +- webapp/src/manifest.js | 17 +++++++++ 17 files changed, 263 insertions(+), 49 deletions(-) create mode 100755 .gitpod/command.sh create mode 100755 .gitpod/init.sh create mode 100644 docker-compose.yml create mode 100644 server/plugin_test.go diff --git a/.gitpod/command.sh b/.gitpod/command.sh new file mode 100755 index 0000000..59f1c5d --- /dev/null +++ b/.gitpod/command.sh @@ -0,0 +1 @@ +docker compose up diff --git a/.gitpod/init.sh b/.gitpod/init.sh new file mode 100755 index 0000000..811b7ef --- /dev/null +++ b/.gitpod/init.sh @@ -0,0 +1 @@ +docker compose pull diff --git a/Makefile b/Makefile index 8c0cfcf..ee85b29 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,12 @@ ifneq ($(wildcard build/custom.mk),) include build/custom.mk endif +ifneq ($(MM_DEBUG),) + GO_BUILD_GCFLAGS = -gcflags "all=-N -l" +else + GO_BUILD_GCFLAGS = +endif + ## Checks the code style, tests, builds and bundles the plugin. .PHONY: all all: check-style test dist @@ -69,9 +75,10 @@ ifeq ($(MM_DEBUG),) else $(info DEBUG mode is on; to disable, unset MM_DEBUG) - cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-darwin-amd64; - cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-linux-amd64; - cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-windows-amd64.exe; + cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -o dist/plugin-darwin-amd64; + cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -o dist/plugin-linux-amd64; + cd server && env GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -o dist/plugin-darwin-arm64; + cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -o dist/plugin-windows-amd64.exe; endif endif diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ca75c7b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.7' + +services: + bitbucket: + container_name: mattermost-bitbucket + image: atlassian/bitbucket:latest # It is recommended to use the bitbucket image instead of bitbucket-server since it's deprecated + ports: + - "7990:7990" + - "7999:7999" + volumes: + - bitbucket-data:/var/atlassian/application-data/bitbucket + +volumes: + bitbucket-data: diff --git a/plugin.json b/plugin.json index 40f7a84..990ce6a 100644 --- a/plugin.json +++ b/plugin.json @@ -12,6 +12,7 @@ "executables": { "linux-amd64": "server/dist/plugin-linux-amd64", "darwin-amd64": "server/dist/plugin-darwin-amd64", + "darwin-arm64": "server/dist/plugin-darwin-arm64", "windows-amd64": "server/dist/plugin-windows-amd64.exe" }, "executable": "" @@ -62,6 +63,22 @@ "help_text": "(Optional) Set to lock the plugin to a single Bitbucket organization.", "placeholder": "", "default": null + }, + { + "key": "BitbucketSelfHostedURL", + "display_name": "Bitbucket Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", + "placeholder": "", + "default": null + }, + { + "key": "BitbucketAPISelfHostedURL", + "display_name": "Bitbucket API Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket API server's URL.", + "placeholder": "", + "default": null } ] } diff --git a/server/api.go b/server/api.go index d85cd1f..86df01a 100644 --- a/server/api.go +++ b/server/api.go @@ -15,7 +15,6 @@ import ( "golang.org/x/oauth2" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/plugin" ) const ( @@ -155,7 +154,7 @@ func checkPluginRequest(next http.HandlerFunc) http.HandlerFunc { } } -func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() if err := config.IsValid(); err != nil { @@ -247,7 +246,7 @@ func (p *Plugin) completeConnectUserToBitbucket(w http.ResponseWriter, r *http.R _ = httpResponse.Body.Close() } if err != nil { - p.API.LogError("Error converting authorization code int token", "err", err.Error()) + p.API.LogError("Error while trying to get users from bitbucket", "err", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/server/api_test.go b/server/api_test.go index 5959923..45b0985 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -72,7 +72,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { req := test.httpTest.CreateHTTPRequest(test.request) req.Header.Add("Mattermost-User-ID", test.userID) rr := httptest.NewRecorder() - p.ServeHTTP(&plugin.Context{}, rr, req) + p.ServeHTTP(rr, req) test.httpTest.CompareHTTPResponse(rr, test.expectedResponse) }) } @@ -122,7 +122,7 @@ func TestGetToken(t *testing.T) { req := test.httpTest.CreateHTTPRequest(test.request) rr := httptest.NewRecorder() - p.ServeHTTP(test.context, rr, req) + p.ServeHTTP(rr, req) test.httpTest.CompareHTTPResponse(rr, test.expectedResponse) }) diff --git a/server/command.go b/server/command.go index 73419b9..fe1fb68 100644 --- a/server/command.go +++ b/server/command.go @@ -225,7 +225,8 @@ func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, par ctx := context.Background() bitbucketClient := p.bitbucketConnect(*userInfo.Token) - owner, repo := parseOwnerAndRepo(parameters[0], BitbucketBaseURL) + bitbucketURL := p.getBitbucketBaseURL() + owner, repo := parseOwnerAndRepo(parameters[0], bitbucketURL) previousSubscribedEvents, err := p.findSubscriptionsEvents(args.ChannelId, owner, repo) if err != nil { return err.Error() @@ -243,7 +244,7 @@ func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, par return err.Error() } - repoLink := fmt.Sprintf("%s%s/%s", p.getBaseURL(), owner, repo) + repoLink := fmt.Sprintf("%s%s/%s", bitbucketURL, owner, repo) msg := fmt.Sprintf("Successfully subscribed to [%s/%s](%s) with events: %s", owner, repo, repoLink, formattedString(features)) if previousSubscribedEvents != "" { @@ -319,7 +320,7 @@ func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, u return text } -func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *BitbucketUserInfo) string { +func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *BitbucketUserInfo) string { message := fmt.Sprintf("#### Welcome to the Mattermost Bitbucket Plugin!\n" + "##### Daily Reminders\n" + "The first time you log in each day, you will get a post right here letting you know what messages you need to read and what pull requests are awaiting your review.\n" + diff --git a/server/configuration.go b/server/configuration.go index 81e27fe..32e7168 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -1,6 +1,7 @@ package main import ( + "net/url" "reflect" "github.com/pkg/errors" @@ -21,6 +22,8 @@ type Configuration struct { BitbucketOrg string BitbucketOAuthClientID string BitbucketOAuthClientSecret string + BitbucketSelfHostedURL string + BitbucketAPISelfHostedURL string WebhookSecret string EncryptionKey string } @@ -49,9 +52,37 @@ func (c *Configuration) IsValid() error { if c.WebhookSecret == "" { return errors.New("must have a webhook secret") } + + if c.BitbucketSelfHostedURL != "" && !c.IsValidURL(c.BitbucketSelfHostedURL) { + return errors.New("BitbucketSelfHostedURL must be a valid URL") + } + + if c.BitbucketAPISelfHostedURL != "" && !c.IsValidURL(c.BitbucketAPISelfHostedURL) { + return errors.New("BitbucketAPISelfHostedURL must be a valid URL") + } return nil } +// IsValidURL checks if an URL passed is valid +func (c *Configuration) IsValidURL(toTest string) bool { + _, err := url.ParseRequestURI(toTest) + if err != nil { + return false + } + + u, err := url.Parse(toTest) + if err != nil || u.Scheme == "" || u.Host == "" { + return false + } + + return true +} + +// IsBitbucketSelfHosted checks if we are dealing with self-hosted bitbucket +func (c *Configuration) IsBitbucketSelfHosted() bool { + return c.BitbucketSelfHostedURL != "" && c.BitbucketAPISelfHostedURL != "" +} + // getConfiguration retrieves the active Configuration under lock, making it safe to use // concurrently. The active Configuration may change underneath the client of this method, but // the struct returned by this API call is considered immutable. diff --git a/server/manifest.go b/server/manifest.go index e116a22..97b8bdb 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -25,6 +25,7 @@ const manifestStr = ` "server": { "executables": { "darwin-amd64": "server/dist/plugin-darwin-amd64", + "darwin-arm64": "server/dist/plugin-darwin-arm64", "linux-amd64": "server/dist/plugin-linux-amd64", "windows-amd64": "server/dist/plugin-windows-amd64.exe" }, @@ -76,6 +77,22 @@ const manifestStr = ` "help_text": "(Optional) Set to lock the plugin to a single Bitbucket organization.", "placeholder": "", "default": null + }, + { + "key": "BitbucketSelfHostedURL", + "display_name": "Bitbucket Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", + "placeholder": "", + "default": null + }, + { + "key": "BitbucketAPISelfHostedURL", + "display_name": "Bitbucket API Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket API server's URL.", + "placeholder": "", + "default": null } ] } diff --git a/server/plugin.go b/server/plugin.go index 57c84ce..b41be79 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -30,7 +30,8 @@ const ( BitbucketOauthKey = "bitbucketoauthkey_" BitbucketAccountIDKey = "_bitbucketaccountid" - BitbucketBaseURL = "https://bitbucket.org/" + BitbucketBaseURL = "https://bitbucket.org/" + BitbucketAPIBaseURL = "https://api.bitbucket.org/2.0" WsEventConnect = "connect" WsEventDisconnect = "disconnect" @@ -160,15 +161,26 @@ func (p *Plugin) OnActivate() error { func (p *Plugin) getOAuthConfig() *oauth2.Config { config := p.getConfiguration() - authURL, _ := url.Parse(BitbucketBaseURL) - tokenURL, _ := url.Parse(BitbucketBaseURL) - authURL.Path = path.Join(authURL.Path, "site", "oauth2", "authorize") - tokenURL.Path = path.Join(tokenURL.Path, "site", "oauth2", "access_token") + bitbucketURL := p.getBitbucketBaseURL() + + authURL, _ := url.Parse(bitbucketURL) + tokenURL, _ := url.Parse(bitbucketURL) + scopes := []string{} + + if config.IsBitbucketSelfHosted() { + authURL.Path = path.Join(authURL.Path, "rest", "oauth2", "latest", "authorize") + tokenURL.Path = path.Join(tokenURL.Path, "rest", "oauth2", "latest", "token") + scopes = append(scopes, "PUBLIC_REPOS", "REPO_READ", "ACCOUNT_WRITE") + } else { + authURL.Path = path.Join(authURL.Path, "site", "oauth2", "authorize") + tokenURL.Path = path.Join(tokenURL.Path, "site", "oauth2", "access_token") + scopes = append(scopes, "repository") + } return &oauth2.Config{ ClientID: config.BitbucketOAuthClientID, ClientSecret: config.BitbucketOAuthClientSecret, - Scopes: []string{"repository"}, + Scopes: scopes, RedirectURL: fmt.Sprintf("%s/plugins/%s/oauth/complete", *p.API.GetConfig().ServiceSettings.SiteURL, manifest.Id), Endpoint: oauth2.Endpoint{ AuthURL: authURL.String(), @@ -303,6 +315,8 @@ func (p *Plugin) PostToDo(info *BitbucketUserInfo) { } func (p *Plugin) GetToDo(ctx context.Context, userInfo *BitbucketUserInfo, bitbucketClient *bitbucket.APIClient) (string, error) { + bitbucketURL := p.getBitbucketBaseURL() + userRepos, err := p.getUserRepositories(ctx, bitbucketClient) if err != nil { return "", errors.Wrap(err, "error occurred while searching for repositories") @@ -331,7 +345,7 @@ func (p *Plugin) GetToDo(ctx context.Context, userInfo *BitbucketUserInfo, bitbu text += fmt.Sprintf("You have %v assignments:\n", len(yourAssignments)) for _, assign := range yourAssignments { - text += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "") + text += getToDoDisplayText(bitbucketURL, assign.Title, assign.Links.Html.Href, "") } } @@ -343,7 +357,7 @@ func (p *Plugin) GetToDo(ctx context.Context, userInfo *BitbucketUserInfo, bitbu text += fmt.Sprintf("You have %v pull requests awaiting your review:\n", len(assignedPRs)) for _, assign := range assignedPRs { - text += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "") + text += getToDoDisplayText(bitbucketURL, assign.Title, assign.Links.Html.Href, "") } } @@ -355,7 +369,7 @@ func (p *Plugin) GetToDo(ctx context.Context, userInfo *BitbucketUserInfo, bitbu text += fmt.Sprintf("You have %v open pull requests:\n", len(yourOpenPrs)) for _, assign := range yourOpenPrs { - text += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "") + text += getToDoDisplayText(bitbucketURL, assign.Title, assign.Links.Html.Href, "") } } @@ -368,10 +382,11 @@ func (p *Plugin) getUserRepositories(ctx context.Context, bitbucketClient *bitbu var urlForRepos string org := p.getConfiguration().BitbucketOrg + apiURL := p.getBitbucketAPIBaseURL() if org != "" { - urlForRepos = getYourOrgReposSearchQuery(org) + urlForRepos = getYourOrgReposSearchQuery(apiURL, org) } else { - urlForRepos = getYourAllReposSearchQuery() + urlForRepos = getYourAllReposSearchQuery(apiURL) } userRepos, err := p.fetchRepositoriesWithNextPagesIfAny(ctx, urlForRepos, bitbucketClient) @@ -416,9 +431,11 @@ func (p *Plugin) getIssuesWithTerm(bitbucketClient *bitbucket.APIClient, searchT return nil, errors.Wrap(err, "error occurred while fetching repositories") } + apiURL := p.getBitbucketAPIBaseURL() + var foundIssues []bitbucket.Issue for _, repo := range userRepos { - paginatedIssues, httpResponse, err := bitbucketClient.PagingApi.IssuesPageGet(context.Background(), getSearchIssuesQuery(repo.FullName, searchTerm)) + paginatedIssues, httpResponse, err := bitbucketClient.PagingApi.IssuesPageGet(context.Background(), getSearchIssuesQuery(apiURL, repo.FullName, searchTerm)) if httpResponse != nil { _ = httpResponse.Body.Close() } @@ -519,10 +536,11 @@ func (p *Plugin) fetchPRsWithNextPagesIfAny(ctx context.Context, urlToFetch stri } func (p *Plugin) getAssignedIssues(ctx context.Context, userInfo *BitbucketUserInfo, bitbucketClient *bitbucket.APIClient, userRepos []bitbucket.Repository) ([]bitbucket.Issue, error) { + apiURL := p.getBitbucketAPIBaseURL() var issuesResult []bitbucket.Issue for _, repo := range userRepos { - urlForIssues := getYourAssigneeIssuesSearchQuery(userInfo.BitbucketAccountID, repo.FullName) + urlForIssues := getYourAssigneeIssuesSearchQuery(apiURL, userInfo.BitbucketAccountID, repo.FullName) paginatedIssuesInRepo, err := p.fetchIssuesWithNextPagesIfAny(ctx, urlForIssues, bitbucketClient) if err != nil { @@ -536,9 +554,10 @@ func (p *Plugin) getAssignedIssues(ctx context.Context, userInfo *BitbucketUserI } func (p *Plugin) getAssignedPRs(ctx context.Context, userInfo *BitbucketUserInfo, bitbucketClient *bitbucket.APIClient, userRepos []bitbucket.Repository) ([]bitbucket.Pullrequest, error) { + apiURL := p.getBitbucketAPIBaseURL() var prsResult []bitbucket.Pullrequest for _, repo := range userRepos { - urlForPRs := getYourAssigneePRsSearchQuery(userInfo.BitbucketAccountID, repo.FullName) + urlForPRs := getYourAssigneePRsSearchQuery(apiURL, userInfo.BitbucketAccountID, repo.FullName) paginatedIssuesInRepo, err := p.fetchPRsWithNextPagesIfAny(ctx, urlForPRs, bitbucketClient) if err != nil { @@ -552,10 +571,11 @@ func (p *Plugin) getAssignedPRs(ctx context.Context, userInfo *BitbucketUserInfo } func (p *Plugin) getOpenPRs(ctx context.Context, userInfo *BitbucketUserInfo, bitbucketClient *bitbucket.APIClient, userRepos []bitbucket.Repository) ([]bitbucket.Pullrequest, error) { + apiURL := p.getBitbucketAPIBaseURL() var prsResult []bitbucket.Pullrequest for _, repo := range userRepos { - urlForPRs := getYourOpenPRsSearchQuery(userInfo.BitbucketAccountID, repo.FullName) + urlForPRs := getYourOpenPRsSearchQuery(apiURL, userInfo.BitbucketAccountID, repo.FullName) paginatedIssuesInRepo, err := p.fetchPRsWithNextPagesIfAny(ctx, urlForPRs, bitbucketClient) if err != nil { @@ -587,10 +607,6 @@ func (p *Plugin) sendRefreshEvent(userID string) { ) } -func (p *Plugin) getBaseURL() string { - return "https://bitbucket.org/" -} - // getBitBucketAccountIDToMattermostUsernameMapping maps a BitBucket account ID to the corresponding Mattermost username, if any. func (p *Plugin) getBitBucketAccountIDToMattermostUsernameMapping(bitbucketAccountID string) string { user, _ := p.API.GetUser(p.getBitbucketAccountIDToMattermostUserIDMapping(bitbucketAccountID)) @@ -661,3 +677,27 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.BitbucketUsername, nil } + +// getBitbucketBaseURL returns the Bitbucket Server URL from the configuration +// if there is a Self Hosted URL configured it returns it +// if not it will return the Bitbucket Cloud base URL +func (p *Plugin) getBitbucketBaseURL() string { + config := p.getConfiguration() + if config.BitbucketSelfHostedURL != "" { + return config.BitbucketSelfHostedURL + } + + return BitbucketBaseURL +} + +// getBitbucketAPIBaseURL returns the Bitbucket Server API URL from the configuration +// if there is a API Self Hosted URL configured it returns it +// if not it will return the Bitbucket Cloud API base URL +func (p *Plugin) getBitbucketAPIBaseURL() string { + config := p.getConfiguration() + if config.BitbucketAPISelfHostedURL != "" { + return config.BitbucketAPISelfHostedURL + } + + return BitbucketAPIBaseURL +} diff --git a/server/plugin_test.go b/server/plugin_test.go new file mode 100644 index 0000000..41104aa --- /dev/null +++ b/server/plugin_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func setupTestPlugin() *Plugin { + return &Plugin{} +} + +func TestGetBitbucketBaseURL(t *testing.T) { + p := setupTestPlugin() + + tests := []struct { + name string + selfHostedURL string + expectedBaseURL string + }{ + { + name: "With self hosted URL", + selfHostedURL: "https://selfhosted.bitbucket.com", + expectedBaseURL: "https://selfhosted.bitbucket.com", + }, + { + name: "Without self hosted URL", + selfHostedURL: "", + expectedBaseURL: BitbucketBaseURL, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p.configuration = &Configuration{ + BitbucketSelfHostedURL: tt.selfHostedURL, + } + + assert.Equal(t, p.getBitbucketBaseURL(), tt.expectedBaseURL) + }) + } +} + +func TestGetBitbucketAPIBaseURL(t *testing.T) { + p := setupTestPlugin() + + tests := []struct { + name string + apiSelfHostedURL string + expectedAPIURL string + }{ + { + name: "With self hosted API URL", + apiSelfHostedURL: "https://api.selfhosted.example.com", + expectedAPIURL: "https://api.selfhosted.example.com", + }, + { + name: "Without self hosted API URL", + apiSelfHostedURL: "", + expectedAPIURL: BitbucketAPIBaseURL, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p.configuration = &Configuration{ + BitbucketAPISelfHostedURL: tt.apiSelfHostedURL, + } + + assert.Equal(t, p.getBitbucketAPIBaseURL(), tt.expectedAPIURL) + }) + } +} diff --git a/server/subscriptions.go b/server/subscriptions.go index c470ed6..77813be 100644 --- a/server/subscriptions.go +++ b/server/subscriptions.go @@ -201,7 +201,7 @@ func (p *Plugin) Unsubscribe(channelID, repo string) (string, error) { return requiredErrorMessage, nil } - owner, repo := parseOwnerAndRepo(repo, p.getBaseURL()) + owner, repo := parseOwnerAndRepo(repo, p.getBitbucketBaseURL()) if owner == "" && repo == "" { return requiredErrorMessage, nil } diff --git a/server/utils.go b/server/utils.go index 854081a..8dc5af4 100644 --- a/server/utils.go +++ b/server/utils.go @@ -14,35 +14,31 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func getBaseURL() string { - return "https://api.bitbucket.org/2.0" +func getYourOrgReposSearchQuery(baseURL, organizationName string) string { + return baseURL + "/repositories/" + organizationName + "?role=member" } -func getYourOrgReposSearchQuery(organizationName string) string { - return getBaseURL() + "/repositories/" + organizationName + "?role=member" +func getYourAllReposSearchQuery(baseURL string) string { + return baseURL + "/repositories?role=member" } -func getYourAllReposSearchQuery() string { - return getBaseURL() + "/repositories?role=member" -} - -func getYourAssigneeIssuesSearchQuery(userAccountID, repoFullName string) string { - return getBaseURL() + "/repositories/" + repoFullName + "/issues?q=" + +func getYourAssigneeIssuesSearchQuery(baseURL, userAccountID, repoFullName string) string { + return baseURL + "/repositories/" + repoFullName + "/issues?q=" + utils.URLEncode("assignee.account_id=\""+userAccountID+"\" AND state!=\"closed\"") } -func getYourAssigneePRsSearchQuery(userAccountID, repoFullName string) string { - return getBaseURL() + "/repositories/" + repoFullName + "/pullrequests?q=" + +func getYourAssigneePRsSearchQuery(baseURL, userAccountID, repoFullName string) string { + return baseURL + "/repositories/" + repoFullName + "/pullrequests?q=" + utils.URLEncode("reviewers.account_id=\""+userAccountID+"\" AND state=\"open\"") } -func getYourOpenPRsSearchQuery(userAccountID, repoFullName string) string { - return getBaseURL() + "/repositories/" + repoFullName + "/pullrequests?q=" + +func getYourOpenPRsSearchQuery(baseURL, userAccountID, repoFullName string) string { + return baseURL + "/repositories/" + repoFullName + "/pullrequests?q=" + utils.URLEncode("author.account_id=\""+userAccountID+"\" AND state=\"open\"") } -func getSearchIssuesQuery(repoFullName, searchTerm string) string { - return getBaseURL() + "/repositories/" + repoFullName + "/issues?q=" + +func getSearchIssuesQuery(baseURL, repoFullName, searchTerm string) string { + return baseURL + "/repositories/" + repoFullName + "/issues?q=" + utils.URLEncode("title ~ \""+searchTerm+"\"") + "&sort=-updated_on" } diff --git a/server/utils_test.go b/server/utils_test.go index 6579d4a..09a5f8a 100644 --- a/server/utils_test.go +++ b/server/utils_test.go @@ -35,7 +35,7 @@ func TestParseOwnerAndRepo(t *testing.T) { } func TestGetYourAssigneeSearchQuery(t *testing.T) { - result := getYourAssigneeIssuesSearchQuery("123", "testworkspace/testrepo") + result := getYourAssigneeIssuesSearchQuery("https://api.bitbucket.org/2.0", "123", "testworkspace/testrepo") assert.Equal(t, "https://api.bitbucket.org/2.0/repositories/testworkspace/testrepo/issues?q=assignee.account_id%3D%22123%22%20AND%20state%21%3D%22closed%22", result) } diff --git a/server/webhook.go b/server/webhook.go index 2443091..4365217 100644 --- a/server/webhook.go +++ b/server/webhook.go @@ -136,7 +136,7 @@ func (p *Plugin) executeHandlers(webhookHandlers []*webhook.HandleWebhook, pl we } func (p *Plugin) permissionToRepo(userID string, ownerAndRepo string) bool { - _, owner, repo := parseOwnerAndRepoAndReturnFullAlso(ownerAndRepo, p.getBaseURL()) + _, owner, repo := parseOwnerAndRepoAndReturnFullAlso(ownerAndRepo, p.getBitbucketBaseURL()) if owner == "" { return false diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 57938df..58fd631 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -14,6 +14,7 @@ const manifest = JSON.parse(` "server": { "executables": { "darwin-amd64": "server/dist/plugin-darwin-amd64", + "darwin-arm64": "server/dist/plugin-darwin-arm64", "linux-amd64": "server/dist/plugin-linux-amd64", "windows-amd64": "server/dist/plugin-windows-amd64.exe" }, @@ -65,6 +66,22 @@ const manifest = JSON.parse(` "help_text": "(Optional) Set to lock the plugin to a single Bitbucket organization.", "placeholder": "", "default": null + }, + { + "key": "BitbucketSelfHostedURL", + "display_name": "Bitbucket Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", + "placeholder": "", + "default": null + }, + { + "key": "BitbucketAPISelfHostedURL", + "display_name": "Bitbucket API Self-hosted URL", + "type": "text", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket API server's URL.", + "placeholder": "", + "default": null } ] }