Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[mm-507] Add support for DM subscriptions based on "watching" an issue in Jira (#507) #792

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
397c5fd
Add support for DM subscriptions based on "watching" an issue in Jira…
jupriano Aug 4, 2021
626719d
Jira's autolink should support issue links that contain a comment lin…
jupriano Aug 19, 2021
d046db9
Jira's autolink should support issue links that contain a comment lin…
jupriano Sep 2, 2021
2325e8e
Jira's autolink should support issue links that contain a comment lin…
jupriano Sep 2, 2021
899017f
Update server/webhook.go
jupriano Oct 28, 2021
8b6b28b
Fixing Request Changes (#507)
Oct 28, 2021
e17c0cf
Fixing Request Changes (#507)
Oct 28, 2021
74e9b69
Revert "Fixing Request Changes (#507)"
Oct 28, 2021
51f0fb2
Revert "Fixing Request Changes (#507)"
Oct 28, 2021
ece396d
Fixing Request Changes (#507)
Oct 28, 2021
dd55c1d
Merge branch 'mattermost:master' into MM-507
jupriano Nov 15, 2021
e3e5502
[GH-507] Change GetConnection Struct (#507)
Nov 16, 2021
29e4ed3
[mm-507] Fixing Unit Test and CI lint
Nov 25, 2021
0b02444
[mm-507] Fixing Unit Test and CI lint
Nov 25, 2021
f980d21
[mm-507] Fixing Unit Test and CI lint
Nov 25, 2021
9cc2e4d
[mm-507] Fixing Unit Test and CI lint
Nov 25, 2021
028ae19
[mm-507] Remove no notification block issue watcher (#507)
Nov 25, 2021
23d01fc
[mm-507] Loload instance error instance not found (#507)
Nov 25, 2021
e1e669d
Update server/user_test.go
jupriano Dec 1, 2021
702c079
Update server/plugin.go
jupriano Dec 1, 2021
24eaa07
Update server/command_test.go
jupriano Dec 1, 2021
73373d1
Update server/command_test.go
jupriano Dec 1, 2021
29a812d
Update server/user_test.go
jupriano Dec 1, 2021
9d72ffe
[MM-507] Add Error Log for error (#MM-507)
Dec 1, 2021
ae2b7f2
[MM-507] Fixing Failed Test
Dec 6, 2021
65f0e66
[MM-507] Fixing Failed Test
Dec 6, 2021
681eff5
[MM-507] Fixing Failed Test
Dec 6, 2021
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
10 changes: 10 additions & 0 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type SearchService interface {
type IssueService interface {
GetIssue(key string, options *jira.GetQueryOptions) (*jira.Issue, error)
CreateIssue(issue *jira.Issue) (*jira.Issue, error)
GetWatchers(issueKey string) (*[]jira.User, error)

AddAttachment(api plugin.API, issueKey, fileID string, maxSize utils.ByteSize) (mattermostName, jiraName, mime string, err error)
AddComment(issueKey string, comment *jira.Comment) (*jira.Comment, error)
Expand Down Expand Up @@ -195,6 +196,15 @@ func (client JiraClient) GetIssue(key string, options *jira.GetQueryOptions) (*j
return issue, nil
}

// GetWatchers returns an Issue watchers by issueKey.
jfrerich marked this conversation as resolved.
Show resolved Hide resolved
func (client JiraClient) GetWatchers(issueKey string) (*[]jira.User, error) {
issue, resp, err := client.Jira.Issue.GetWatchers(issueKey)
mickmister marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, userFriendlyJiraError(resp, err)
}
return issue, nil
}

// GetTransitions returns transitions for an issue with issueKey.
func (client JiraClient) GetTransitions(issueKey string) ([]jira.Transition, error) {
transitions, resp, err := client.Jira.Issue.GetTransitions(issueKey)
Expand Down
2 changes: 2 additions & 0 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ func executeSettings(p *Plugin, c *plugin.Context, header *model.CommandArgs, ar
return p.responsef(header, "Current settings:\n%s", conn.Settings.String())
case "notifications":
return p.settingsNotifications(header, instance.GetID(), user.MattermostUserID, conn, args)
case "watching":
return p.settingsWatching(header, instance.GetID(), user.MattermostUserID, conn, args)
default:
return p.responsef(header, "Unknown setting.")
}
Expand Down
39 changes: 39 additions & 0 deletions server/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,42 @@ func (p *Plugin) settingsNotifications(header *model.CommandArgs, instanceID, ma

return p.responsef(header, "Settings updated. Notifications %s.", notifications)
}

func (p *Plugin) settingsWatching(header *model.CommandArgs, instanceID, mattermostUserID types.ID, connection *Connection, args []string) *model.CommandResponse {
const helpText = "`/jira watching watching [value]`\n* Invalid value. Accepted values are: `on` or `off`."
jfrerich marked this conversation as resolved.
Show resolved Hide resolved

if len(args) != 2 {
return p.responsef(header, helpText)
}

var value bool
switch args[1] {
case settingOn:
value = true
case settingOff:
value = false
default:
return p.responsef(header, helpText)
}

if connection.Settings == nil {
connection.Settings = &ConnectionSettings{}
}
connection.Settings.Watching = value
if err := p.userStore.StoreConnection(instanceID, mattermostUserID, connection); err != nil {
p.errorf("settingsWatching, err: %v", err)
p.responsef(header, "Could not store new settings. Please contact your system administrator. error: %v", err)
}

// send back the actual value
updatedConnection, err := p.userStore.LoadConnection(instanceID, mattermostUserID)
if err != nil {
return p.responsef(header, "Your username is not connected to Jira. Please type `jira connect`. %v", err)
mickmister marked this conversation as resolved.
Show resolved Hide resolved
}
watching := settingOff
if updatedConnection.Settings.Watching {
watching = settingOn
}

return p.responsef(header, "Settings updated. Watching %s.", watching)
jfrerich marked this conversation as resolved.
Show resolved Hide resolved
}
1 change: 1 addition & 0 deletions server/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (c *Connection) JiraAccountID() types.ID {

type ConnectionSettings struct {
Notifications bool `json:"notifications"`
Watching bool `json:"watching"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this *bool, and use a similar strategy as described here? I'd like users to be able to get these notifications without enabling this.

@DHaussermann Maybe we should be clear about why the user got the notification, and note that they can opt-out of this feature? If we have it off by default, I think it won't be discovered by most users.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister I've pushed a new commit for this change, could you please check it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DHaussermann @aaronrothschild Please see my comment above

  • If the current settings for a user has notifications "on" before this PR, should we assume the user also wants reporter and watcher notifications when we introduce the changes from this PR? Should we tell the user how to change their settings? Maybe we only tell them how to change their settings the next time they get a notification. We tell them how to change their settings, and don't remind them again.

  • Should we tell the user the reason they are getting a notification? It would be one of the following reasons:

    • Mentioned
    • Assignee
    • Reporter
    • Watching

}

func (s *ConnectionSettings) String() string {
Expand Down
79 changes: 79 additions & 0 deletions server/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"fmt"
"github.com/andygrunwald/go-jira"
"net/http"
"net/url"

Expand Down Expand Up @@ -109,6 +110,8 @@ func (wh *webhook) PostNotifications(p *Plugin, instanceID types.ID) ([]*model.P
return nil, http.StatusOK, nil
}

wh.checkWatcherUser(p, instance)
mickmister marked this conversation as resolved.
Show resolved Hide resolved

posts := []*model.Post{}
for _, notification := range wh.notifications {
var mattermostUserID types.ID
Expand Down Expand Up @@ -199,3 +202,79 @@ func (p *Plugin) GetWebhookURL(jiraURL string, teamID, channelID string) (subURL

return subURL, legacyURL, nil
}

func (wh *webhook) getConnection(p *Plugin, instance Instance, notification webhookUserNotification) (con *Connection, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this method need to be defined on *webhook? Is something similar already implemented on the *Plugin struct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicky-demansol I don't think this method needs to be defined on *webhook. Can we just call an existing method to fetch the connection instead?

var mattermostUserID types.ID

// prefer accountId to username when looking up UserIds
if notification.jiraAccountID != "" {
mattermostUserID, err = p.userStore.LoadMattermostUserID(instance.GetID(), notification.jiraAccountID)
} else {
mattermostUserID, err = p.userStore.LoadMattermostUserID(instance.GetID(), notification.jiraUsername)
}
if err != nil {
return
}

// Check if the user has permissions.
con, err = p.userStore.LoadConnection(instance.GetID(), mattermostUserID)
if err != nil {
// Not connected to Jira, so can't check permissions
return
}

return
}

func (wh *webhook) checkWatcherUser(p *Plugin, instance Instance) {
watcherUsers := &[]jira.User{}
for _, notification := range wh.notifications {
c, err := wh.getConnection(p, instance, notification)
if err != nil {
continue
}
client, err2 := instance.GetClient(c)
if err2 != nil {
continue
}
watcherUsers, err = client.GetWatchers(wh.Issue.ID)
if err != nil {
continue
}
break
}
mickmister marked this conversation as resolved.
Show resolved Hide resolved

for _, watcherUser := range *watcherUsers {
whUserNotification := &webhookUserNotification{}
mickmister marked this conversation as resolved.
Show resolved Hide resolved
postType := ""
message := ""
for _, notification := range wh.notifications {
if notification.jiraAccountID == watcherUser.AccountID || notification.jiraUsername == watcherUser.Name {
mickmister marked this conversation as resolved.
Show resolved Hide resolved
jupriano marked this conversation as resolved.
Show resolved Hide resolved
whUserNotification = &notification
mickmister marked this conversation as resolved.
Show resolved Hide resolved
}
postType = notification.postType
message = notification.message
jupriano marked this conversation as resolved.
Show resolved Hide resolved
}
mickmister marked this conversation as resolved.
Show resolved Hide resolved
if whUserNotification == nil {
whUserNotification = &webhookUserNotification{
jiraUsername: watcherUser.Name,
jiraAccountID: watcherUser.AccountID,
message: message,
postType: postType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here when the wh.notifications array is empty and message is blank? How do we access the message to create a notification? Maybe we need to call a function to generate a message for this webhook call.

Copy link
Author

@jupriano jupriano Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister I've pushed a new commit for this change, could you please check it?

I put the same logic with func PostNotifications

func (wh *webhook) PostNotifications(p *Plugin, instanceID types.ID) ([]*model.Post, int, error) {
if len(wh.notifications) == 0 {
return nil, http.StatusOK, nil
}

Copy link
Contributor

@mickmister mickmister Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commentSelf: watcherUser.Self,
mickmister marked this conversation as resolved.
Show resolved Hide resolved
}

c, err := wh.getConnection(p, instance, *whUserNotification)
if err != nil {
continue
}

// if setting watching value is false don't put into webhookUserNotification
if c.Settings == nil || !c.Settings.Watching || err != nil {
mickmister marked this conversation as resolved.
Show resolved Hide resolved
continue
}

wh.notifications = append(wh.notifications, *whUserNotification)
}
}
}