parent
5fdd30423e
commit
cc98737ca8
5 changed files with 156 additions and 87 deletions
|
@ -22,6 +22,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -315,19 +316,21 @@ func (a *Action) GetIssueContent() string {
|
||||||
|
|
||||||
// GetFeedsOptions options for retrieving feeds
|
// GetFeedsOptions options for retrieving feeds
|
||||||
type GetFeedsOptions struct {
|
type GetFeedsOptions struct {
|
||||||
RequestedUser *user_model.User // the user we want activity for
|
db.ListOptions
|
||||||
RequestedTeam *Team // the team we want activity for
|
RequestedUser *user_model.User // the user we want activity for
|
||||||
Actor *user_model.User // the user viewing the activity
|
RequestedTeam *Team // the team we want activity for
|
||||||
IncludePrivate bool // include private actions
|
RequestedRepo *repo_model.Repository // the repo we want activity for
|
||||||
OnlyPerformedBy bool // only actions performed by requested user
|
Actor *user_model.User // the user viewing the activity
|
||||||
IncludeDeleted bool // include deleted actions
|
IncludePrivate bool // include private actions
|
||||||
Date string // the day we want activity for: YYYY-MM-DD
|
OnlyPerformedBy bool // only actions performed by requested user
|
||||||
|
IncludeDeleted bool // include deleted actions
|
||||||
|
Date string // the day we want activity for: YYYY-MM-DD
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFeeds returns actions according to the provided options
|
// GetFeeds returns actions according to the provided options
|
||||||
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||||
if !activityReadable(opts.RequestedUser, opts.Actor) {
|
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
|
||||||
return make([]*Action, 0), nil
|
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
||||||
}
|
}
|
||||||
|
|
||||||
cond, err := activityQueryCondition(opts)
|
cond, err := activityQueryCondition(opts)
|
||||||
|
@ -335,9 +338,14 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actions := make([]*Action, 0, setting.UI.FeedPagingNum)
|
sess := db.GetEngine(db.DefaultContext).Where(cond)
|
||||||
|
|
||||||
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
|
opts.SetDefaultValues()
|
||||||
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
|
||||||
|
actions := make([]*Action, 0, opts.PageSize)
|
||||||
|
|
||||||
|
if err := sess.Desc("created_unix").Find(&actions); err != nil {
|
||||||
return nil, fmt.Errorf("Find: %v", err)
|
return nil, fmt.Errorf("Find: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,41 +357,44 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityReadable(user, doer *user_model.User) bool {
|
func activityReadable(user, doer *user_model.User) bool {
|
||||||
var doerID int64
|
return !user.KeepActivityPrivate ||
|
||||||
if doer != nil {
|
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
|
||||||
doerID = doer.ID
|
|
||||||
}
|
|
||||||
if doer == nil || !doer.IsAdmin {
|
|
||||||
if user.KeepActivityPrivate && doerID != user.ID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
|
||||||
var repoIDs []int64
|
if opts.RequestedTeam != nil && opts.RequestedUser == nil {
|
||||||
var actorID int64
|
org, err := user_model.GetUserByID(opts.RequestedTeam.OrgID)
|
||||||
if opts.Actor != nil {
|
if err != nil {
|
||||||
actorID = opts.Actor.ID
|
return nil, err
|
||||||
|
}
|
||||||
|
opts.RequestedUser = org
|
||||||
|
}
|
||||||
|
|
||||||
|
// check activity visibility for actor ( similar to activityReadable() )
|
||||||
|
if opts.Actor == nil {
|
||||||
|
cond = cond.And(builder.In("act_user_id",
|
||||||
|
builder.Select("`user`.id").Where(
|
||||||
|
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
|
||||||
|
).From("`user`"),
|
||||||
|
))
|
||||||
|
} else if !opts.Actor.IsAdmin {
|
||||||
|
cond = cond.And(builder.In("act_user_id",
|
||||||
|
builder.Select("`user`.id").Where(
|
||||||
|
builder.Eq{"keep_activity_private": false}.
|
||||||
|
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
|
||||||
|
Or(builder.Eq{"id": opts.Actor.ID}).From("`user`"),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// check readable repositories by doer/actor
|
// check readable repositories by doer/actor
|
||||||
if opts.Actor == nil || !opts.Actor.IsAdmin {
|
if opts.Actor == nil || !opts.Actor.IsAdmin {
|
||||||
if opts.RequestedUser.IsOrganization() {
|
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
|
||||||
env, err := OrgFromUser(opts.RequestedUser).AccessibleReposEnv(actorID)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
|
if opts.RequestedRepo != nil {
|
||||||
}
|
cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
|
||||||
if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUserRepositories: %v", err)
|
|
||||||
}
|
|
||||||
cond = cond.And(builder.In("repo_id", repoIDs))
|
|
||||||
} else {
|
|
||||||
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.RequestedTeam != nil {
|
if opts.RequestedTeam != nil {
|
||||||
|
@ -395,11 +406,14 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
||||||
cond = cond.And(builder.In("repo_id", teamRepoIDs))
|
cond = cond.And(builder.In("repo_id", teamRepoIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
|
if opts.RequestedUser != nil {
|
||||||
|
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
|
||||||
|
|
||||||
if opts.OnlyPerformedBy {
|
if opts.OnlyPerformedBy {
|
||||||
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.IncludePrivate {
|
if !opts.IncludePrivate {
|
||||||
cond = cond.And(builder.Eq{"is_private": false})
|
cond = cond.And(builder.Eq{"is_private": false})
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,46 @@ func TestGetFeeds2(t *testing.T) {
|
||||||
assert.Len(t, actions, 0)
|
assert.Len(t, actions, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActivityReadable(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
desc string
|
||||||
|
user *user_model.User
|
||||||
|
doer *user_model.User
|
||||||
|
result bool
|
||||||
|
}{{
|
||||||
|
desc: "user should see own activity",
|
||||||
|
user: &user_model.User{ID: 1},
|
||||||
|
doer: &user_model.User{ID: 1},
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
desc: "anon should see activity if public",
|
||||||
|
user: &user_model.User{ID: 1},
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
desc: "anon should NOT see activity",
|
||||||
|
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
desc: "user should see own activity if private too",
|
||||||
|
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||||
|
doer: &user_model.User{ID: 1},
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
desc: "other user should NOT see activity",
|
||||||
|
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||||
|
doer: &user_model.User{ID: 2},
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
desc: "admin should see activity",
|
||||||
|
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||||
|
doer: &user_model.User{ID: 2, IsAdmin: true},
|
||||||
|
result: true,
|
||||||
|
}}
|
||||||
|
for _, test := range tt {
|
||||||
|
assert.Equal(t, test.result, activityReadable(test.user, test.doer), test.desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNotifyWatchers(t *testing.T) {
|
func TestNotifyWatchers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -19,25 +19,40 @@ import (
|
||||||
|
|
||||||
func TestGetUserHeatmapDataByUser(t *testing.T) {
|
func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
userID int64
|
userID int64
|
||||||
doerID int64
|
doerID int64
|
||||||
CountResult int
|
CountResult int
|
||||||
JSONResult string
|
JSONResult string
|
||||||
}{
|
}{
|
||||||
// self looks at action in private repo
|
{
|
||||||
{2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`},
|
"self looks at action in private repo",
|
||||||
// admin looks at action in private repo
|
2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`,
|
||||||
{2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`},
|
},
|
||||||
// other user looks at action in private repo
|
{
|
||||||
{2, 3, 0, `[]`},
|
"admin looks at action in private repo",
|
||||||
// nobody looks at action in private repo
|
2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`,
|
||||||
{2, 0, 0, `[]`},
|
},
|
||||||
// collaborator looks at action in private repo
|
{
|
||||||
{16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`},
|
"other user looks at action in private repo",
|
||||||
// no action action not performed by target user
|
2, 3, 0, `[]`,
|
||||||
{3, 3, 0, `[]`},
|
},
|
||||||
// multiple actions performed with two grouped together
|
{
|
||||||
{10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`},
|
"nobody looks at action in private repo",
|
||||||
|
2, 0, 0, `[]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collaborator looks at action in private repo",
|
||||||
|
16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no action action not performed by target user",
|
||||||
|
3, 3, 0, `[]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple actions performed with two grouped together",
|
||||||
|
10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
// Prepare
|
// Prepare
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
@ -46,7 +61,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||||
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
defer timeutil.Unset()
|
defer timeutil.Unset()
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}).(*user_model.User)
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}).(*user_model.User)
|
||||||
|
|
||||||
doer := &user_model.User{ID: tc.doerID}
|
doer := &user_model.User{ID: tc.doerID}
|
||||||
|
@ -74,7 +89,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
|
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
|
||||||
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase %d", i))
|
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc))
|
||||||
|
|
||||||
// Test JSON rendering
|
// Test JSON rendering
|
||||||
jsonData, err := json.Marshal(heatmap)
|
jsonData, err := json.Marshal(heatmap)
|
||||||
|
|
|
@ -23,31 +23,34 @@ func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*mode
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
|
// TODO: move load repoOwner of act.Repo into models.GetFeeds->loadAttributes()
|
||||||
if ctx.User != nil {
|
{
|
||||||
userCache[ctx.User.ID] = ctx.User
|
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
|
||||||
}
|
if ctx.User != nil {
|
||||||
for _, act := range actions {
|
userCache[ctx.User.ID] = ctx.User
|
||||||
if act.ActUser != nil {
|
}
|
||||||
userCache[act.ActUserID] = act.ActUser
|
for _, act := range actions {
|
||||||
|
if act.ActUser != nil {
|
||||||
|
userCache[act.ActUserID] = act.ActUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, act := range actions {
|
||||||
|
repoOwner, ok := userCache[act.Repo.OwnerID]
|
||||||
|
if !ok {
|
||||||
|
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx.ServerError("GetUserByID", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
userCache[repoOwner.ID] = repoOwner
|
||||||
|
}
|
||||||
|
act.Repo.Owner = repoOwner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, act := range actions {
|
|
||||||
repoOwner, ok := userCache[act.Repo.OwnerID]
|
|
||||||
if !ok {
|
|
||||||
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
|
|
||||||
if err != nil {
|
|
||||||
if user_model.IsErrUserNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ctx.ServerError("GetUserByID", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
userCache[repoOwner.ID] = repoOwner
|
|
||||||
}
|
|
||||||
act.Repo.Owner = repoOwner
|
|
||||||
}
|
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +60,7 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
|
||||||
RequestedUser: ctxUser,
|
RequestedUser: ctxUser,
|
||||||
Actor: ctx.User,
|
Actor: ctx.User,
|
||||||
IncludePrivate: false,
|
IncludePrivate: false,
|
||||||
OnlyPerformedBy: true,
|
OnlyPerformedBy: !ctxUser.IsOrganization(),
|
||||||
IncludeDeleted: false,
|
IncludeDeleted: false,
|
||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
})
|
})
|
||||||
|
|
|
@ -94,14 +94,11 @@ func Profile(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctxUser.IsOrganization() {
|
if ctxUser.IsOrganization() {
|
||||||
/*
|
// Show Org RSS feed
|
||||||
// TODO: enable after rss.RetrieveFeeds() do handle org correctly
|
if len(showFeedType) != 0 {
|
||||||
// Show Org RSS feed
|
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
||||||
if len(showFeedType) != 0 {
|
return
|
||||||
rss.ShowUserFeed(ctx, ctxUser, showFeedType)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
org.Home(ctx)
|
org.Home(ctx)
|
||||||
return
|
return
|
||||||
|
|
Reference in a new issue