Skip to content

Prevent sending emails and notifications to inactive users #2384

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

Merged
merged 7 commits into from
Sep 16, 2017
Merged
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 models/fixtures/repository.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
num_pulls: 2
num_closed_pulls: 0
num_milestones: 2
num_watches: 2
num_watches: 3

-
id: 2
Expand Down
5 changes: 5 additions & 0 deletions models/fixtures/watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@
id: 2
user_id: 4
repo_id: 1

-
id: 3
user_id: 10
repo_id: 1
7 changes: 5 additions & 2 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1204,8 +1204,11 @@ func GetParticipantsByIssueID(issueID int64) ([]*User, error) {
func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
userIDs := make([]int64, 0, 5)
if err := e.Table("comment").Cols("poster_id").
Where("issue_id = ?", issueID).
And("type = ?", CommentTypeComment).
Where("`comment`.issue_id = ?", issueID).
And("`comment`.type = ?", CommentTypeComment).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `comment`.poster_id").
Distinct("poster_id").
Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %v", err)
Expand Down
8 changes: 6 additions & 2 deletions models/issue_mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, comment
return fmt.Errorf("getParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err)
}

// In case the issue poster is not watching the repository,
// In case the issue poster is not watching the repository and is active,
// even if we have duplicated in watchers, can be safely filtered out.
if issue.PosterID != doer.ID {
poster, err := GetUserByID(issue.PosterID)
if err != nil {
return fmt.Errorf("GetUserByID [%d]: %v", issue.PosterID, err)
}
if issue.PosterID != doer.ID && poster.IsActive && !poster.ProhibitLogin {
Copy link
Member

Choose a reason for hiding this comment

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

missing testing in this patch?.

Copy link
Member

Choose a reason for hiding this comment

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

@appleboy what do you mean?

participants = append(participants, issue.Poster)
}

Expand Down
3 changes: 2 additions & 1 deletion models/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ func TestGetParticipantsByIssueID(t *testing.T) {
// User 1 is issue1 poster (see fixtures/issue.yml)
// User 2 only labeled issue1 (see fixtures/comment.yml)
// Users 3 and 5 made actual comments (see fixtures/comment.yml)
checkParticipants(1, []int{3, 5})
// User 3 is inactive, thus not active participant
checkParticipants(1, []int{5})
}

func TestIssue_AddLabel(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion models/issue_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {

func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
err = e.
Where("issue_id = ?", issueID).
Where("`issue_watch`.issue_id = ?", issueID).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `issue_watch`.user_id").
Find(&watches)
return
}
5 changes: 5 additions & 0 deletions models/issue_watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func TestGetIssueWatchers(t *testing.T) {

iws, err := GetIssueWatchers(1)
assert.NoError(t, err)
// Watcher is inactive, thus 0
assert.Equal(t, 0, len(iws))

iws, err = GetIssueWatchers(2)
assert.NoError(t, err)
assert.Equal(t, 1, len(iws))

iws, err = GetIssueWatchers(5)
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ var migrations = []Migration{
NewMigration("adds time tracking and stopwatches", addTimetracking),
// v40 -> v41
NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
// v41 -> v42
NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
}

// Migrate database to current version
Expand Down
42 changes: 42 additions & 0 deletions models/migrations/v41.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"fmt"

"code.gitea.io/gitea/models"

"github.com/go-xorm/xorm"
)

func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
user := &models.User{
ProhibitLogin: false,
}

if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
return err
}

dialect := x.Dialect().DriverName()

switch dialect {
case "mysql":
_, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
case "postgres":
_, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
case "mssql":
// xorm already set DEFAULT 0 for data type BIT in mssql
_, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
case "sqlite3":
}

if err != nil {
return fmt.Errorf("Error changing user prohibit_login column definition: %v", err)
}

return err
}
5 changes: 2 additions & 3 deletions models/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {

assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2))

notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status)
notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
// Two watchers are inactive, thus only notification for user 10 is created
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 10, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status)
CheckConsistencyFor(t, &Issue{ID: issue.ID})
}
Expand Down
6 changes: 5 additions & 1 deletion models/repo_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {

func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
watches := make([]*Watch, 0, 10)
return watches, e.Find(&watches, &Watch{RepoID: repoID})
return watches, e.Where("`watch`.repo_id=?", repoID).
And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false).
Copy link
Member

Choose a reason for hiding this comment

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

I don't think user who is prohibit_login will be moved from watchers UI.

Copy link
Member Author

@daviian daviian Aug 25, 2017

Choose a reason for hiding this comment

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

The method is also used in

watches, err := getWatchers(e, issue.RepoID)

Perhaps I should add a second method or a parameter to add restriction conditionally?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, add additional parameter to function to know in to return all as it was or just active ones

Copy link
Member Author

Choose a reason for hiding this comment

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

@lunny @lafriks I figured out that the view isn't using this method anyways. The view is using the method from here https://github.com/daviian/gitea/blob/e3f679f39da7985193cec28a7c21fbc16ab2310d/models/repo_watch.go#L67

The method in question is only used by notification and mail

Copy link
Member Author

Choose a reason for hiding this comment

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

@lunny Any response?

Copy link
Member

Choose a reason for hiding this comment

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

@daviian OK.

Join("INNER", "user", "`user`.id = `watch`.user_id").
Find(&watches)
}

// GetWatchers returns all watchers of given repository.
Expand Down
14 changes: 5 additions & 9 deletions models/repo_watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ func TestGetWatchers(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
watches, err := GetWatchers(repo.ID)
assert.NoError(t, err)
assert.Len(t, watches, repo.NumWatches)
// Two watchers are inactive, thus minus 2
assert.Len(t, watches, repo.NumWatches-2)
for _, watch := range watches {
assert.EqualValues(t, repo.ID, watch.RepoID)
}
Expand Down Expand Up @@ -77,21 +78,16 @@ func TestNotifyWatchers(t *testing.T) {
}
assert.NoError(t, NotifyWatchers(action))

// Two watchers are inactive, thus action is only created for user 8, 10
AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID,
UserID: 1,
RepoID: action.RepoID,
OpType: action.OpType,
})
AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID,
UserID: 4,
UserID: 8,
RepoID: action.RepoID,
OpType: action.OpType,
})
AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID,
UserID: 8,
UserID: 10,
RepoID: action.RepoID,
OpType: action.OpType,
})
Expand Down
2 changes: 1 addition & 1 deletion models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type User struct {
AllowGitHook bool
AllowImportLocal bool // Allow migrate repository by local path
AllowCreateOrganization bool `xorm:"DEFAULT true"`
ProhibitLogin bool
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`

// Avatar
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
Expand Down