Browse Source

Fix user profile activity feed (#1848)

* Fix user profile activity feed

* gofmt, and avoid overlapping database connections
tags/v1.2.0-rc1
Ethan Koenig 7 years ago
parent
commit
4e5ee2b67a
5 changed files with 74 additions and 48 deletions
  1. 28
    22
      models/action.go
  2. 33
    12
      models/action_test.go
  3. 1
    1
      models/fixtures/action.yml
  4. 11
    12
      routers/user/home.go
  5. 1
    1
      routers/user/profile.go

+ 28
- 22
models/action.go View File

return mergePullRequestAction(x, actUser, repo, pull) return mergePullRequestAction(x, actUser, repo, pull)
} }


// GetFeeds returns action list of given user in given context.
// actorID is the user who's requesting, ctxUserID is the user/org that is requested.
// actorID can be -1 when isProfile is true or to skip the permission check.
func GetFeeds(ctxUser *User, actorID, offset int64, isProfile bool) ([]*Action, error) {
actions := make([]*Action, 0, 20)
sess := x.
Limit(20, int(offset)).
Desc("id").
Where("user_id = ?", ctxUser.ID)
if isProfile {
sess.
And("is_private = ?", false).
And("act_user_id = ?", ctxUser.ID)
} else if actorID != -1 && ctxUser.IsOrganization() {
env, err := ctxUser.AccessibleReposEnv(actorID)
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
RequestedUser *User
RequestingUserID int64
IncludePrivate bool // include private actions
OnlyPerformedBy bool // only actions performed by requested user
}

// GetFeeds returns actions according to the provided options
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
var repoIDs []int64
if opts.RequestedUser.IsOrganization() {
env, err := opts.RequestedUser.AccessibleReposEnv(opts.RequestingUserID)
if err != nil { if err != nil {
return nil, fmt.Errorf("AccessibleReposEnv: %v", err) return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
} }
repoIDs, err := env.RepoIDs(1, ctxUser.NumRepos)
if err != nil {
if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
return nil, fmt.Errorf("GetUserRepositories: %v", err) return nil, fmt.Errorf("GetUserRepositories: %v", err)
} }
if len(repoIDs) > 0 {
sess.In("repo_id", repoIDs)
}
} }


err := sess.Find(&actions)
return actions, err
actions := make([]*Action, 0, 20)
sess := x.Limit(20).
Desc("id").
Where("user_id = ?", opts.RequestedUser.ID)
if opts.OnlyPerformedBy {
sess.And("act_user_id = ?", opts.RequestedUser.ID)
}
if !opts.IncludePrivate {
sess.And("is_private = ?", false)
}
if opts.RequestedUser.IsOrganization() {
sess.In("repo_id", repoIDs)
}
return actions, sess.Find(&actions)
} }

+ 33
- 12
models/action_test.go View File

assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)


actions, err := GetFeeds(user, user.ID, 0, false)
actions, err := GetFeeds(GetFeedsOptions{
RequestedUser: user,
RequestingUserID: user.ID,
IncludePrivate: true,
OnlyPerformedBy: false,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, 1) assert.Len(t, actions, 1)
assert.Equal(t, int64(1), actions[0].ID)
assert.Equal(t, user.ID, actions[0].UserID)

actions, err = GetFeeds(user, user.ID, 0, true)
assert.EqualValues(t, 1, actions[0].ID)
assert.EqualValues(t, user.ID, actions[0].UserID)

actions, err = GetFeeds(GetFeedsOptions{
RequestedUser: user,
RequestingUserID: user.ID,
IncludePrivate: false,
OnlyPerformedBy: false,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, 0) assert.Len(t, actions, 0)
} }
func TestGetFeeds2(t *testing.T) { func TestGetFeeds2(t *testing.T) {
// test with an organization user // test with an organization user
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)

actions, err := GetFeeds(user, user.ID, 0, false)
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
userID := AssertExistsAndLoadBean(t, &OrgUser{OrgID: org.ID, IsOwner: true}).(*OrgUser).UID

actions, err := GetFeeds(GetFeedsOptions{
RequestedUser: org,
RequestingUserID: userID,
IncludePrivate: true,
OnlyPerformedBy: false,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, 1) assert.Len(t, actions, 1)
assert.Equal(t, int64(2), actions[0].ID)
assert.Equal(t, user.ID, actions[0].UserID)

actions, err = GetFeeds(user, user.ID, 0, true)
assert.EqualValues(t, 2, actions[0].ID)
assert.EqualValues(t, org.ID, actions[0].UserID)

actions, err = GetFeeds(GetFeedsOptions{
RequestedUser: org,
RequestingUserID: userID,
IncludePrivate: false,
OnlyPerformedBy: false,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, 0) assert.Len(t, actions, 0)
} }

+ 1
- 1
models/fixtures/action.yml View File

id: 2 id: 2
user_id: 3 user_id: 3
op_type: 2 # rename repo op_type: 2 # rename repo
act_user_id: 3
act_user_id: 2
repo_id: 3 repo_id: 3
is_private: true is_private: true
content: oldRepoName content: oldRepoName

+ 11
- 12
routers/user/home.go View File

return ctxUser return ctxUser
} }


// retrieveFeeds loads feeds from database by given context user.
// The user could be organization so it is not always the logged in user,
// which is why we have to explicitly pass the context user ID.
func retrieveFeeds(ctx *context.Context, ctxUser *models.User, userID, offset int64, isProfile bool) {
actions, err := models.GetFeeds(ctxUser, userID, offset, isProfile)
// retrieveFeeds loads feeds for the specified user
func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isProfile bool) {
actions, err := models.GetFeeds(models.GetFeedsOptions{
RequestedUser: user,
RequestingUserID: ctx.User.ID,
IncludePrivate: includePrivate,
OnlyPerformedBy: isProfile,
})
if err != nil { if err != nil {
ctx.Handle(500, "GetFeeds", err) ctx.Handle(500, "GetFeeds", err)
return return
} }


// Check access of private repositories.
feeds := make([]*models.Action, 0, len(actions))
userCache := map[int64]*models.User{ctxUser.ID: ctxUser}
userCache := map[int64]*models.User{user.ID: user}
repoCache := map[int64]*models.Repository{} repoCache := map[int64]*models.Repository{}
for _, act := range actions { for _, act := range actions {
// Cache results to reduce queries. // Cache results to reduce queries.
} }
} }
repo.Owner = repoOwner repo.Owner = repoOwner

feeds = append(feeds, act)
} }
ctx.Data["Feeds"] = feeds
ctx.Data["Feeds"] = actions
} }


// Dashboard render the dashborad page // Dashboard render the dashborad page
ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["MirrorCount"] = len(mirrors)
ctx.Data["Mirrors"] = mirrors ctx.Data["Mirrors"] = mirrors


retrieveFeeds(ctx, ctxUser, ctx.User.ID, 0, false)
retrieveFeeds(ctx, ctxUser, true, false)
if ctx.Written() { if ctx.Written() {
return return
} }

+ 1
- 1
routers/user/profile.go View File

ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword
switch tab { switch tab {
case "activity": case "activity":
retrieveFeeds(ctx, ctxUser, -1, 0, !showPrivate)
retrieveFeeds(ctx, ctxUser, showPrivate, true)
if ctx.Written() { if ctx.Written() {
return return
} }

Loading…
Cancel
Save