From f4f4e18b14c3d772e9183e8f1d2b2df45712c496 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 May 2024 21:47:03 +0800 Subject: [PATCH] Filter out duplicate action(activity) items for a repository (#30957) Fix #20986 --- models/activities/action.go | 11 ++++++++++- models/activities/action_test.go | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/models/activities/action.go b/models/activities/action.go index 7e2ef4c9ae..d23f2bd986 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -524,7 +524,12 @@ func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder. } if opts.RequestedRepo != nil { - cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID}) + // repo's actions could have duplicate items, see the comment of NotifyWatchers + // so here we only filter the "original items", aka: user_id == act_user_id + cond = cond.And( + builder.Eq{"`action`.repo_id": opts.RequestedRepo.ID}, + builder.Expr("`action`.user_id = `action`.act_user_id"), + ) } if opts.RequestedTeam != nil { @@ -577,6 +582,10 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) } // NotifyWatchers creates batch of actions for every watcher. +// It could insert duplicate actions for a repository action, like this: +// * Original action: UserID=1 (the real actor), ActUserID=1 +// * Organization action: UserID=100 (the repo's org), ActUserID=1 +// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1 func NotifyWatchers(ctx context.Context, actions ...*Action) error { var watchers []*repo_model.Watch var repo *repo_model.Repository diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 5467bd35fb..557415dcda 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -318,3 +318,24 @@ func TestDeleteIssueActions(t *testing.T) { assert.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index)) unittest.AssertCount(t, &activities_model.Action{}, 0) } + +func TestRepoActions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + _ = db.TruncateBeans(db.DefaultContext, &activities_model.Action{}) + for i := 0; i < 3; i++ { + _ = db.Insert(db.DefaultContext, &activities_model.Action{ + UserID: 2 + int64(i), + ActUserID: 2, + RepoID: repo.ID, + OpType: activities_model.ActionCommentIssue, + }) + } + count, _ := db.Count[activities_model.Action](db.DefaultContext, &db.ListOptions{}) + assert.EqualValues(t, 3, count) + actions, _, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ + RequestedRepo: repo, + }) + assert.NoError(t, err) + assert.Len(t, actions, 1) +}