From 1802aaa2242208fd994eaabb8a4993ca423ea01e Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Fri, 18 Aug 2023 09:59:37 -0300 Subject: [PATCH 01/15] feat: creates new config to set a BitbucketSelfHostedUrl --- plugin.json | 10 +++++++++- server/configuration.go | 1 + server/manifest.go | 8 ++++++++ webapp/src/manifest.js | 8 ++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 40f7a84..bb11e8c 100644 --- a/plugin.json +++ b/plugin.json @@ -62,7 +62,15 @@ "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": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "placeholder": "", + "default": null } ] } -} +} \ No newline at end of file diff --git a/server/configuration.go b/server/configuration.go index 81e27fe..272a559 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -21,6 +21,7 @@ type Configuration struct { BitbucketOrg string BitbucketOAuthClientID string BitbucketOAuthClientSecret string + BitbucketSelfHostedUrl string WebhookSecret string EncryptionKey string } diff --git a/server/manifest.go b/server/manifest.go index e116a22..82d58a0 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -76,6 +76,14 @@ 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": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "placeholder": "", + "default": null } ] } diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 57938df..2ab9c14 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -65,6 +65,14 @@ 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": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "placeholder": "", + "default": null } ] } From 1196da4837127328c1d663b632bb0677711afd75 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Sat, 19 Aug 2023 23:37:54 -0300 Subject: [PATCH 02/15] feat: creates new plugin method to get the bitbucket baseURL and replaces the locations where the url is used --- server/command.go | 5 +++-- server/plugin.go | 30 +++++++++++++++++++++--------- server/subscriptions.go | 2 +- server/webhook.go | 2 +- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/server/command.go b/server/command.go index 73419b9..ebe328b 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 != "" { diff --git a/server/plugin.go b/server/plugin.go index 57c84ce..a4ecd26 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -160,8 +160,10 @@ func (p *Plugin) OnActivate() error { func (p *Plugin) getOAuthConfig() *oauth2.Config { config := p.getConfiguration() - authURL, _ := url.Parse(BitbucketBaseURL) - tokenURL, _ := url.Parse(BitbucketBaseURL) + bitbucketURL := p.getBitbucketBaseURL() + + authURL, _ := url.Parse(bitbucketURL) + tokenURL, _ := url.Parse(bitbucketURL) authURL.Path = path.Join(authURL.Path, "site", "oauth2", "authorize") tokenURL.Path = path.Join(tokenURL.Path, "site", "oauth2", "access_token") @@ -303,6 +305,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 +335,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 +347,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 +359,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, "") } } @@ -587,10 +591,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 +661,15 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.BitbucketUsername, nil } + +// getBitbucketBaseURl returns the base URL from the configuration +// if there is a Self Hosted URL configured it returns it +// if not it will return the bitbucket base URL +func (p *Plugin) getBitbucketBaseURL() string { + config := p.getConfiguration() + + if config.BitbucketSelfHostedUrl != "" { + return config.BitbucketSelfHostedUrl + } + return BitbucketBaseURL +} 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/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 From a5108118f7f2964485ded363b0976d7b0af0610f Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 21 Aug 2023 20:51:53 -0300 Subject: [PATCH 03/15] chore: renaming and refactoring regarding PR review --- plugin.json | 6 +++--- server/configuration.go | 2 +- server/manifest.go | 4 ++-- server/plugin.go | 8 ++++---- webapp/src/manifest.js | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugin.json b/plugin.json index bb11e8c..ea40a8c 100644 --- a/plugin.json +++ b/plugin.json @@ -64,13 +64,13 @@ "default": null }, { - "key": "BitbucketSelfHostedUrl", + "key": "BitbucketSelfHostedURL", "display_name": "Bitbucket Self-hosted Url", "type": "text", - "help_text": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", "placeholder": "", "default": null } ] } -} \ No newline at end of file +} diff --git a/server/configuration.go b/server/configuration.go index 272a559..b4c0daf 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -21,7 +21,7 @@ type Configuration struct { BitbucketOrg string BitbucketOAuthClientID string BitbucketOAuthClientSecret string - BitbucketSelfHostedUrl string + BitbucketSelfHostedURL string WebhookSecret string EncryptionKey string } diff --git a/server/manifest.go b/server/manifest.go index 82d58a0..649b134 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -78,10 +78,10 @@ const manifestStr = ` "default": null }, { - "key": "BitbucketSelfHostedUrl", + "key": "BitbucketSelfHostedURL", "display_name": "Bitbucket Self-hosted Url", "type": "text", - "help_text": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", "placeholder": "", "default": null } diff --git a/server/plugin.go b/server/plugin.go index a4ecd26..9e51832 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -662,14 +662,14 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.BitbucketUsername, nil } -// getBitbucketBaseURl returns the base URL from the configuration +// 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 base URL +// if not it will return the Bitbucket Cloud base URL func (p *Plugin) getBitbucketBaseURL() string { config := p.getConfiguration() - if config.BitbucketSelfHostedUrl != "" { - return config.BitbucketSelfHostedUrl + if config.BitbucketSelfHostedURL != "" { + return config.BitbucketSelfHostedURL } return BitbucketBaseURL } diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 2ab9c14..f5c73e1 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -67,10 +67,10 @@ const manifest = JSON.parse(` "default": null }, { - "key": "BitbucketSelfHostedUrl", + "key": "BitbucketSelfHostedURL", "display_name": "Bitbucket Self-hosted Url", "type": "text", - "help_text": "(Optional) Set to change the default bitbucket URL to a self-hosted one.", + "help_text": "If using Bitbucket Server instead of Bitbucket Cloud, please set this value to your BitBucket server's URL.", "placeholder": "", "default": null } From c5d200f3a51c56571e05e63735042e5539556724 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 21 Aug 2023 23:10:54 -0300 Subject: [PATCH 04/15] feat: adding docker-compose setting up bitbucket server --- docker-compose.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e5df5b6 --- /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: \ No newline at end of file From b0e99a629b5b38a70a416467af9b1de6d4635355 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 19:45:41 -0300 Subject: [PATCH 05/15] feat: adding support for Apple M1 in the build --- Makefile | 13 ++++++++++--- plugin.json | 3 ++- server/manifest.go | 1 + webapp/src/manifest.js | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) 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/plugin.json b/plugin.json index ea40a8c..665a221 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": "" @@ -73,4 +74,4 @@ } ] } -} +} \ No newline at end of file diff --git a/server/manifest.go b/server/manifest.go index 649b134..01cd080 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" }, diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index f5c73e1..a23fb97 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" }, From f959dfbd0389137aa340fc64c6e1a01d7a232068 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 20:23:26 -0300 Subject: [PATCH 06/15] feat: adds the API Self-Hosted URL --- plugin.json | 10 +++++++++- server/manifest.go | 10 +++++++++- webapp/src/manifest.js | 10 +++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/plugin.json b/plugin.json index 665a221..8bf05b2 100644 --- a/plugin.json +++ b/plugin.json @@ -66,11 +66,19 @@ }, { "key": "BitbucketSelfHostedURL", - "display_name": "Bitbucket Self-hosted Url", + "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/manifest.go b/server/manifest.go index 01cd080..97b8bdb 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -80,11 +80,19 @@ const manifestStr = ` }, { "key": "BitbucketSelfHostedURL", - "display_name": "Bitbucket Self-hosted Url", + "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/webapp/src/manifest.js b/webapp/src/manifest.js index a23fb97..58fd631 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -69,11 +69,19 @@ const manifest = JSON.parse(` }, { "key": "BitbucketSelfHostedURL", - "display_name": "Bitbucket Self-hosted Url", + "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 } ] } From ebcb372a5479ace52270d8b332c7ee447bd5d20d Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 20:42:17 -0300 Subject: [PATCH 07/15] feat: adds the code to check for the Bitbucket API Self Hosted URL --- server/configuration.go | 1 + server/plugin.go | 33 ++++++++++++++++++++++++++------- server/utils.go | 28 ++++++++++++---------------- server/utils_test.go | 2 +- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/server/configuration.go b/server/configuration.go index b4c0daf..e305f77 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -22,6 +22,7 @@ type Configuration struct { BitbucketOAuthClientID string BitbucketOAuthClientSecret string BitbucketSelfHostedURL string + BitbucketAPISelfHostedURL string WebhookSecret string EncryptionKey string } diff --git a/server/plugin.go b/server/plugin.go index 9e51832..f0e35de 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" @@ -372,10 +373,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) @@ -420,9 +422,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() } @@ -523,10 +527,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 { @@ -540,9 +545,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 { @@ -556,10 +562,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 { @@ -673,3 +680,15 @@ func (p *Plugin) getBitbucketBaseURL() string { } 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/utils.go b/server/utils.go index 854081a..886c665 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 string, 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 string, userAccountID string, 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 string, userAccountID string, 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 string, userAccountID string, 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 string, repoFullName string, 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) } From 09f23c0a5e655c14a9a8aa1572d93e11e822413f Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 21:07:35 -0300 Subject: [PATCH 08/15] feat: refactor of the plugin to be less repetitive on getting the configuration --- server/plugin.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/server/plugin.go b/server/plugin.go index f0e35de..6cb7dd4 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -669,16 +669,19 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.BitbucketUsername, nil } +func (p *Plugin) getURL(selfHostedURL, defaultURL string) string { + if selfHostedURL != "" { + return selfHostedURL + } + return defaultURL +} + // 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 + return p.getURL(config.BitbucketSelfHostedURL, BitbucketBaseURL) } // getBitbucketAPIBaseURL returns the Bitbucket Server API URL from the configuration @@ -686,9 +689,5 @@ func (p *Plugin) getBitbucketBaseURL() string { // 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 + return p.getURL(config.BitbucketAPISelfHostedURL, BitbucketAPIBaseURL) } From 3cc5ada7493ab6b2c3e5d44c1b7b876cf055dd97 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 21:12:42 -0300 Subject: [PATCH 09/15] feat: refactor to get back to the shortcut string parameters --- server/utils.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/utils.go b/server/utils.go index 886c665..8dc5af4 100644 --- a/server/utils.go +++ b/server/utils.go @@ -14,7 +14,7 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func getYourOrgReposSearchQuery(baseURL string, organizationName string) string { +func getYourOrgReposSearchQuery(baseURL, organizationName string) string { return baseURL + "/repositories/" + organizationName + "?role=member" } @@ -22,22 +22,22 @@ func getYourAllReposSearchQuery(baseURL string) string { return baseURL + "/repositories?role=member" } -func getYourAssigneeIssuesSearchQuery(baseURL string, userAccountID string, repoFullName string) string { +func getYourAssigneeIssuesSearchQuery(baseURL, userAccountID, repoFullName string) string { return baseURL + "/repositories/" + repoFullName + "/issues?q=" + utils.URLEncode("assignee.account_id=\""+userAccountID+"\" AND state!=\"closed\"") } -func getYourAssigneePRsSearchQuery(baseURL string, userAccountID string, repoFullName string) string { +func getYourAssigneePRsSearchQuery(baseURL, userAccountID, repoFullName string) string { return baseURL + "/repositories/" + repoFullName + "/pullrequests?q=" + utils.URLEncode("reviewers.account_id=\""+userAccountID+"\" AND state=\"open\"") } -func getYourOpenPRsSearchQuery(baseURL string, userAccountID string, repoFullName string) string { +func getYourOpenPRsSearchQuery(baseURL, userAccountID, repoFullName string) string { return baseURL + "/repositories/" + repoFullName + "/pullrequests?q=" + utils.URLEncode("author.account_id=\""+userAccountID+"\" AND state=\"open\"") } -func getSearchIssuesQuery(baseURL string, repoFullName string, searchTerm string) string { +func getSearchIssuesQuery(baseURL, repoFullName, searchTerm string) string { return baseURL + "/repositories/" + repoFullName + "/issues?q=" + utils.URLEncode("title ~ \""+searchTerm+"\"") + "&sort=-updated_on" } From 97962973699b60e6aeb141edd6e6fc041d89b7f0 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Mon, 28 Aug 2023 23:36:42 -0300 Subject: [PATCH 10/15] test: adding tests for the new methods --- server/plugin_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 server/plugin_test.go 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) + }) + } +} From 1ec6a253b582e2a693aa571ab19c28063282831e Mon Sep 17 00:00:00 2001 From: Michael Kochell Date: Tue, 29 Aug 2023 04:37:36 +0000 Subject: [PATCH 11/15] add gitpod files --- .gitpod/command.sh | 1 + .gitpod/init.sh | 1 + 2 files changed, 2 insertions(+) create mode 100755 .gitpod/command.sh create mode 100755 .gitpod/init.sh 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 From 69672ed47f496f6293480fbb9d21fea5eb771ce7 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Tue, 29 Aug 2023 11:36:52 -0300 Subject: [PATCH 12/15] feat: adding a verification for the bitbucket URL if it's valid --- server/configuration.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server/configuration.go b/server/configuration.go index e305f77..b90a027 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -1,6 +1,7 @@ package main import ( + "net/url" "reflect" "github.com/pkg/errors" @@ -51,9 +52,32 @@ 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("must be a valid URL") + } + + if c.BitbucketAPISelfHostedURL != "" && !c.IsValidURL(c.BitbucketAPISelfHostedURL) { + return errors.New("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 +} + // 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. From 150290a64490823d57dbc363303dc34ed1dd2cba Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Thu, 14 Sep 2023 00:57:53 -0300 Subject: [PATCH 13/15] feat: adding conditionals to initialize oauth based on bitbucket server configs --- server/api.go | 2 +- server/configuration.go | 5 +++++ server/plugin.go | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/server/api.go b/server/api.go index d85cd1f..70fd115 100644 --- a/server/api.go +++ b/server/api.go @@ -247,7 +247,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/configuration.go b/server/configuration.go index b90a027..8ecd1c2 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -78,6 +78,11 @@ func (c *Configuration) IsValidURL(toTest string) bool { 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/plugin.go b/server/plugin.go index 6cb7dd4..d071aab 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -165,13 +165,22 @@ func (p *Plugin) getOAuthConfig() *oauth2.Config { authURL, _ := url.Parse(bitbucketURL) tokenURL, _ := url.Parse(bitbucketURL) - authURL.Path = path.Join(authURL.Path, "site", "oauth2", "authorize") - tokenURL.Path = path.Join(tokenURL.Path, "site", "oauth2", "access_token") + 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(), From b2b359f81aeb539a290f73d9ea3de80ee8667a16 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Wed, 4 Oct 2023 23:49:21 -0300 Subject: [PATCH 14/15] feat: PR review --- docker-compose.yml | 2 +- plugin.json | 2 +- server/api.go | 3 +-- server/api_test.go | 4 ++-- server/configuration.go | 4 ++-- server/plugin.go | 19 ++++++++++--------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e5df5b6..ca75c7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,4 +11,4 @@ services: - bitbucket-data:/var/atlassian/application-data/bitbucket volumes: - bitbucket-data: \ No newline at end of file + bitbucket-data: diff --git a/plugin.json b/plugin.json index 8bf05b2..990ce6a 100644 --- a/plugin.json +++ b/plugin.json @@ -82,4 +82,4 @@ } ] } -} \ No newline at end of file +} diff --git a/server/api.go b/server/api.go index 70fd115..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 { 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/configuration.go b/server/configuration.go index 8ecd1c2..32e7168 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -54,11 +54,11 @@ func (c *Configuration) IsValid() error { } if c.BitbucketSelfHostedURL != "" && !c.IsValidURL(c.BitbucketSelfHostedURL) { - return errors.New("must be a valid URL") + return errors.New("BitbucketSelfHostedURL must be a valid URL") } if c.BitbucketAPISelfHostedURL != "" && !c.IsValidURL(c.BitbucketAPISelfHostedURL) { - return errors.New("must be a valid URL") + return errors.New("BitbucketAPISelfHostedURL must be a valid URL") } return nil } diff --git a/server/plugin.go b/server/plugin.go index d071aab..b41be79 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -678,19 +678,16 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.BitbucketUsername, nil } -func (p *Plugin) getURL(selfHostedURL, defaultURL string) string { - if selfHostedURL != "" { - return selfHostedURL - } - return defaultURL -} - // 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() - return p.getURL(config.BitbucketSelfHostedURL, BitbucketBaseURL) + if config.BitbucketSelfHostedURL != "" { + return config.BitbucketSelfHostedURL + } + + return BitbucketBaseURL } // getBitbucketAPIBaseURL returns the Bitbucket Server API URL from the configuration @@ -698,5 +695,9 @@ func (p *Plugin) getBitbucketBaseURL() string { // if not it will return the Bitbucket Cloud API base URL func (p *Plugin) getBitbucketAPIBaseURL() string { config := p.getConfiguration() - return p.getURL(config.BitbucketAPISelfHostedURL, BitbucketAPIBaseURL) + if config.BitbucketAPISelfHostedURL != "" { + return config.BitbucketAPISelfHostedURL + } + + return BitbucketAPIBaseURL } From e29edea57e19bc983fdc5d896dcd5bb754dd7276 Mon Sep 17 00:00:00 2001 From: panoramix360 Date: Thu, 5 Oct 2023 00:16:45 -0300 Subject: [PATCH 15/15] fix: fix lint --- server/command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/command.go b/server/command.go index ebe328b..fe1fb68 100644 --- a/server/command.go +++ b/server/command.go @@ -320,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" +