Repository: mattermost/mattermost-plugin-github
Pull request #346 (2020-08-07 10:38:02)

Add dropdown setting for Code Preview
#### Summary
![Screenshot from 2020-08-07 12-32-33](https://user-images.githubusercontent.com/16541325/89637184-16655100-d8aa-11ea-9e14-14f577f4e18e.png)

Add a dropdown to the system console to set the level for which code preview is enabled. By default, it's enabled for public repos.

Installations that had Code Preview enabled before will now only have it enabled for public repo. That seams like a fine solution as the security implications were not clear for admin in the past.


#### Ticket Link
Fixes https://github.com/mattermost/mattermost-plugin-github/issues/323


codecov-commenter: # [Codecov](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=h1) Report
> Merging [#346](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=desc) into [master](https://codecov.io/gh/mattermost/mattermost-plugin-github/commit/cbf81f114c8ca64ae23477f8ffe73e3ce4c7efc3&el=desc) will **increase** coverage by `0.10%`.
> The diff coverage is `40.00%`.

[![Impacted file tree graph](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346/graphs/tree.svg?width=650&height=150&src=pr&token=0uHZTjTrgl)](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #346      +/-   ##
==========================================
+ Coverage   20.03%   20.14%   +0.10%     
==========================================
  Files          10       10              
  Lines        2565     2576      +11     
==========================================
+ Hits          514      519       +5     
- Misses       2016     2021       +5     
- Partials       35       36       +1     
```


| [Impacted Files](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [server/plugin/configuration.go](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346/diff?src=pr&el=tree#diff-c2VydmVyL3BsdWdpbi9jb25maWd1cmF0aW9uLmdv) | `38.70% <ø> (+6.45%)` | :arrow_up: |
| [server/plugin/plugin.go](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346/diff?src=pr&el=tree#diff-c2VydmVyL3BsdWdpbi9wbHVnaW4uZ28=) | `4.11% <0.00%> (ø)` | |
| [server/plugin/permalinks.go](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346/diff?src=pr&el=tree#diff-c2VydmVyL3BsdWdpbi9wZXJtYWxpbmtzLmdv) | `70.37% <42.85%> (-6.78%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=footer). Last update [cbf81f1...7dab103](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/346?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jupenur: What happens on upgrade when the data type of a config has changed? Does it just revert to the default? What about users that had code previews disabled before?
hanzei: @jupenur [`LoadPluginConfiguration`](https://developers.mattermost.com/extend/plugins/server/reference/#API.LoadPluginConfiguration) will try to unmarshal the stored configuration into the new struct. Given that the data previously stored will either be `true` or `false` that unmarshaling will fail and leave `EnableCodePreview` as `""`. All existing installations settings will be ignore and the default value used.

This change must be announced in the release notes.

Patch:
diff --git a/README.md b/README.md
index 574f7d3..beee906 100644
--- a/README.md
+++ b/README.md
@@ -18,14 +18,14 @@ After your System Admin has [configured the GitHub plugin](#Configuration), run
 
 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 GitHub
-* __Post actions__ - create a GitHub issue from a post or attach a post message to an issue
+* __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 GitHub
+* __Post actions__ - Create a GitHub issue from a post or attach a post message to an issue
     * hover over a post to reveal the post actions menu and click the __More Actions (...)__ option
-* __Sidebar buttons__ - stay up-to-date with how many reviews, unread messages, assignments and open pull requests you have with buttons in the Mattermost sidebar
-* __Slash commands__ - interact with the GitHub plugin using the `/github` slash command
-    * __Subscribe to a respository__ - Use `/github subscribe` to subscribe a Mattermost channel to receive notifications for new pull requests, issues, branch creation and more in a GitHub repository.
-        - For instance, to post notifications for issues, issue comments and pull requests matching the label `Help Wanted` from `mattermost/mattermost-server`, use
+* __Sidebar buttons__ - Stay up-to-date with how many reviews, unread messages, assignments and open pull requests you have with buttons in the Mattermost sidebar
+* __Slash commands__ - Interact with the GitHub plugin using the `/github` slash command
+    * __Subscribe to a respository__ - Use `/github subscribe` to subscribe a Mattermost channel to receive notifications for new pull requests, issues, branch creation and more in a GitHub repository
+        - For instance, to post notifications for issues, issue comments, and pull requests matching the label `Help Wanted` from `mattermost/mattermost-server`, use
 
           ```
           /github subscribe mattermost/mattermost-server issues,pulls,issue_comments,label:"Help Wanted"
@@ -43,7 +43,7 @@ Once connected, you'll have access to the following features:
 In Mattermost 5.12 and later, the GitHub plugin is pre-packaged and no steps are required for installation. You can go directly to [Configuration](#configuration).
 
 In Mattermost 5.11 and earlier, follow these steps:
-1. Go to https://github.com/mattermost/mattermost-plugin-github/releases to download the latest release file in zip or tar.gz format.
+1. Go to https://github.com/mattermost/mattermost-plugin-github/releases to download the latest release file in `zip` or `tar.gz` format.
 2. Upload the file through **System Console > Plugins > Management**, or manually upload it to the Mattermost server under plugin directory. See [documentation](https://docs.mattermost.com/administration/plugins.html#set-up-guide) for more details.
 
 ## Configuration
@@ -54,14 +54,15 @@ __If you are using GitHub Enterprise, replace all GitHub links below with your G
 
 1. Go to https://github.com/settings/applications/new to register an OAuth app.
 2. Set the following values:
-   - **Application Name**: `Mattermost GitHub Plugin - <your company name>`
-   - **Homepage URL**: `https://github.com/mattermost/mattermost-plugin-github`
-   - **Authorization callback URL**: `https://your-mattermost-url.com/plugins/github/oauth/complete`, replacing `https://your-mattermost-url.com` with your Mattermost URL.
-3. Submit. Copy the **Client ID** and **Client Secret** in the resulting screen.
-4. Go to **System Console > Plugins > GitHub** and enter **GitHub OAuth Client ID** and **GitHub OAuth Client Secret** you copied in a previous step.
+   - **Application Name:** `Mattermost GitHub Plugin - <your company name>`
+   - **Homepage URL:** `https://github.com/mattermost/mattermost-plugin-github`
+   - **Authorization callback URL:** `https://your-mattermost-url.com/plugins/github/oauth/complete`, replacing `https://your-mattermost-url.com` with your Mattermost URL.
+3. Submit. 
+4. Copy the **Client ID** and **Client Secret** in the resulting screen.
+5. Go to **System Console > Plugins > GitHub** and enter **GitHub OAuth Client ID** and **GitHub OAuth Client Secret** you copied in a previous step.
 
-   **Note**: If you are running Mattermost v5.11 or earlier, you must first go to the [releases page of this GitHub repository](https://github.com/mattermost/mattermost-plugin-github/releases), download the latest release, and upload it to your Mattermost instance [following this documentation](https://docs.mattermost.com/administration/plugins.html#plugin-uploads).
-5. Hit **Save**.
+   **Note:** If you are running Mattermost v5.11 or earlier, you must first go to the [releases page of this GitHub repository](https://github.com/mattermost/mattermost-plugin-github/releases), download the latest release, and upload it to your Mattermost instance [following this documentation](https://docs.mattermost.com/administration/plugins.html#plugin-uploads).
+6. Hit **Save**.
 
 ### Step 2: Create a webhook in GitHub
 
@@ -69,30 +70,30 @@ __If you are using GitHub Enterprise, replace all GitHub links below with your G
 2. Hit **Save** to save the secret.
 3. Go to the **Settings** page of your GitHub organization you want to send notifications from, then select **Webhooks** in the sidebar.
 
-   **Note**: You must create a webhook for each organization you want to receive notifications for or subscribe to.
+   **Note:** You must create a webhook for each organization you want to receive notifications for or subscribe to.
 
 4. Click **Add webhook**
 5. Set the following values:
-   - **Payload URL**: `https://your-mattermost-url.com/plugins/github/webhook`, replacing `https://your-mattermost-url.com` with your Mattermost URL
-   - **Content Type**: `application/json`
-   - **Secret**: the webhook secret you copied previously
+   - **Payload URL:** `https://your-mattermost-url.com/plugins/github/webhook`, replacing `https://your-mattermost-url.com` with your Mattermost URL
+   - **Content Type:** `application/json`
+   - **Secret:** the webhook secret you copied previously
 6. Select **Let me select individual events** for "Which events would you like to trigger this webhook?", then select the following events:  `Branch or Tag creation`, `Branch or Tag deletion`, `Issue comments`, `Issues`, `Pull requests`, `Pull request review`, `Pull request review comments`, `Pushes`
 7. Hit **Add Webhook** to save it.
 
 ### Step 3: Configure plugin in Mattermost
 
 1. If you have an existing user account with the name `github`, either:
-  - convert the `github` user to a bot account by running ``mattermost user convert github --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, after which the plugin will create a bot account with the name ``github``.
+  - Convert the `github` user to a bot account by running ``mattermost user convert github --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, after which the plugin will create a bot account with the name ``github``.
 
   If you don't take either action, the plugin will post using the `github` account but without a `BOT` tag.
 
-  **Note**: For versions 0.9 and earlier of the GitHub plugin, instead of using bot accounts, set the username the plugin is attached to in **System Console > Plugins > GitHub**.
+  **Note:** For versions 0.9 and earlier of the GitHub plugin, instead of using bot accounts, set the username the plugin is attached to in **System Console > Plugins > GitHub**.
   
 2. Go to **System Console > Plugins > GitHub** and do the following:
   - Generate a new value for **At Rest Encryption Key**.
-  - (Optional) **GitHub Organization**: Lock the plugin to a single GitHub organization by setting this field to the name of your GitHub organization.
-  - (Optional) **Enable Private Repositories**: Allow the plugin to receive notifications from private repositories by setting this value to true.
+  - (Optional) **GitHub Organization:** Lock the plugin to a single GitHub organization by setting this field to the name of your GitHub organization.
+  - (Optional) **Enable Private Repositories:** Allow the plugin to receive notifications from private repositories by setting this value to true.
     When enabled, existing users must reconnect their accounts to gain access to private repositories. Affected users will be notified by the plugin once private repositories are enabled.
 
   - (Enterprise only) **Enterprise Base URL** and **Enterprise Upload URL**: Set these values to your GitHub Enterprise URLs, e.g. `https://github.example.com`. The Base and Upload URLs are often the same.
@@ -101,7 +102,7 @@ __If you are using GitHub Enterprise, replace all GitHub links below with your G
 
 You're all set! To test it, run the `/github connect` slash command to connect your Mattermost account with GitHub.
 
-### Configuration Notes in HA
+### Configuration Notes in High Availability Mode
 
 If you are running Mattermost v5.11 or earlier in [High Availability mode](https://docs.mattermost.com/deployment/cluster.html), please review the following:
 
@@ -134,11 +135,11 @@ If you are running Mattermost v5.11 or earlier in [High Availability mode](https
 
 This plugin contains both a server and web app portion.
 
-Use `make dist` to build distributions of the plugin that you can upload to a Mattermost server.
+- Use `make dist` to build distributions of the plugin that you can upload to a Mattermost server.
+- Use `make check-style` to check the style.
+- Use `make deploy` to deploy the plugin to your local server. 
 
-Use `make check-style` to check the style.
-
-Use `make deploy` to deploy the plugin to your local server. Before running `make deploy` you need to set a few environment variables:
+Before running `make deploy` you need to set a few environment variables:
 
 ```
 export MM_SERVICESETTINGS_SITEURL=http://localhost:8065
@@ -164,7 +165,7 @@ Suppose you want to send notifications to a Mattermost channel when `Severity/Cr
 
 ### How do I share feedback on this plugin?
 
-Feel free to create a GitHub issue or [join the GitHub Plugin channel on our community Mattermost instance](https://pre-release.mattermost.com/core/channels/github-plugin) to discuss.
+Feel free to create a GitHub issue or [join the GitHub Plugin channel on our community Mattermost instance](https://community-release.mattermost.com/core/channels/github-plugin) to discuss.
 
 ### How does the plugin save user data for each connected GitHub user?
 
diff --git a/plugin.json b/plugin.json
index 5ce5544..540296b 100644
--- a/plugin.json
+++ b/plugin.json
@@ -71,8 +71,23 @@
             {
                 "key": "EnableCodePreview",
                 "display_name": "Enable Code Previews",
-                "type": "bool",
-                "help_text": "(Optional) Allow the plugin to expand permalinks to github files with an actual preview of the linked file."
+                "type": "dropdown",
+                "help_text": "Allow the plugin to expand permalinks to GitHub files with an actual preview of the linked file.",
+                "default": "public",
+                "options": [
+                    {
+                        "display_name": "Enable for public repositories",
+                        "value": "public"
+                    },
+                    {
+                        "display_name": "Enable for public and private repositories. This might leak confidential code into public channels.",
+                        "value": "privateAndPublic"
+                    },
+                    {
+                        "display_name": "Disable",
+                        "value": "disable"
+                    }
+                ]
             }
         ],
         "footer": "* To report an issue, make a suggestion or a contribution, [check the repository](https://github.com/mattermost/mattermost-plugin-github)."
diff --git a/server/plugin/api.go b/server/plugin/api.go
index fe58b14..3a090b6 100644
--- a/server/plugin/api.go
+++ b/server/plugin/api.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"runtime/debug"
 	"strconv"
 	"strings"
 	"sync"
@@ -98,6 +99,7 @@ func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse)
 
 func (p *Plugin) initializeAPI() {
 	p.router = mux.NewRouter()
+	p.router.Use(p.withRecovery)
 
 	oauthRouter := p.router.PathPrefix("/oauth").Subrouter()
 	apiRouter := p.router.PathPrefix("/api/v1").Subrouter()
@@ -128,6 +130,21 @@ func (p *Plugin) initializeAPI() {
 	apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet)
 }
 
+func (p *Plugin) withRecovery(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer func() {
+			if x := recover(); x != nil {
+				p.API.LogError("Recovered from a panic",
+					"url", r.URL.String(),
+					"error", x,
+					"stack", string(debug.Stack()))
+			}
+		}()
+
+		next.ServeHTTP(w, r)
+	})
+}
+
 func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, responseType ResponseType) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		userID := r.Header.Get("Mattermost-User-ID")
@@ -520,9 +537,17 @@ func (p *Plugin) getUnreads(w http.ResponseWriter, r *http.Request, userID strin
 			continue
 		}
 
+		issueURL := n.GetSubject().GetURL()
+		issueNumIndex := strings.LastIndex(issueURL, "/")
+		issueNum := issueURL[issueNumIndex+1:]
+		subjectURL := n.GetSubject().GetURL()
+		if n.GetSubject().GetLatestCommentURL() != "" {
+			subjectURL = n.GetSubject().GetLatestCommentURL()
+		}
+
 		filteredNotifications = append(filteredNotifications, &filteredNotification{
 			Notification: *n,
-			HTMLUrl:      fixGithubNotificationSubjectURL(n.GetSubject().GetURL()),
+			HTMLUrl:      fixGithubNotificationSubjectURL(subjectURL, issueNum),
 		})
 	}
 
@@ -938,7 +963,9 @@ func (p *Plugin) getIssueByNumber(w http.ResponseWriter, r *http.Request, userID
 		p.writeAPIError(w, &APIErrorResponse{Message: "Could not get issue", StatusCode: http.StatusInternalServerError})
 		return
 	}
-	*result.Body = mdCommentRegex.ReplaceAllString(result.GetBody(), "")
+	if result.Body != nil {
+		*result.Body = mdCommentRegex.ReplaceAllString(result.GetBody(), "")
+	}
 	p.writeJSON(w, result)
 }
 
@@ -975,7 +1002,9 @@ func (p *Plugin) getPrByNumber(w http.ResponseWriter, r *http.Request, userID st
 		p.writeAPIError(w, &APIErrorResponse{Message: "Could not get pull request", StatusCode: http.StatusInternalServerError})
 		return
 	}
-	*result.Body = mdCommentRegex.ReplaceAllString(result.GetBody(), "")
+	if result.Body != nil {
+		*result.Body = mdCommentRegex.ReplaceAllString(result.GetBody(), "")
+	}
 	p.writeJSON(w, result)
 }
 
diff --git a/server/plugin/command.go b/server/plugin/command.go
index 72fca8e..5d1852f 100644
--- a/server/plugin/command.go
+++ b/server/plugin/command.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"strings"
+	"unicode"
 
 	"github.com/google/go-github/v31/github"
 	"github.com/mattermost/mattermost-server/v5/mlog"
@@ -155,7 +156,16 @@ func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, par
 		return err.Error()
 	}
 
-	return fmt.Sprintf("Successfully subscribed to %s.", repo)
+	msg := fmt.Sprintf("Successfully subscribed to %s.", repo)
+
+	ghRepo, _, err := githubClient.Repositories.Get(ctx, owner, repo)
+	if err != nil {
+		p.API.LogWarn("Failed to fetch repository", "error", err.Error())
+	} else if ghRepo != nil && ghRepo.GetPrivate() {
+		msg += "\n\n**Warning:** You subscribed to a private repository. Anyone with access to this channel will be able to read the events getting posted here."
+	}
+
+	return msg
 }
 
 func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string {
@@ -258,16 +268,7 @@ func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, paramet
 type CommandHandleFunc func(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string
 
 func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
-	split := strings.Fields(args.Command)
-	command := split[0]
-	var parameters []string
-	action := ""
-	if len(split) > 1 {
-		action = split[1]
-	}
-	if len(split) > 2 {
-		parameters = split[2:]
-	}
+	command, action, parameters := parseCommand(args.Command)
 
 	if command != "/github" {
 		return &model.CommandResponse{}, nil
@@ -323,3 +324,54 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
 	p.postCommandResponse(args, fmt.Sprintf("Unknown action %v", action))
 	return &model.CommandResponse{}, nil
 }
+
+// parseCommand parses the entire command input string and retrieves the command, action and parameters
+func parseCommand(input string) (command, action string, parameters []string) {
+	split := make([]string, 0)
+	current := ""
+	inQuotes := false
+
+	for _, char := range input {
+		if unicode.IsSpace(char) {
+			// keep whitespaces that are inside double qoutes
+			if inQuotes {
+				current += " "
+				continue
+			}
+
+			// ignore successive whitespaces that are outside of double quotes
+			if len(current) == 0 && !inQuotes {
+				continue
+			}
+
+			// append the current word to the list & move on to the next word/expression
+			split = append(split, current)
+			current = ""
+			continue
+		}
+
+		// append the current character to the current word
+		current += string(char)
+
+		if char == '"' {
+			inQuotes = !inQuotes
+		}
+	}
+
+	// append the last word/expression to the list
+	if len(current) > 0 {
+		split = append(split, current)
+	}
+
+	command = split[0]
+
+	if len(split) > 1 {
+		action = split[1]
+	}
+
+	if len(split) > 2 {
+		parameters = split[2:]
+	}
+
+	return command, action, parameters
+}
diff --git a/server/plugin/configuration.go b/server/plugin/configuration.go
index 0c1146d..63640a7 100644
--- a/server/plugin/configuration.go
+++ b/server/plugin/configuration.go
@@ -26,7 +26,7 @@ type Configuration struct {
 	EncryptionKey           string
 	EnterpriseBaseURL       string
 	EnterpriseUploadURL     string
-	EnableCodePreview       bool
+	EnableCodePreview       string
 }
 
 // Clone shallow copies the configuration. Your implementation may require a deep copy if
diff --git a/server/plugin/permalinks.go b/server/plugin/permalinks.go
index 063a8f8..c7637e2 100644
--- a/server/plugin/permalinks.go
+++ b/server/plugin/permalinks.go
@@ -88,6 +88,8 @@ func (p *Plugin) getReplacements(msg string) []replacement {
 // makeReplacements perform the given replacements on the msg and returns
 // the new msg. The replacements slice needs to be sorted by the index in ascending order.
 func (p *Plugin) makeReplacements(msg string, replacements []replacement, ghClient *github.Client) string {
+	config := p.getConfiguration()
+
 	// iterating the slice in reverse to preserve the replacement indices.
 	for i := len(replacements) - 1; i >= 0; i-- {
 		r := replacements[i]
@@ -97,15 +99,32 @@ func (p *Plugin) makeReplacements(msg string, replacements []replacement, ghClie
 			continue
 		}
 
+		ctx, cancel := context.WithTimeout(context.Background(), permalinkReqTimeout)
+		defer cancel()
+
+		// Check if repo is public
+		if config.EnableCodePreview != "privateAndPublic" {
+			repo, _, err := ghClient.Repositories.Get(ctx, r.permalinkInfo.user, r.permalinkInfo.repo)
+			if err != nil {
+				p.API.LogError("error while fetching repository information",
+					"error", err.Error(),
+					"repo", r.permalinkInfo.repo,
+					"user", r.permalinkInfo.user)
+				continue
+			}
+
+			if repo.GetPrivate() {
+				continue
+			}
+		}
+
 		// get the file contents
 		opts := github.RepositoryContentGetOptions{
 			Ref: r.permalinkInfo.commit,
 		}
 		// TODO: make all of these requests concurrently.
-		reqctx, cancel := context.WithTimeout(context.Background(), permalinkReqTimeout)
-		fileContent, _, _, err := ghClient.Repositories.GetContents(reqctx,
+		fileContent, _, _, err := ghClient.Repositories.GetContents(ctx,
 			r.permalinkInfo.user, r.permalinkInfo.repo, r.permalinkInfo.path, &opts)
-		defer cancel()
 		if err != nil {
 			p.API.LogError("error while fetching file contents", "error", err.Error(), "path", r.permalinkInfo.path)
 			continue
diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go
index 85c6ab1..37f14b2 100644
--- a/server/plugin/plugin.go
+++ b/server/plugin/plugin.go
@@ -164,7 +164,7 @@ func (p *Plugin) OnActivate() error {
 func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
 	// If not enabled in config, ignore.
 	config := p.getConfiguration()
-	if !config.EnableCodePreview {
+	if config.EnableCodePreview == "disable" {
 		return nil, ""
 	}
 
@@ -422,11 +422,19 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git
 		notificationType := notificationSubject.GetType()
 		switch notificationType {
 		case "RepositoryVulnerabilityAlert":
-			message := fmt.Sprintf("[Vulnerability Alert for %v](%v)", n.GetRepository().GetFullName(), fixGithubNotificationSubjectURL(n.GetSubject().GetURL()))
+			message := fmt.Sprintf("[Vulnerability Alert for %v](%v)", n.GetRepository().GetFullName(), fixGithubNotificationSubjectURL(n.GetSubject().GetURL(), ""))
 			notificationContent += fmt.Sprintf("* %v\n", message)
 		default:
+			issueURL := n.GetSubject().GetURL()
+			issueNumIndex := strings.LastIndex(issueURL, "/")
+			issueNum := issueURL[issueNumIndex+1:]
+			subjectURL := n.GetSubject().GetURL()
+			if n.GetSubject().GetLatestCommentURL() != "" {
+				subjectURL = n.GetSubject().GetLatestCommentURL()
+			}
+
 			notificationTitle := notificationSubject.GetTitle()
-			notificationURL := fixGithubNotificationSubjectURL(notificationSubject.GetURL())
+			notificationURL := fixGithubNotificationSubjectURL(subjectURL, issueNum)
 			notificationContent += getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType)
 		}
 
diff --git a/server/plugin/template.go b/server/plugin/template.go
index ddb549c..50cdb34 100644
--- a/server/plugin/template.go
+++ b/server/plugin/template.go
@@ -2,6 +2,7 @@ package plugin
 
 import (
 	"bytes"
+	"net/url"
 	"regexp"
 	"strings"
 	"text/template"
@@ -81,6 +82,25 @@ func init() {
 		return ">" + strings.ReplaceAll(body, "\n", "\n>")
 	}
 
+	// Escape characters not allowed in URL path
+	funcMap["pathEscape"] = url.PathEscape
+
+	// Transform multiple variables to dictionary
+	funcMap["dict"] = func(values ...interface{}) (map[string]interface{}, error) {
+		if len(values)%2 != 0 {
+			return nil, errors.New("invalid dict call, exactly one value is required for every key")
+		}
+		dict := make(map[string]interface{}, len(values)/2)
+		for i := 0; i < len(values); i += 2 {
+			key, ok := values[i].(string)
+			if !ok {
+				return nil, errors.New("dict keys must be strings")
+			}
+			dict[key] = values[i+1]
+		}
+		return dict, nil
+	}
+
 	masterTemplate = template.Must(template.New("master").Funcs(funcMap).Parse(""))
 
 	// The user template links to the corresponding GitHub user. If the GitHub user is a known
@@ -149,10 +169,24 @@ func init() {
 		`{{template "eventRepoIssueFullLink" .}} - {{.GetIssue.GetTitle}}`,
 	))
 
+	template.Must(masterTemplate.New("labels").Funcs(funcMap).Parse(`
+{{- if .Labels }}
+Labels: {{range $i, $el := .Labels -}}` + "{{- if $i}}, {{end}}[`{{ $el.Name }}`]({{ $.RepositoryURL }}/labels/{{ $el.Name | pathEscape }})" + `{{end -}}
+{{ end -}}
+`))
+
+	template.Must(masterTemplate.New("assignee").Funcs(funcMap).Parse(`
+{{- if .Assignees }}
+Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "user" $el}}{{end -}}
+{{- end -}}
+`))
+
 	template.Must(masterTemplate.New("newPR").Funcs(funcMap).Parse(`
 #### {{.GetPullRequest.GetTitle}}
 ##### {{template "eventRepoPullRequest" .}}
 #new-pull-request by {{template "user" .GetSender}}
+{{- template "labels" dict "Labels" .GetPullRequest.Labels "RepositoryURL" .GetRepo.GetHTMLURL  }}
+{{- template "assignee" .GetPullRequest }}
 
 {{.GetPullRequest.GetBody | removeComments | replaceAllGitHubUsernames}}
 `))
@@ -178,6 +212,8 @@ func init() {
 #### {{.GetIssue.GetTitle}}
 ##### {{template "eventRepoIssue" .}}
 #new-issue by {{template "user" .GetSender}}
+{{- template "labels" dict "Labels" .GetIssue.Labels "RepositoryURL" .GetRepo.GetHTMLURL  }}
+{{- template "assignee" .GetIssue }}
 
 {{.GetIssue.GetBody | removeComments | replaceAllGitHubUsernames}}
 `))
@@ -296,7 +332,8 @@ func init() {
 		"    * `deletes` - includes branch and tag deletions\n" +
 		"    * `issue_comments` - includes new issue comments\n" +
 		"    * `pull_reviews` - includes pull request reviews\n" +
-		"    * `label:<labelname>` - must include `pulls` or `issues` in feature list when using a label. Defaults to `pulls,issues,creates,deletes`\n" +
+		"    * `label:<labelname>` - limit pull request and issue events to only this label. Must include `pulls` or `issues` in feature list when using a label.\n" +
+		"    * Defaults to `pulls,issues,creates,deletes`\n" +
 		"  * `flags` currently supported:\n" +
 		"    * `--exclude-org-member` - events triggered by organization members will not be delivered (the GitHub organization config should be set, otherwise this flag has not effect)\n" +
 		"* `/github unsubscribe owner/repo` - Unsubscribe the current channel from a repository\n" +
diff --git a/server/plugin/utils.go b/server/plugin/utils.go
index ca40843..5ed0b47 100644
--- a/server/plugin/utils.go
+++ b/server/plugin/utils.go
@@ -160,12 +160,13 @@ func parseGitHubUsernamesFromText(text string) []string {
 	return usernames
 }
 
-func fixGithubNotificationSubjectURL(url string) string {
+func fixGithubNotificationSubjectURL(url, issueNum string) string {
 	url = strings.Replace(url, "api.", "", 1)
 	url = strings.Replace(url, "repos/", "", 1)
 	url = strings.Replace(url, "/pulls/", "/pull/", 1)
 	url = strings.Replace(url, "/commits/", "/commit/", 1)
 	url = strings.Replace(url, "/api/v3", "", 1)
+	url = strings.Replace(url, "comments/", issueNum+"#issuecomment-", 1)
 	return url
 }
 


Test patch:
diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go
index b15a1a9..36c470b 100644
--- a/server/plugin/api_test.go
+++ b/server/plugin/api_test.go
@@ -1,16 +1,58 @@
 package plugin
 
 import (
+	"io"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"testing"
 
 	"github.com/mattermost/mattermost-server/v5/plugin"
 	"github.com/mattermost/mattermost-server/v5/plugin/plugintest"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
 
 	"github.com/mattermost/mattermost-plugin-github/server/testutils"
 )
 
+type panicHandler struct {
+}
+
+func (ph panicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	panic("bad handler")
+}
+
+func TestWithRecovery(t *testing.T) {
+	defer func() {
+		if x := recover(); x != nil {
+			require.Fail(t, "got panic")
+		}
+	}()
+
+	p := NewPlugin()
+	api := &plugintest.API{}
+	api.On("LogError",
+		"Recovered from a panic",
+		"url", "http://random",
+		"error", "bad handler",
+		"stack", mock.Anything)
+	p.SetAPI(api)
+
+	ph := panicHandler{}
+	handler := p.withRecovery(ph)
+
+	req := httptest.NewRequest(http.MethodGet, "http://random", nil)
+	w := httptest.NewRecorder()
+	handler.ServeHTTP(w, req)
+
+	resp := w.Result()
+	if resp.Body != nil {
+		defer resp.Body.Close()
+		_, err := io.Copy(ioutil.Discard, resp.Body)
+		require.NoError(t, err)
+	}
+}
+
 func TestPlugin_ServeHTTP(t *testing.T) {
 	httpTestJSON := testutils.HTTPTest{
 		T:       t,
@@ -68,7 +110,7 @@ func TestPlugin_ServeHTTP(t *testing.T) {
 					EncryptionKey:           "mockKey",
 					EnterpriseBaseURL:       "",
 					EnterpriseUploadURL:     "",
-					EnableCodePreview:       false,
+					EnableCodePreview:       "disable",
 				})
 			p.initializeAPI()
 			p.SetAPI(&plugintest.API{})
diff --git a/server/plugin/command_test.go b/server/plugin/command_test.go
index d4791e7..8130833 100644
--- a/server/plugin/command_test.go
+++ b/server/plugin/command_test.go
@@ -44,32 +44,32 @@ func TestValidateFeatures(t *testing.T) {
 		},
 		{
 			name: "all features valid with label but issues and pulls missing",
-			args: []string{"pushes", "label:\"ruby\""},
+			args: []string{"pushes", `label:"ruby"`},
 			want: output{false, []string{}},
 		},
 		{
 			name: "all features valid with label and issues in features",
-			args: []string{"issues", "label:\"ruby\""},
+			args: []string{"issues", `label:"ruby"`},
 			want: output{true, []string{}},
 		},
 		{
 			name: "all features valid with label and pulls in features",
-			args: []string{"pulls", "label:\"ruby\""},
+			args: []string{"pulls", `label:"ruby"`},
 			want: output{true, []string{}},
 		},
 		{
 			name: "multiple features invalid with label but issues and pulls missing",
-			args: []string{"issue", "push", "label:\"ruby\""},
+			args: []string{"issue", "push", `label:"ruby"`},
 			want: output{false, []string{"issue", "push"}},
 		},
 		{
 			name: "multiple features invalid with label and issues in features",
-			args: []string{"issues", "push", "create", "label:\"ruby\""},
+			args: []string{"issues", "push", "create", `label:"ruby"`},
 			want: output{false, []string{"push", "create"}},
 		},
 		{
 			name: "multiple features invalid with label and pulls in features",
-			args: []string{"pulls", "push", "create", "label:\"ruby\""},
+			args: []string{"pulls", "push", "create", `label:"ruby"`},
 			want: output{false, []string{"push", "create"}},
 		},
 	}
@@ -83,3 +83,117 @@ func TestValidateFeatures(t *testing.T) {
 		})
 	}
 }
+
+func TestParseCommand(t *testing.T) {
+	type output struct {
+		command    string
+		action     string
+		parameters []string
+	}
+
+	tt := []struct {
+		name  string
+		input string
+		want  output
+	}{
+		{
+			name:  "no parameters",
+			input: "/github subscribe",
+			want: output{
+				"/github",
+				"subscribe",
+				[]string(nil),
+			},
+		},
+		{
+			name:  "no action and no parameters",
+			input: "/github",
+			want: output{
+				"/github",
+				"",
+				[]string(nil),
+			},
+		},
+		{
+			name:  "simple one-word label",
+			input: `/github subscribe DHaussermann/hello-world issues,label:"Help"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Help"`},
+			},
+		},
+		{
+			name:  "two-word label",
+			input: `/github subscribe DHaussermann/hello-world issues,label:"Help Wanted"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Help Wanted"`},
+			},
+		},
+		{
+			name:  "multi-word label",
+			input: `/github subscribe DHaussermann/hello-world issues,label:"Good First Issue"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Good First Issue"`},
+			},
+		},
+		{
+			name:  "multiple spaces inside double-quotes",
+			input: `/github subscribe DHaussermann/hello-world issues,label:"Help    Wanted"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Help    Wanted"`},
+			},
+		},
+		{
+			name:  "multiple spaces outside of double-quotes",
+			input: `  /github    subscribe     DHaussermann/hello-world issues,label:"Help Wanted"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Help Wanted"`},
+			},
+		},
+		{
+			name:  "trailing whitespaces",
+			input: `/github subscribe DHaussermann/hello-world issues,label:"Help Wanted" `,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Help Wanted"`},
+			},
+		},
+		{
+			name:  "non-ASCII characters",
+			input: `/github subscribe طماطم issues,label:"日本語"`,
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"طماطم", `issues,label:"日本語"`},
+			},
+		},
+		{
+			name:  "line breaks",
+			input: "/github \nsubscribe\nDHaussermann/hello-world\nissues,label:\"Good First Issue\"",
+			want: output{
+				"/github",
+				"subscribe",
+				[]string{"DHaussermann/hello-world", `issues,label:"Good First Issue"`},
+			},
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			command, action, parameters := parseCommand(tc.input)
+			got := output{command, action, parameters}
+			errMsg := fmt.Sprintf("validateFeatures() = %v, want %v", got, tc.want)
+			assert.EqualValues(t, tc.want, got, errMsg)
+		})
+	}
+}
diff --git a/server/plugin/template_test.go b/server/plugin/template_test.go
index bdc9b5a..c08de39 100644
--- a/server/plugin/template_test.go
+++ b/server/plugin/template_test.go
@@ -19,6 +19,21 @@ var pushEventRepository = github.PushEventRepository{
 	HTMLURL:  sToP("https://github.com/mattermost/mattermost-plugin-github"),
 }
 
+var singleLabel = []*github.Label{
+	{
+		Name: sToP("Help Wanted"),
+	},
+}
+
+var labels = []*github.Label{
+	{
+		Name: sToP("Help Wanted"),
+	},
+	{
+		Name: sToP("Tech/Go"),
+	},
+}
+
 var pullRequest = github.PullRequest{
 	Number:    iToP(42),
 	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/pull/42"),
@@ -46,6 +61,36 @@ var pullRequestWithMentions = github.PullRequest{
 -->`),
 }
 
+var pullRequestWithLabelAndAssignee = github.PullRequest{
+	Number:    iToP(42),
+	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/pull/42"),
+	Title:     sToP("Leverage git-get-head"),
+	CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)),
+	UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)),
+	Body: sToP(`<!-- Thank you for opening this pull request-->git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases.
+<!-- Please make sure you have done the following :
+- Added tests
+- Removed console logs
+-->`),
+	Labels:    singleLabel,
+	Assignees: []*github.User{&user},
+}
+
+var pullRequestWithMultipleLabelsAndAssignees = github.PullRequest{
+	Number:    iToP(42),
+	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/pull/42"),
+	Title:     sToP("Leverage git-get-head"),
+	CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)),
+	UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)),
+	Body: sToP(`<!-- Thank you for opening this pull request-->git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases.
+<!-- Please make sure you have done the following :
+- Added tests
+- Removed console logs
+-->`),
+	Labels:    labels,
+	Assignees: []*github.User{&user, &user},
+}
+
 var mergedPullRequest = github.PullRequest{
 	Number:    iToP(42),
 	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/pull/42"),
@@ -79,6 +124,29 @@ var issueWithMentions = github.Issue{
 ` + gitHubMentions),
 }
 
+var issueWithLabelAndAssignee = github.Issue{
+	Number:    iToP(1),
+	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"),
+	Title:     sToP("Implement git-get-head"),
+	CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)),
+	UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)),
+	Body:      sToP(`<!-- Thank you for opening this issue-->git-get-head sounds like a great feature we should support`),
+	Labels:    singleLabel,
+	Assignee:  &user,
+	Assignees: []*github.User{&user},
+}
+
+var issueWithMultipleLabelsAndAssignee = github.Issue{
+	Number:    iToP(1),
+	HTMLURL:   sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"),
+	Title:     sToP("Implement git-get-head"),
+	CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)),
+	UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)),
+	Body:      sToP(`<!-- Thank you for opening this issue-->git-get-head sounds like a great feature we should support`),
+	Labels:    labels,
+	Assignees: []*github.User{&user, &user},
+}
+
 var user = github.User{
 	Login:   sToP("panda"),
 	HTMLURL: sToP("https://github.com/panda"),
@@ -198,6 +266,48 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app
 		require.NoError(t, err)
 		require.Equal(t, expected, actual)
 	}))
+
+	t.Run("with single label and assignee", func(t *testing.T) {
+		expected := `
+#### Leverage git-get-head
+##### [mattermost-plugin-github#42](https://github.com/mattermost/mattermost-plugin-github/pull/42)
+#new-pull-request by [panda](https://github.com/panda)
+Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted)" + `
+Assignees: [panda](https://github.com/panda)
+
+git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases.
+
+`
+
+		actual, err := renderTemplate("newPR", &github.PullRequestEvent{
+			Repo:        &repo,
+			PullRequest: &pullRequestWithLabelAndAssignee,
+			Sender:      &user,
+		})
+		require.NoError(t, err)
+		require.Equal(t, expected, actual)
+	})
+
+	t.Run("with multiple labels and assignees", func(t *testing.T) {
+		expected := `
+#### Leverage git-get-head
+##### [mattermost-plugin-github#42](https://github.com/mattermost/mattermost-plugin-github/pull/42)
+#new-pull-request by [panda](https://github.com/panda)
+Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted), [`Tech/Go`](https://github.com/mattermost/mattermost-plugin-github/labels/Tech%2FGo)" + `
+Assignees: [panda](https://github.com/panda), [panda](https://github.com/panda)
+
+git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases.
+
+`
+
+		actual, err := renderTemplate("newPR", &github.PullRequestEvent{
+			Repo:        &repo,
+			PullRequest: &pullRequestWithMultipleLabelsAndAssignees,
+			Sender:      &user,
+		})
+		require.NoError(t, err)
+		require.Equal(t, expected, actual)
+	})
 }
 
 func TestClosedPRMessageTemplate(t *testing.T) {
@@ -286,6 +396,46 @@ git-get-head sounds like a great feature we should support
 		require.NoError(t, err)
 		require.Equal(t, expected, actual)
 	}))
+
+	t.Run("with single label and assignee", func(t *testing.T) {
+		expected := `
+#### Implement git-get-head
+##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1)
+#new-issue by [panda](https://github.com/panda)
+Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted)" + `
+Assignees: [panda](https://github.com/panda)
+
+git-get-head sounds like a great feature we should support
+`
+
+		actual, err := renderTemplate("newIssue", &github.IssuesEvent{
+			Repo:   &repo,
+			Issue:  &issueWithLabelAndAssignee,
+			Sender: &user,
+		})
+		require.NoError(t, err)
+		require.Equal(t, expected, actual)
+	})
+
+	t.Run("with multiple labels and assignees", func(t *testing.T) {
+		expected := `
+#### Implement git-get-head
+##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1)
+#new-issue by [panda](https://github.com/panda)
+Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted), [`Tech/Go`](https://github.com/mattermost/mattermost-plugin-github/labels/Tech%2FGo)" + `
+Assignees: [panda](https://github.com/panda), [panda](https://github.com/panda)
+
+git-get-head sounds like a great feature we should support
+`
+
+		actual, err := renderTemplate("newIssue", &github.IssuesEvent{
+			Repo:   &repo,
+			Issue:  &issueWithMultipleLabelsAndAssignee,
+			Sender: &user,
+		})
+		require.NoError(t, err)
+		require.Equal(t, expected, actual)
+	})
 }
 
 func TestClosedIssueTemplate(t *testing.T) {
diff --git a/server/plugin/utils_test.go b/server/plugin/utils_test.go
index b6bd976..46ca183 100644
--- a/server/plugin/utils_test.go
+++ b/server/plugin/utils_test.go
@@ -39,16 +39,18 @@ func TestFixGithubNotificationSubjectURL(t *testing.T) {
 	tcs := []struct {
 		Text     string
 		Expected string
+		IssueNum string
 	}{
 		{Text: "https://api.github.com/repos/jwilander/mattermost-webapp/issues/123", Expected: "https://github.com/jwilander/mattermost-webapp/issues/123"},
 		{Text: "https://api.github.com/repos/jwilander/mattermost-webapp/pulls/123", Expected: "https://github.com/jwilander/mattermost-webapp/pull/123"},
 		{Text: "https://enterprise.github.com/api/v3/jwilander/mattermost-webapp/issues/123", Expected: "https://enterprise.github.com/jwilander/mattermost-webapp/issues/123"},
 		{Text: "https://enterprise.github.com/api/v3/jwilander/mattermost-webapp/pull/123", Expected: "https://enterprise.github.com/jwilander/mattermost-webapp/pull/123"},
 		{Text: "https://api.github.com/repos/mattermost/mattermost-server/commits/cc6c385d3e8903546fc6fc856bf468ad09b70913", Expected: "https://github.com/mattermost/mattermost-server/commit/cc6c385d3e8903546fc6fc856bf468ad09b70913"},
+		{Text: "https://api.github.com/repos/user/rate_my_cakes/issues/comments/655139214", Expected: "https://github.com/user/rate_my_cakes/issues/4#issuecomment-655139214", IssueNum: "4"},
 	}
 
 	for _, tc := range tcs {
-		assert.Equal(t, tc.Expected, fixGithubNotificationSubjectURL(tc.Text))
+		assert.Equal(t, tc.Expected, fixGithubNotificationSubjectURL(tc.Text, tc.IssueNum))
 	}
 }