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

Entire code base PR #5

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ linters-settings:
gofmt:
simplify: true
goimports:
local-prefixes: github.com/kosgrz/mattermost-plugin-bitbucket
local-prefixes: github.com/mattermost/mattermost-plugin-bitbucket
golint:
min-confidence: 0
govet:
Expand Down
126 changes: 126 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,129 @@ Visit the [Bitbucket documentation](https://mattermost.gitbook.io/bitbucket-plug
## License

This repository is licensed under the [Apache 2.0 License](https://github.com/mattermost/mattermost-plugin-bitbucket/blob/master/LICENSE).

## About the Bitbucket Plugin

The Mattermost Bitbucket plugin uses a webhook to connect your Bitbucket account to Mattermost to listen for incoming Bitbucket events. Events notifications are via DM in Mattermost. The Events don’t need separate configuration.

After your System Admin has [configured the Bitbucket plugin](#configuration), run `/bitbucket connect` in a Mattermost channel to connect your Mattermost and Bitbucket accounts.

Once connected, you'll have access to the following features:

* __Daily reminders__ - The first time you log in to Mattermost each day, get a post letting you know what issues and pull requests need your attention.
* __Notifications__ - Get a direct message in Mattermost when someone mentions you, requests your review, comments on or modifies one of your pull requests/issues, or assigns you on Bitbucket.
* __Post actions__ - Create a Bitbucket issue from a post or attach a post message to an issue. Hover over a post to reveal the post actions menu and click **More Actions (...)**.
* __Sidebar buttons__ - Stay up-to-date with how many reviews, assignments, and open pull requests you have with buttons in the Mattermost sidebar.
* __Slash commands__ - Interact with the Bitbucket plugin using the `/bitbucket` slash command. Read more about slash commands [here](#slash-commands).

## Before You Start

This guide assumes:

- You have a Bitbucket account.
- You're a Mattermost System Admin.
- You're running Mattermost v5.25 or higher.

## Configuration

Configuration is started in Bitbucket and completed in Mattermost.

### Step 1: Register an OAuth Application in Bitbucket

1. Go to https://bitbucket.org and log in.
2. Visit the **Settings** page for your organization.
3. Click the **OAuth** tab under **Access Management**.
3. Click the **Add consumer** button and set the following values:
- **Name:** `Mattermost Bitbucket Plugin - <your company name>`.
- **Callback URL:** `https://your-mattermost-url.com/plugins/bitbucket/oauth/complete`, replacing `https://your-mattermost-url.com` with your Mattermost URL.
- **URL:** `https://github.com/mattermost/mattermost-plugin-bitbucket`.
4. Set:
- **Account:** `Email` and `Read` permissions.
- **Projects:** `Read` permission.
- **Repositories:** `Read` and `Write` permissions.
- **Pull requests:** `Read` permission.
- **Issues:** `Read` and `write` permissions.
5. Save the **Key** and **Secret** in the resulting screen.
6. Go to **System Console > Plugins > Bitbucket** and enter the **Bitbucket OAuth Client ID** and **Bitbucket OAuth Client Secret** you copied in a previous step.
7. Hit **Save**.

### Step 2: Create a Webhook in Bitbucket

You must create a webhook for each repository you want to receive notifications for or subscribe to.

1. Go to the **Repository settings** page of your Bitbucket organization you want to send notifications from, then select **Webhooks** in the sidebar.
2. Click **Add Webhook**.
3. Set the following values:
- **Title:** `Mattermost Bitbucket Webhook - <repository_name>`, replacing `repository_name` with the name of your repository.
- **URL:** `https://your-mattermost-url.com/plugins/bitbucket/webhook`, replacing `https://your-mattermost-url.com` with your Mattermost URL.
4. Select **Choose from a full list of triggers**.
5. Select:
- **Repository:** `Push`.
- **Pull Request:** `Created`, `Updated`, `Approved`, `Approval removed`, `Merged`, `Declined`, `Comment created`.
- **Issue:** `Created`, `Updated`, `Comment created`.
6. Hit **Save**.

If you have multiple repositories, repeat the process to create a webhook for each repository.

### Step 3: Configure the Plugin in Mattermost

If you have an existing Mattermost user account with the name `bitbucket`, the plugin will post using the `bitbucket` account but without a `BOT` tag.

To prevent this, either:

- Convert the `bitbucket` user to a bot account by running `mattermost user convert bitbucket --bot` in the CLI.

or

- If the user is an existing user account you want to preserve, change its username and restart the Mattermost server. Once restarted, the plugin will create a bot
account with the name `bitbucket`.

#### Generate a Key

Open **System Console > Plugins > Bitbucket** and do the following:

1. Generate a new value for **At Rest Encryption Key**.
2. (Optional) **Bitbucket Organization:** Lock the plugin to a single Bitbucket organization by setting this field to the name of your Bitbucket organization.
3. (Optional) **Enable Private Repositories:** Allow the plugin to receive notifications from private repositories by setting this value to `true`.
4. Hit **Save**.
5. Go to **System Console > Plugins > Management** and click **Enable** to enable the Bitbucket plugin.

You're all set!

## Using the Plugin

Once configuration is complete, run the `/bitbucket connect` slash command from any channel within Mattermost to connect your Mattermost account with Bitbucket.

## Onboarding Your Users

When you’ve tested the plugin and confirmed it’s working, notify your team so they can connect their Bitbucket account to Mattermost and get started. Copy and paste the text below, edit it to suit your requirements, and send it out.

> Hi team,

> We've set up the Mattermost Bitbucket plugin, so you can get notifications from Bitbucket in Mattermost. To get started, run the `/bitbucket connect` slash command from any channel within Mattermost to connect your Mattermost account with Bitbucket. Then, take a look at the [slash commands](#slash-commands) section for details about how to use the plugin.

## Slash Commands

* __Subscribe to a respository__ - Use `/bitbucket subscriptions add` to subscribe a Mattermost channel to receive notifications for new pull requests, issues, branch creation, and more in a Bitbucket repository.

- For instance, to post notifications for issues, issue comments, and pull requests from `mattermost/mattermost-server`, use:
```
/bitbucket subscribe mattermost/mattermost-server issues,pulls,issue_comments
```
* __Get to do items__ - Use `/bitbucket todo` to get an ephemeral message with items to do in Bitbucket, including a list of assigned issues and pull requests awaiting your review.
* __Update settings__ - Use `/bitbucket settings` to update your settings for notifications and daily reminders.
* __And more!__ - Run `/bitbucket help` to see what else the slash command can do.

## Frequently Asked Questions

### How do I share feedback on this plugin?

Feel free to create a GitHub issue or [join the Bitbucket Plugin channel on our community Mattermost instance](https://community-release.mattermost.com/core/channels/plugin-bitbucket) to discuss.

### How does the plugin save user data for each connected Bitbucket user?

Bitbucket user tokens are AES encrypted with an At Rest Encryption Key configured in the plugin's settings page. Once encrypted, the tokens are saved in the `PluginKeyValueStore` table in your Mattermost database.

## Development

This plugin contains both a server and web app portion. Read our documentation about the [Developer Workflow](https://developers.mattermost.com/extend/plugins/developer-workflow/) and [Developer Setup](https://developers.mattermost.com/extend/plugins/developer-setup/) for more information about developing and extending plugins.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/kosgrz/mattermost-plugin-bitbucket
module github.com/mattermost/mattermost-plugin-bitbucket

go 1.12

Expand Down
6 changes: 3 additions & 3 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"id": "bitbucket",
"name": "Bitbucket",
"description": "Bitbucket plugin for Mattermost.",
"homepage_url": "https://github.com/kosgrz/mattermost-plugin-bitbucket",
"support_url": "https://github.com/kosgrz/mattermost-plugin-bitbucket/issues",
"homepage_url": "https://github.com/mattermost/mattermost-plugin-bitbucket",
"support_url": "https://github.com/mattermost/mattermost-plugin-bitbucket/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "5.25.0",
Expand Down Expand Up @@ -57,6 +57,6 @@
"help_text": "(Optional) Allow the plugin to work with private repositories. Enabling private repositories will require existing users to reconnect their accounts to gain access to private repositories. A message will be automatically be sent to affected users next time they load Mattermost alerting them of this."
}
],
"footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/kosgrz/mattermost-plugin-bitbucket)."
"footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/mattermost/mattermost-plugin-bitbucket)."
}
}
5 changes: 3 additions & 2 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ func (p *Plugin) getYourAssignments(w http.ResponseWriter, _ *http.Request, user

bitbucketClient := p.bitbucketConnect(*userInfo.Token)

userRepos, err := p.getUserRepositories(context.Background(), bitbucketClient)
userRepos, err := p.getUserRepositoriesWithIssueTracker(context.Background(), bitbucketClient)
if err != nil {
p.API.LogError("Error occurred while searching for repositories", "err", err)
return
Expand Down Expand Up @@ -873,7 +873,7 @@ func (p *Plugin) getRepositories(w http.ResponseWriter, _ *http.Request, userID

ctx := context.Background()

repos, err := p.getUserRepositories(ctx, bitbucketClient)
repos, err := p.getUserRepositoriesWithIssueTracker(ctx, bitbucketClient)
if err != nil {
p.API.LogError("Failed to fetch repositories", "err", err.Error())
p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
Expand Down Expand Up @@ -975,6 +975,7 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri
issuePostResult, issuePostResponse, err := bitbucketClient.IssueTrackerApi.RepositoriesUsernameRepoSlugIssuesPost(context.Background(), owner, repoName, bbIssue)
if err != nil {
if issuePostResponse != nil {
p.API.LogError("failed to create issue: " + err.Error())
p.writeAPIError(w,
&APIErrorResponse{
ID: "",
Expand Down
2 changes: 1 addition & 1 deletion server/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/plugin/plugintest"

"github.com/kosgrz/mattermost-plugin-bitbucket/server/testutils"
"github.com/mattermost/mattermost-plugin-bitbucket/server/testutils"
)

func TestPlugin_ServeHTTP(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions server/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const manifestStr = `
"id": "bitbucket",
"name": "Bitbucket",
"description": "Bitbucket plugin for Mattermost.",
"homepage_url": "https://github.com/kosgrz/mattermost-plugin-bitbucket",
"support_url": "https://github.com/kosgrz/mattermost-plugin-bitbucket/issues",
"homepage_url": "https://github.com/mattermost/mattermost-plugin-bitbucket",
"support_url": "https://github.com/mattermost/mattermost-plugin-bitbucket/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "5.25.0",
Expand All @@ -33,7 +33,7 @@ const manifestStr = `
},
"settings_schema": {
"header": "To set up the Bitbucket plugin, you need to register a Bitbucket OAuth consumer here https://bitbucket.org/YOURWORKSPACE/workspace/settings/oauth-consumers/new.",
"footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/kosgrz/mattermost-plugin-bitbucket).",
"footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/mattermost/mattermost-plugin-bitbucket).",
"settings": [
{
"key": "BitbucketOAuthClientID",
Expand Down
91 changes: 47 additions & 44 deletions server/plugin.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package main

import (
"fmt"

"context"

"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"

"github.com/gorilla/mux"
"github.com/pkg/errors"

"github.com/kosgrz/mattermost-plugin-bitbucket/server/templaterenderer"
"github.com/kosgrz/mattermost-plugin-bitbucket/server/webhook"
"github.com/mattermost/mattermost-plugin-bitbucket/server/templaterenderer"
"github.com/mattermost/mattermost-plugin-bitbucket/server/webhook"

"net/http"
"net/url"
Expand Down Expand Up @@ -126,31 +126,20 @@ func (p *Plugin) OnActivate() error {
return errors.Wrap(err, "failed to register command")
}

botID, err := p.Helpers.EnsureBot(&model.Bot{
bot := &model.Bot{
Username: "bitbucket",
DisplayName: "BitBucket",
Description: "Created by the BitBucket plugin.",
})
if err != nil {
return errors.Wrap(err, "failed to ensure BitBucket bot")
}

p.BotUserID = botID

bundlePath, err := p.API.GetBundlePath()
if err != nil {
return errors.Wrap(err, "couldn't get bundle path")
options := []plugin.EnsureBotOption{
plugin.ProfileImagePath("assets/profile.png"),
}

profileImage, err := ioutil.ReadFile(filepath.Join(bundlePath, "assets", "profile.png"))
botID, err := p.Helpers.EnsureBot(bot, options...)
if err != nil {
return errors.Wrap(err, "couldn't read profile image")
}

appErr := p.API.SetProfileImage(botID, profileImage)
if appErr != nil {
return errors.Wrap(appErr, "couldn't set profile image")
return errors.Wrap(err, "failed to ensure BitBucket bot")
}
p.BotUserID = botID

return nil
}
Expand Down Expand Up @@ -323,47 +312,42 @@ func (p *Plugin) GetToDo(ctx context.Context, userInfo *BitbucketUserInfo, bitbu
}

text := "##### Your Assignments\n"

if len(yourAssignments) == 0 {
text += "You don't have any assignments.\n"
} else {
text += fmt.Sprintf("You have %v assignments:\n", len(yourAssignments))

textAssignments := "You don't have any assignments.\n"
if len(yourAssignments) != 0 {
textAssignments = fmt.Sprintf("You have %v assignments:\n", len(yourAssignments))
for _, assign := range yourAssignments {
text += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "")
textAssignments += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "")
}
}
text += textAssignments

text += "##### Review Requests\n"

if len(assignedPRs) == 0 {
text += "You don't have any pull requests awaiting your review.\n"
} else {
text += fmt.Sprintf("You have %v pull requests awaiting your review:\n", len(assignedPRs))

textReviews := "You don't have any pull requests awaiting your review.\n"
if len(assignedPRs) != 0 {
textReviews = 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, "")
textReviews += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "")
}
}
text += textReviews

text += "##### Your Open Pull Requests\n"

if len(yourOpenPrs) == 0 {
text += "You don't have any open pull requests.\n"
} else {
text += fmt.Sprintf("You have %v open pull requests:\n", len(yourOpenPrs))

textYourOpenPrs := "You don't have any open pull requests.\n"
if len(yourOpenPrs) != 0 {
textYourOpenPrs = fmt.Sprintf("You have %v open pull requests:\n", len(yourOpenPrs))
for _, assign := range yourOpenPrs {
text += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "")
textYourOpenPrs += getToDoDisplayText(BitbucketBaseURL, assign.Title, assign.Links.Html.Href, "")
}
}
text += textYourOpenPrs

return text, nil
}

func (p *Plugin) getUserRepositories(ctx context.Context, bitbucketClient *bitbucket.APIClient) ([]bitbucket.Repository, error) {
options := make(map[string]interface{})
options["role"] = "member"
options["pagelen"] = 100

var urlForRepos string
org := p.getConfiguration().BitbucketOrg
Expand All @@ -381,6 +365,22 @@ func (p *Plugin) getUserRepositories(ctx context.Context, bitbucketClient *bitbu
return userRepos, nil
}

func (p *Plugin) getUserRepositoriesWithIssueTracker(ctx context.Context, bitbucketClient *bitbucket.APIClient) ([]bitbucket.Repository, error) {
userRepos, err := p.getUserRepositories(ctx, bitbucketClient)
if err != nil {
return nil, err
}

var userReposWithIssueTracker []bitbucket.Repository
for _, repo := range userRepos {
if repo.HasIssues {
userReposWithIssueTracker = append(userReposWithIssueTracker, repo)
}
}

return userReposWithIssueTracker, nil
}

func (p *Plugin) fetchRepositoriesWithNextPagesIfAny(ctx context.Context, urlToFetch string, bitbucketClient *bitbucket.APIClient) ([]bitbucket.Repository, error) {
var result []bitbucket.Repository

Expand Down Expand Up @@ -410,7 +410,7 @@ func (p *Plugin) fetchRepositoriesWithNextPagesIfAny(ctx context.Context, urlToF
}

func (p *Plugin) getIssuesWithTerm(bitbucketClient *bitbucket.APIClient, searchTerm string) ([]bitbucket.Issue, error) {
userRepos, err := p.getUserRepositories(context.Background(), bitbucketClient)
userRepos, err := p.getUserRepositoriesWithIssueTracker(context.Background(), bitbucketClient)
if err != nil {
return nil, errors.Wrap(err, "error occurred while fetching repositories")
}
Expand Down Expand Up @@ -456,6 +456,9 @@ func (p *Plugin) fetchIssuesWithNextPagesIfAny(ctx context.Context, urlToFetch s
if err != nil {
if httpResponse != nil {
_ = httpResponse.Body.Close()
if httpResponse.StatusCode == 404 {
return nil, nil
}
}
return nil, errors.Wrap(err, "error occurred while fetching issues")
}
Expand Down
4 changes: 2 additions & 2 deletions server/subscriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"github.com/pkg/errors"
"github.com/wbrefvem/go-bitbucket"

"github.com/kosgrz/mattermost-plugin-bitbucket/server/subscription"
"github.com/kosgrz/mattermost-plugin-bitbucket/server/webhookpayload"
"github.com/mattermost/mattermost-plugin-bitbucket/server/subscription"
"github.com/mattermost/mattermost-plugin-bitbucket/server/webhookpayload"
)

const (
Expand Down
Loading