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,699]: Added feature to receive notifications for different Jira actions. #1097

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 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
33 changes: 32 additions & 1 deletion server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type UserService interface {
type ProjectService interface {
GetProject(key string) (*jira.Project, error)
ListProjects(query string, limit int, expandIssueTypes bool) (jira.ProjectList, error)
GetAllProjectKeys() ([]string, error)
GetIssueTypes(projectID string) ([]jira.IssueType, error)
ListProjectStatuses(projectID string) ([]*IssueTypeWithStatuses, error)
}
Expand All @@ -66,6 +67,7 @@ type SearchService interface {
SearchUsersAssignableToIssue(issueKey, query string, maxResults int) ([]jira.User, error)
SearchUsersAssignableInProject(projectKey, query string, maxResults int) ([]jira.User, error)
SearchAutoCompleteFields(params map[string]string) (*AutoCompleteResult, error)
GetWatchers(instanceID, issueKey string, connection *Connection) (*jira.Watches, error)
}

// IssueService is the interface for issue-related APIs.
Expand Down Expand Up @@ -179,6 +181,20 @@ func (client JiraClient) GetProject(key string) (*jira.Project, error) {
return project, nil
}

func (client JiraClient) GetAllProjectKeys() ([]string, error) {
projectList, resp, err := client.Jira.Project.GetList()
if err != nil {
return nil, userFriendlyJiraError(resp, err)
}

keys := make([]string, len(*projectList))
for index, project := range *projectList {
keys[index] = project.Key
}

return keys, nil
}

// GetIssue returns an Issue by key (with options).
func (client JiraClient) GetIssue(key string, options *jira.GetQueryOptions) (*jira.Issue, error) {
issue, resp, err := client.Jira.Issue.Get(key, options)
Expand All @@ -188,6 +204,21 @@ func (client JiraClient) GetIssue(key string, options *jira.GetQueryOptions) (*j
return issue, nil
}

// GetWatchers returns an array of Jira users watching a given issue.
func (client JiraClient) GetWatchers(instanceID, issueKey string, connection *Connection) (*jira.Watches, error) {
var watchers jira.Watches
params := map[string]string{
"accountId": connection.AccountID,
}

endpoint := fmt.Sprintf("2/issue/%s/watchers", issueKey)

if err := client.RESTGet(endpoint, params, &watchers); err != nil {
return nil, err
}
return &watchers, 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 Expand Up @@ -393,7 +424,7 @@ func endpointURL(endpoint string) (string, error) {
return "", err
}
if parsedURL.Scheme == "" {
// relative path
// Relative path
fmartingr marked this conversation as resolved.
Show resolved Hide resolved
endpoint = path.Join("/rest/api", endpoint)
}
return endpoint, nil
Expand Down
39 changes: 32 additions & 7 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ const commonHelpText = "\n" +
"* `/jira me` - Display information about the current user\n" +
"* `/jira about` - Display build info\n" +
"* `/jira instance list` - List installed Jira instances\n" +
"* `/jira instance settings [setting] [value]` - Update your user settings\n" +
"* `/jira instance settings [setting] [role] [value]` - Update your user settings\n" +
" * [setting] can be `notifications`\n" +
" * [role] can be `assignee` , `mention` , `reporter` or `watching`\n" +
" * [value] can be `on` or `off`\n" +
""

Expand Down Expand Up @@ -262,13 +263,34 @@ func createSettingsCommand(optInstance bool) *model.AutocompleteData {
"list", "", "View your current settings")
settings.AddCommand(list)

notifications := model.NewAutocompleteData(
"notifications", "[on|off]", "Update your user notifications settings")
notifications.AddStaticListArgument("value", true, []model.AutocompleteListItem{
setting := []model.AutocompleteListItem{
{HelpText: "Turn notifications on", Item: "on"},
{HelpText: "Turn notifications off", Item: "off"},
})
withFlagInstance(notifications, optInstance, makeAutocompleteRoute(routeAutocompleteInstalledInstanceWithAlias))
}
notifications := model.NewAutocompleteData(
"notifications", "[assignee|mention|reporter|watching]", "manage notifications")

assigneeNotifications := model.NewAutocompleteData(assigneeRole, "", "manage assignee notifications")
assigneeNotifications.AddStaticListArgument("value", true, setting)
withFlagInstance(assigneeNotifications, optInstance, makeAutocompleteRoute(routeAutocompleteInstalledInstanceWithAlias))

mentionNotifications := model.NewAutocompleteData(mentionRole, "", "manage mention notifications")
mentionNotifications.AddStaticListArgument("value", true, setting)
withFlagInstance(mentionNotifications, optInstance, makeAutocompleteRoute(routeAutocompleteInstalledInstanceWithAlias))

reporterNotifications := model.NewAutocompleteData(reporterRole, "", "manage reporter notifications")
reporterNotifications.AddStaticListArgument("value", true, setting)
withFlagInstance(reporterNotifications, optInstance, makeAutocompleteRoute(routeAutocompleteInstalledInstanceWithAlias))

watchingNotifications := model.NewAutocompleteData(watchingRole, "", "manage watching notifications")
watchingNotifications.AddStaticListArgument("value", true, setting)
withFlagInstance(watchingNotifications, optInstance, makeAutocompleteRoute(routeAutocompleteInstalledInstanceWithAlias))

notifications.AddCommand(assigneeNotifications)
notifications.AddCommand(mentionNotifications)
notifications.AddCommand(reporterNotifications)
notifications.AddCommand(watchingNotifications)

settings.AddCommand(notifications)

return settings
Expand Down Expand Up @@ -601,7 +623,10 @@ func executeSettings(p *Plugin, c *plugin.Context, header *model.CommandArgs, ar

switch args[0] {
case "list":
return p.responsef(header, "Current settings:\n%s", conn.Settings.String())
if conn.Settings != nil {
return p.responsef(header, "Current settings:\n%s", conn.Settings.String())
}
return p.responsef(header, "Please connect to Jira account using the command `/jira connect`")
case "notifications":
return p.settingsNotifications(header, instance.GetID(), user.MattermostUserID, conn, args)
default:
Expand Down
120 changes: 89 additions & 31 deletions server/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,20 @@ func getMockUserStoreKV() mockUserStoreKV {

connection := Connection{
User: jira.User{
AccountID: "test",
AccountID: "test-AccountID",
},
}

withNotifications := connection // copy
withNotifications.Settings = &ConnectionSettings{Notifications: true}
withNotifications.Settings = &ConnectionSettings{
Notifications: true,
RolesForDMNotification: map[string]bool{
assigneeRole: true,
mentionRole: true,
reporterRole: true,
watchingRole: true,
},
}

return mockUserStoreKV{
users: map[types.ID]*User{
Expand Down Expand Up @@ -123,7 +131,7 @@ func (p *Plugin) getMockInstanceStoreKV(n int) *mockInstanceStoreKV {
}

for i, ti := range []*testInstance{testInstance1, testInstance2} {
if i > n {
if i >= n {
break
}
instance := *ti
Expand Down Expand Up @@ -169,12 +177,12 @@ func TestPlugin_ExecuteCommand_Settings(t *testing.T) {
"no params, with notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Current settings:\n\tNotifications: on",
expectedMsg: "Current settings:\n\t- Notifications for assignee: on \n\t- Notifications for mention: on \n\t- Notifications for reporter: on \n\t- Notifications for watching: on",
},
"no params, without notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Current settings:\n\tNotifications: off",
expectedMsg: "Current settings:\n\t- Notifications for assignee: off \n\t- Notifications for mention: off \n\t- Notifications for reporter: off \n\t- Notifications for watching: off",
},
"unknown setting": {
commandArgs: &model.CommandArgs{Command: "/jira settings" + " test", UserId: mockUserIDWithoutNotifications},
Expand All @@ -184,22 +192,52 @@ func TestPlugin_ExecuteCommand_Settings(t *testing.T) {
"set notifications without value": {
commandArgs: &model.CommandArgs{Command: "/jira settings" + " notifications", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "`/jira settings notifications [value]`\n* Invalid value. Accepted values are: `on` or `off`.",
expectedMsg: "`/jira settings notifications [assignee|mention|reporter|watching] [value]`\n* Invalid command args.",
},
"set notification with unknown value": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications test", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "`/jira settings notifications [value]`\n* Invalid value. Accepted values are: `on` or `off`.",
expectedMsg: "`/jira settings notifications [assignee|mention|reporter|watching] [value]`\n* Invalid command args.",
},
"enable assignee notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications assignee on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Assignee notifications on.",
},
"disable assignee notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications assignee off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Assignee notifications off.",
},
"enable reporter notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications reporter on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Reporter notifications on.",
},
"disable reporter notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications reporter off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Reporter notifications off.",
},
"enable mention notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications mention on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Mention notifications on.",
},
"disable mention notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications mention off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Mention notifications off.",
},
"enable notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications on", UserId: mockUserIDWithoutNotifications},
"enable watching notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications watching on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated. Notifications on.",
expectedMsg: "Settings updated:\n* Watching notifications on.",
},
"disable notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications off", UserId: mockUserIDWithNotifications},
"disable watching notifications": {
commandArgs: &model.CommandArgs{Command: "/jira settings notifications watching off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated. Notifications off.",
expectedMsg: "Settings updated:\n* Watching notifications off.",
},
}
for name, tt := range tests {
Expand Down Expand Up @@ -255,12 +293,12 @@ func TestPlugin_ExecuteCommand_Instance_Settings(t *testing.T) {
"no params, with notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Current settings:\n\tNotifications: on",
expectedMsg: "Current settings:\n\t- Notifications for assignee: on \n\t- Notifications for mention: on \n\t- Notifications for reporter: on \n\t- Notifications for watching: on",
},
"no params, without notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Current settings:\n\tNotifications: off",
expectedMsg: "Current settings:\n\t- Notifications for assignee: off \n\t- Notifications for mention: off \n\t- Notifications for reporter: off \n\t- Notifications for watching: off",
},
"unknown setting": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings" + " test", UserId: mockUserIDWithoutNotifications},
Expand All @@ -270,32 +308,52 @@ func TestPlugin_ExecuteCommand_Instance_Settings(t *testing.T) {
"set notifications without value": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings" + " notifications", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "`/jira settings notifications [value]`\n* Invalid value. Accepted values are: `on` or `off`.",
expectedMsg: "`/jira settings notifications [assignee|mention|reporter|watching] [value]`\n* Invalid command args.",
},
"set notification with unknown value": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications test", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "`/jira settings notifications [value]`\n* Invalid value. Accepted values are: `on` or `off`.",
expectedMsg: "`/jira settings notifications [assignee|mention|reporter|watching] [value]`\n* Invalid command args.",
},
"enable notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications on", UserId: mockUserIDWithoutNotifications},
"enable assignee notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications assignee on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated. Notifications on.",
expectedMsg: "Settings updated:\n* Assignee notifications on.",
},
"disable notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications off", UserId: mockUserIDWithNotifications},
"disable assignee notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications assignee off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated. Notifications off.",
expectedMsg: "Settings updated:\n* Assignee notifications off.",
},
"multiple instances are present: Notifications off": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications off --instance https://jiraurl1.com", UserId: mockUserIDWithNotifications},
numInstances: 2,
expectedMsg: "Settings updated. Notifications off.",
"enable reporter notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications reporter on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Reporter notifications on.",
},
"multiple instances are present: Notifications on": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications on --instance https://jiraurl2.com", UserId: mockUserIDWithNotifications},
numInstances: 2,
expectedMsg: "Settings updated. Notifications on.",
"disable reporter notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications reporter off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Reporter notifications off.",
},
"enable mention notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications mention on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Mention notifications on.",
},
"disable mention notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications mention off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Mention notifications off.",
},
"enable watching notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications watching on", UserId: mockUserIDWithoutNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Watching notifications on.",
},
"disable watching notifications": {
commandArgs: &model.CommandArgs{Command: "/jira instance settings notifications watching off", UserId: mockUserIDWithNotifications},
numInstances: 1,
expectedMsg: "Settings updated:\n* Watching notifications off.",
},
}
for name, tt := range tests {
Expand Down
24 changes: 12 additions & 12 deletions server/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ func TestSubscribe(t *testing.T) {
api := &plugintest.API{}
p := Plugin{}

api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return()

api.On("GetChannelMember", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.ChannelMember{}, (*model.AppError)(nil))
api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
Expand Down Expand Up @@ -431,9 +431,9 @@ func TestDeleteSubscription(t *testing.T) {
api := &plugintest.API{}
p := Plugin{}

api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return()

api.On("GetChannelMember", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.ChannelMember{}, (*model.AppError)(nil))
api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
Expand Down Expand Up @@ -653,9 +653,9 @@ func TestEditSubscription(t *testing.T) {
api := &plugintest.API{}
p := Plugin{}

api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return()

api.On("GetChannelMember", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.ChannelMember{}, (*model.AppError)(nil))
api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
Expand Down Expand Up @@ -817,9 +817,9 @@ func TestGetSubscriptionsForChannel(t *testing.T) {
api := &plugintest.API{}
p := Plugin{}

api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return(nil)
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 10)...).Return()
api.On("LogWarn", mockAnythingOfTypeBatch("string", 13)...).Return()

api.On("GetChannelMember", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.ChannelMember{}, (*model.AppError)(nil))

Expand Down
Loading
Loading