aboutsummaryrefslogtreecommitdiffstats
path: root/models/activities
diff options
context:
space:
mode:
Diffstat (limited to 'models/activities')
-rw-r--r--models/activities/action.go192
-rw-r--r--models/activities/action_list.go55
-rw-r--r--models/activities/action_test.go39
-rw-r--r--models/activities/notification_list.go20
-rw-r--r--models/activities/notification_test.go12
-rw-r--r--models/activities/repo_activity.go5
-rw-r--r--models/activities/statistic.go21
-rw-r--r--models/activities/user_heatmap.go2
8 files changed, 112 insertions, 234 deletions
diff --git a/models/activities/action.go b/models/activities/action.go
index f96621b7d5..1a0dfe6412 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -9,6 +9,7 @@ import (
"fmt"
"net/url"
"path"
+ "slices"
"strconv"
"strings"
"time"
@@ -16,9 +17,7 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
- access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -72,9 +71,9 @@ func (at ActionType) String() string {
case ActionRenameRepo:
return "rename_repo"
case ActionStarRepo:
- return "star_repo"
+ return "star_repo" // will not displayed in feeds.tmpl
case ActionWatchRepo:
- return "watch_repo"
+ return "watch_repo" // will not displayed in feeds.tmpl
case ActionCommitRepo:
return "commit_repo"
case ActionCreateIssue:
@@ -127,12 +126,7 @@ func (at ActionType) String() string {
}
func (at ActionType) InActions(actions ...string) bool {
- for _, action := range actions {
- if action == at.String() {
- return true
- }
- }
- return false
+ return slices.Contains(actions, at.String())
}
// Action represents user operation type and other information to
@@ -174,7 +168,10 @@ func (a *Action) TableIndices() []*schemas.Index {
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
cuIndex.AddColumn("user_id", "is_deleted")
- indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex}
+ actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
+ actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
+
+ indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
return indices
}
@@ -190,7 +187,7 @@ func (a *Action) LoadActUser(ctx context.Context) {
return
}
var err error
- a.ActUser, err = user_model.GetUserByID(ctx, a.ActUserID)
+ a.ActUser, err = user_model.GetPossibleUserByID(ctx, a.ActUserID)
if err == nil {
return
} else if user_model.IsErrUserNotExist(err) {
@@ -200,15 +197,13 @@ func (a *Action) LoadActUser(ctx context.Context) {
}
}
-func (a *Action) LoadRepo(ctx context.Context) {
+func (a *Action) LoadRepo(ctx context.Context) error {
if a.Repo != nil {
- return
+ return nil
}
var err error
a.Repo, err = repo_model.GetRepositoryByID(ctx, a.RepoID)
- if err != nil {
- log.Error("repo_model.GetRepositoryByID(%d): %v", a.RepoID, err)
- }
+ return err
}
// GetActFullName gets the action's user full name.
@@ -250,7 +245,7 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
// GetRepoUserName returns the name of the action repository owner.
func (a *Action) GetRepoUserName(ctx context.Context) string {
- a.LoadRepo(ctx)
+ _ = a.LoadRepo(ctx)
if a.Repo == nil {
return "(non-existing-repo)"
}
@@ -265,7 +260,7 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
// GetRepoName returns the name of the action repository.
func (a *Action) GetRepoName(ctx context.Context) string {
- a.LoadRepo(ctx)
+ _ = a.LoadRepo(ctx)
if a.Repo == nil {
return "(non-existing-repo)"
}
@@ -446,6 +441,7 @@ type GetFeedsOptions struct {
OnlyPerformedBy bool // only actions performed by requested user
IncludeDeleted bool // include deleted actions
Date string // the day we want activity for: YYYY-MM-DD
+ DontCount bool // do counting in GetFeeds
}
// ActivityReadable return whether doer can read activities of user
@@ -454,6 +450,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
}
+func FeedDateCond(opts GetFeedsOptions) builder.Cond {
+ cond := builder.NewCond()
+ if opts.Date == "" {
+ return cond
+ }
+
+ dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
+ if err != nil {
+ log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
+ } else {
+ dateHigh := dateLow.Add(86399000000000) // 23h59m59s
+
+ cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
+ cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
+ }
+ return cond
+}
+
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
cond := builder.NewCond()
@@ -511,8 +525,8 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
}
if opts.RequestedTeam != nil {
- env := repo_model.AccessibleTeamReposEnv(ctx, organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
- teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
+ env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
+ teamRepoIDs, err := env.RepoIDs(ctx)
if err != nil {
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
}
@@ -534,17 +548,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
cond = cond.And(builder.Eq{"is_deleted": false})
}
- if opts.Date != "" {
- dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
- if err != nil {
- log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
- } else {
- dateHigh := dateLow.Add(86399000000000) // 23h59m59s
-
- cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
- cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
- }
- }
+ cond = cond.And(FeedDateCond(opts))
return cond, nil
}
@@ -559,130 +563,6 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error)
return err
}
-// 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
- var err error
- var permCode []bool
- var permIssue []bool
- var permPR []bool
-
- e := db.GetEngine(ctx)
-
- for _, act := range actions {
- repoChanged := repo == nil || repo.ID != act.RepoID
-
- if repoChanged {
- // Add feeds for user self and all watchers.
- watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
- if err != nil {
- return fmt.Errorf("get watchers: %w", err)
- }
- }
-
- // Add feed for actioner.
- act.UserID = act.ActUserID
- if _, err = e.Insert(act); err != nil {
- return fmt.Errorf("insert new actioner: %w", err)
- }
-
- if repoChanged {
- act.LoadRepo(ctx)
- repo = act.Repo
-
- // check repo owner exist.
- if err := act.Repo.LoadOwner(ctx); err != nil {
- return fmt.Errorf("can't get repo owner: %w", err)
- }
- } else if act.Repo == nil {
- act.Repo = repo
- }
-
- // Add feed for organization
- if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID {
- act.ID = 0
- act.UserID = act.Repo.Owner.ID
- if err = db.Insert(ctx, act); err != nil {
- return fmt.Errorf("insert new actioner: %w", err)
- }
- }
-
- if repoChanged {
- permCode = make([]bool, len(watchers))
- permIssue = make([]bool, len(watchers))
- permPR = make([]bool, len(watchers))
- for i, watcher := range watchers {
- user, err := user_model.GetUserByID(ctx, watcher.UserID)
- if err != nil {
- permCode[i] = false
- permIssue[i] = false
- permPR[i] = false
- continue
- }
- perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
- if err != nil {
- permCode[i] = false
- permIssue[i] = false
- permPR[i] = false
- continue
- }
- permCode[i] = perm.CanRead(unit.TypeCode)
- permIssue[i] = perm.CanRead(unit.TypeIssues)
- permPR[i] = perm.CanRead(unit.TypePullRequests)
- }
- }
-
- for i, watcher := range watchers {
- if act.ActUserID == watcher.UserID {
- continue
- }
- act.ID = 0
- act.UserID = watcher.UserID
- act.Repo.Units = nil
-
- switch act.OpType {
- case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch:
- if !permCode[i] {
- continue
- }
- case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
- if !permIssue[i] {
- continue
- }
- case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest, ActionAutoMergePullRequest:
- if !permPR[i] {
- continue
- }
- }
-
- if err = db.Insert(ctx, act); err != nil {
- return fmt.Errorf("insert new action: %w", err)
- }
- }
- }
- return nil
-}
-
-// NotifyWatchersActions creates batch of actions for every watcher.
-func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- for _, act := range acts {
- if err := NotifyWatchers(ctx, act); err != nil {
- return err
- }
- }
- return committer.Commit()
-}
-
// DeleteIssueActions delete all actions related with issueID
func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error {
// delete actions assigned to this issue
diff --git a/models/activities/action_list.go b/models/activities/action_list.go
index 5f9acb8f2a..b52cf7ee49 100644
--- a/models/activities/action_list.go
+++ b/models/activities/action_list.go
@@ -5,6 +5,7 @@ package activities
import (
"context"
+ "errors"
"fmt"
"strconv"
@@ -205,12 +206,34 @@ func (actions ActionList) LoadIssues(ctx context.Context) error {
// GetFeeds returns actions according to the provided options
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) {
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
- return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
+ return nil, 0, errors.New("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
}
- cond, err := ActivityQueryCondition(ctx, opts)
- if err != nil {
- return nil, 0, err
+ var err error
+ var cond builder.Cond
+ // if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
+ if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
+ cond = builder.Eq{
+ "user_id": opts.RequestedUser.ID,
+ }.And(
+ FeedDateCond(opts),
+ )
+
+ if !opts.IncludeDeleted {
+ cond = cond.And(builder.Eq{"is_deleted": false})
+ }
+
+ if !opts.IncludePrivate {
+ cond = cond.And(builder.Eq{"is_private": false})
+ }
+ if opts.OnlyPerformedBy {
+ cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
+ }
+ } else {
+ cond, err = ActivityQueryCondition(ctx, opts)
+ if err != nil {
+ return nil, 0, err
+ }
}
actions := make([]*Action, 0, opts.PageSize)
@@ -221,7 +244,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
sess := db.GetEngine(ctx).Where(cond)
sess = db.SetSessionPagination(sess, &opts)
- count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
+ if opts.DontCount {
+ err = sess.Desc("`action`.created_unix").Find(&actions)
+ } else {
+ count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
+ }
if err != nil {
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
}
@@ -235,11 +262,13 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, fmt.Errorf("Find(actionsIDs): %w", err)
}
- count, err = db.GetEngine(ctx).Where(cond).
- Table("action").
- Cols("`action`.id").Count()
- if err != nil {
- return nil, 0, fmt.Errorf("Count: %w", err)
+ if !opts.DontCount {
+ count, err = db.GetEngine(ctx).Where(cond).
+ Table("action").
+ Cols("`action`.id").Count()
+ if err != nil {
+ return nil, 0, fmt.Errorf("Count: %w", err)
+ }
}
if err := db.GetEngine(ctx).In("`action`.id", actionIDs).Desc("`action`.created_unix").Find(&actions); err != nil {
@@ -253,3 +282,9 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return actions, count, nil
}
+
+func CountUserFeeds(ctx context.Context, userID int64) (int64, error) {
+ return db.GetEngine(ctx).Where("user_id = ?", userID).
+ And("is_deleted = ?", false).
+ Count(&Action{})
+}
diff --git a/models/activities/action_test.go b/models/activities/action_test.go
index 9cfe981656..ff311ac891 100644
--- a/models/activities/action_test.go
+++ b/models/activities/action_test.go
@@ -82,43 +82,6 @@ func TestActivityReadable(t *testing.T) {
}
}
-func TestNotifyWatchers(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- action := &activities_model.Action{
- ActUserID: 8,
- RepoID: 1,
- OpType: activities_model.ActionStarRepo,
- }
- assert.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action))
-
- // One watchers are inactive, thus action is only created for user 8, 1, 4, 11
- unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
- ActUserID: action.ActUserID,
- UserID: 8,
- RepoID: action.RepoID,
- OpType: action.OpType,
- })
- unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
- ActUserID: action.ActUserID,
- UserID: 1,
- RepoID: action.RepoID,
- OpType: action.OpType,
- })
- unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
- ActUserID: action.ActUserID,
- UserID: 4,
- RepoID: action.RepoID,
- OpType: action.OpType,
- })
- unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
- ActUserID: action.ActUserID,
- UserID: 11,
- RepoID: action.RepoID,
- OpType: action.OpType,
- })
-}
-
func TestConsistencyUpdateAction(t *testing.T) {
if !setting.Database.Type.IsSQLite3() {
t.Skip("Test is only for SQLite database.")
@@ -167,7 +130,7 @@ func TestDeleteIssueActions(t *testing.T) {
// load an issue
issue := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{ID: 4})
- assert.NotEqualValues(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex
+ assert.NotEqual(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex
// insert a comment
err := db.Insert(db.DefaultContext, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID})
diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go
index 0cbb91df3c..b47f5dc404 100644
--- a/models/activities/notification_list.go
+++ b/models/activities/notification_list.go
@@ -208,10 +208,7 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Rows(new(repo_model.Repository))
@@ -282,10 +279,7 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(issues_model.Issue))
@@ -377,10 +371,7 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
users := make(map[int64]*user_model.User, len(userIDs))
left := len(userIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", userIDs[:limit]).
Rows(new(user_model.User))
@@ -428,10 +419,7 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
left := len(commentIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", commentIDs[:limit]).
Rows(new(issues_model.Comment))
diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go
index 52f0eacba1..5d2a29bc36 100644
--- a/models/activities/notification_test.go
+++ b/models/activities/notification_test.go
@@ -44,11 +44,11 @@ func TestNotificationsForUser(t *testing.T) {
assert.NoError(t, err)
if assert.Len(t, notfs, 3) {
assert.EqualValues(t, 5, notfs[0].ID)
- assert.EqualValues(t, user.ID, notfs[0].UserID)
+ assert.Equal(t, user.ID, notfs[0].UserID)
assert.EqualValues(t, 4, notfs[1].ID)
- assert.EqualValues(t, user.ID, notfs[1].UserID)
+ assert.Equal(t, user.ID, notfs[1].UserID)
assert.EqualValues(t, 2, notfs[2].ID)
- assert.EqualValues(t, user.ID, notfs[2].UserID)
+ assert.Equal(t, user.ID, notfs[2].UserID)
}
}
@@ -58,7 +58,7 @@ func TestNotification_GetRepo(t *testing.T) {
repo, err := notf.GetRepo(db.DefaultContext)
assert.NoError(t, err)
assert.Equal(t, repo, notf.Repository)
- assert.EqualValues(t, notf.RepoID, repo.ID)
+ assert.Equal(t, notf.RepoID, repo.ID)
}
func TestNotification_GetIssue(t *testing.T) {
@@ -67,7 +67,7 @@ func TestNotification_GetIssue(t *testing.T) {
issue, err := notf.GetIssue(db.DefaultContext)
assert.NoError(t, err)
assert.Equal(t, issue, notf.Issue)
- assert.EqualValues(t, notf.IssueID, issue.ID)
+ assert.Equal(t, notf.IssueID, issue.ID)
}
func TestGetNotificationCount(t *testing.T) {
@@ -136,5 +136,5 @@ func TestSetIssueReadBy(t *testing.T) {
nt, err := activities_model.GetIssueNotification(db.DefaultContext, user.ID, issue.ID)
assert.NoError(t, err)
- assert.EqualValues(t, activities_model.NotificationStatusRead, nt.Status)
+ assert.Equal(t, activities_model.NotificationStatusRead, nt.Status)
}
diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go
index 3ccdbd47d3..aeaa452c9e 100644
--- a/models/activities/repo_activity.go
+++ b/models/activities/repo_activity.go
@@ -139,10 +139,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository
return v[i].Commits > v[j].Commits
})
- cnt := count
- if cnt > len(v) {
- cnt = len(v)
- }
+ cnt := min(count, len(v))
return v[:cnt], nil
}
diff --git a/models/activities/statistic.go b/models/activities/statistic.go
index ff81ad78a1..940651d359 100644
--- a/models/activities/statistic.go
+++ b/models/activities/statistic.go
@@ -17,13 +17,16 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
)
// Statistic contains the database statistics
type Statistic struct {
Counter struct {
- User, Org, PublicKey,
+ UsersActive, UsersNotActive,
+ Org, PublicKey,
Repo, Watch, Star, Access,
Issue, IssueClosed, IssueOpen,
Comment, Oauth, Follow,
@@ -53,8 +56,20 @@ type IssueByRepositoryCount struct {
// GetStatistic returns the database statistics
func GetStatistic(ctx context.Context) (stats Statistic) {
e := db.GetEngine(ctx)
- stats.Counter.User = user_model.CountUsers(ctx, nil)
- stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludePrivate: true})
+
+ // Number of active users
+ usersActiveOpts := user_model.CountUserFilter{
+ IsActive: optional.Some(true),
+ }
+ stats.Counter.UsersActive = user_model.CountUsers(ctx, &usersActiveOpts)
+
+ // Number of inactive users
+ usersNotActiveOpts := user_model.CountUserFilter{
+ IsActive: optional.Some(false),
+ }
+ stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts)
+
+ stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate})
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{})
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go
index 1f8f0f590e..ef67838be7 100644
--- a/models/activities/user_heatmap.go
+++ b/models/activities/user_heatmap.go
@@ -66,7 +66,7 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi
Select(groupBy+" AS timestamp, count(user_id) as contributions").
Table("action").
Where(cond).
- And("created_unix > ?", timeutil.TimeStampNow()-31536000).
+ And("created_unix > ?", timeutil.TimeStampNow()-(366+7)*86400). // (366+7) days to include the first week for the heatmap
GroupBy(groupByName).
OrderBy("timestamp").
Find(&hdata)