diff --git a/models/models.go b/models/models.go index eee29d2c7..3927dfbf0 100755 --- a/models/models.go +++ b/models/models.go @@ -136,7 +136,9 @@ func init() { ) tablesStatistic = append(tablesStatistic, - new(FileChunk)) + new(FileChunk), + new(UserBusinessAnalysis), + ) gonicNames := []string{"SSL", "UID"} for _, name := range gonicNames { diff --git a/models/repo_watch.go b/models/repo_watch.go index 11cfa8891..6bdef9c7f 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -26,10 +26,11 @@ const ( // Watch is connection request for receiving repository notification. type Watch struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` - Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(watch)"` + RepoID int64 `xorm:"UNIQUE(watch)"` + Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + CreatedUnix int64 `xorm:"created"` } // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found diff --git a/models/star.go b/models/star.go index 4e84a6e4d..418a76b17 100644 --- a/models/star.go +++ b/models/star.go @@ -4,11 +4,14 @@ package models +import "code.gitea.io/gitea/modules/timeutil" + // Star represents a starred repo by an user. type Star struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } // StarRepo or unstar repository. @@ -39,7 +42,7 @@ func StarRepo(userID, repoID int64, star bool) error { return nil } - if _, err := sess.Delete(&Star{0, userID, repoID}); err != nil { + if _, err := sess.Delete(&Star{0, userID, repoID, 0}); err != nil { return err } if _, err := sess.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil { @@ -59,7 +62,7 @@ func IsStaring(userID, repoID int64) bool { } func isStaring(e Engine, userID, repoID int64) bool { - has, _ := e.Get(&Star{0, userID, repoID}) + has, _ := e.Get(&Star{0, userID, repoID, 0}) return has } diff --git a/models/user_business_analysis.go b/models/user_business_analysis.go new file mode 100644 index 000000000..9da0694a5 --- /dev/null +++ b/models/user_business_analysis.go @@ -0,0 +1,356 @@ +package models + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" +) + +type UserBusinessAnalysis struct { + ID int64 `xorm:"pk"` + + CountDate int64 `xorm:"pk"` + + //action :ActionMergePullRequest // 11 + CodeMergeCount int `xorm:"NOT NULL DEFAULT 0"` + + //action :ActionCommitRepo // 5 + CommitCount int `xorm:"NOT NULL DEFAULT 0"` + + //action :ActionCommentIssue // 10 + IssueCount int `xorm:"NOT NULL DEFAULT 0"` + + //comment table current date + CommentCount int `xorm:"NOT NULL DEFAULT 0"` + + //watch table current date + FocusRepoCount int `xorm:"NOT NULL DEFAULT 0"` + + //star table current date + StarRepoCount int `xorm:"NOT NULL DEFAULT 0"` + + //follow table + WatchedCount int `xorm:"NOT NULL DEFAULT 0"` + + // user table + GiteaAgeMonth int `xorm:"NOT NULL DEFAULT 0"` + + // + CommitCodeSize int `xorm:"NOT NULL DEFAULT 0"` + + //attachement table + CommitDatasetSize int `xorm:"NOT NULL DEFAULT 0"` + + //0 + CommitModelCount int `xorm:"NOT NULL DEFAULT 0"` + + //issue, issueassignees + SolveIssueCount int `xorm:"NOT NULL DEFAULT 0"` + + //baike + EncyclopediasCount int `xorm:"NOT NULL DEFAULT 0"` + + //user + RegistDate timeutil.TimeStamp `xorm:"NOT NULL"` + + //user + Email string `xorm:"NOT NULL"` + + //user + Name string `xorm:"NOT NULL"` +} + +func CountData(wikiCountMap map[string]int) { + log.Info("start to count other user info data") + sess := x.NewSession() + defer sess.Close() + sess.Select("`user`.*").Table("user") + userList := make([]*User, 0) + sess.Find(&userList) + + currentTimeNow := time.Now() + log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05")) + + yesterday := currentTimeNow.AddDate(0, 0, -1) + startTime := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location()) + start_unix := startTime.Unix() + log.Info("DB query time:" + startTime.Format("2006-01-02 15:04:05")) + + endTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + end_unix := endTime.Unix() + + CountDate := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 1, 0, 0, currentTimeNow.Location()) + + CodeMergeCountMap := queryAction(start_unix, end_unix, 11) + CommitCountMap := queryAction(start_unix, end_unix, 5) + IssueCountMap := queryAction(start_unix, end_unix, 10) + + CommentCountMap := queryComment(start_unix, end_unix) + FocusRepoCountMap := queryWatch(start_unix, end_unix) + StarRepoCountMap := queryStar(start_unix, end_unix) + WatchedCountMap := queryFollow(start_unix, end_unix) + + CommitCodeSizeMap, err := GetAllUserKPIStats() + if err != nil { + log.Info("query commit code errr.") + } else { + log.Info("query commit code size, len=" + fmt.Sprint(len(CommitCodeSizeMap))) + } + CommitDatasetSizeMap := queryDatasetSize(start_unix, end_unix) + SolveIssueCountMap := querySolveIssue(start_unix, end_unix) + + for i, userRecord := range userList { + var dateRecord UserBusinessAnalysis + dateRecord.ID = userRecord.ID + log.Info("i=" + fmt.Sprint(i) + " userName=" + userRecord.Name) + dateRecord.CountDate = CountDate.Unix() + dateRecord.Email = userRecord.Email + dateRecord.RegistDate = userRecord.CreatedUnix + dateRecord.Name = userRecord.Name + dateRecord.GiteaAgeMonth = subMonth(currentTimeNow, userRecord.CreatedUnix.AsTime()) + if _, ok := CodeMergeCountMap[dateRecord.ID]; !ok { + dateRecord.CodeMergeCount = 0 + } else { + dateRecord.CodeMergeCount = CodeMergeCountMap[dateRecord.ID] + } + + if _, ok := CommitCountMap[dateRecord.ID]; !ok { + dateRecord.CommitCount = 0 + } else { + dateRecord.CommitCount = CommitCountMap[dateRecord.ID] + } + + if _, ok := IssueCountMap[dateRecord.ID]; !ok { + dateRecord.IssueCount = 0 + } else { + dateRecord.IssueCount = IssueCountMap[dateRecord.ID] + } + + if _, ok := CommentCountMap[dateRecord.ID]; !ok { + dateRecord.CommentCount = 0 + } else { + dateRecord.CommentCount = CommentCountMap[dateRecord.ID] + } + + if _, ok := FocusRepoCountMap[dateRecord.ID]; !ok { + dateRecord.FocusRepoCount = 0 + } else { + dateRecord.FocusRepoCount = FocusRepoCountMap[dateRecord.ID] + } + + if _, ok := StarRepoCountMap[dateRecord.ID]; !ok { + dateRecord.StarRepoCount = 0 + } else { + dateRecord.StarRepoCount = StarRepoCountMap[dateRecord.ID] + } + + if _, ok := WatchedCountMap[dateRecord.ID]; !ok { + dateRecord.WatchedCount = 0 + } else { + dateRecord.WatchedCount = WatchedCountMap[dateRecord.ID] + } + + if _, ok := CommitCodeSizeMap[dateRecord.Email]; !ok { + dateRecord.CommitCodeSize = 0 + } else { + dateRecord.CommitCodeSize = int(CommitCodeSizeMap[dateRecord.Email].CommitLines) + } + + if _, ok := CommitDatasetSizeMap[dateRecord.ID]; !ok { + dateRecord.CommitDatasetSize = 0 + } else { + dateRecord.CommitDatasetSize = CommitDatasetSizeMap[dateRecord.ID] + } + + if _, ok := SolveIssueCountMap[dateRecord.ID]; !ok { + dateRecord.SolveIssueCount = 0 + } else { + dateRecord.SolveIssueCount = SolveIssueCountMap[dateRecord.ID] + } + + if _, ok := wikiCountMap[dateRecord.Name]; !ok { + dateRecord.EncyclopediasCount = 0 + } else { + dateRecord.EncyclopediasCount = wikiCountMap[dateRecord.Name] + } + + dateRecord.CommitModelCount = 0 + + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + statictisSess.Insert(&dateRecord) + } + +} + +func querySolveIssue(start_unix int64, end_unix int64) map[int64]int { + //select issue_assignees.* from issue_assignees,issue where issue.is_closed=true and issue.id=issue_assignees.issue_id + sess := x.NewSession() + defer sess.Close() + sess.Select("issue_assignees.*").Table("issue_assignees"). + Join("inner", "issue", "issue.id=issue_assignees.issue_id"). + Where("issue.is_closed=true and issue.closed_unix>=" + fmt.Sprint(start_unix) + " and issue.closed_unix<=" + fmt.Sprint(end_unix)) + issueAssigneesList := make([]*IssueAssignees, 0) + sess.Find(&issueAssigneesList) + resultMap := make(map[int64]int) + log.Info("query IssueAssignees size=" + fmt.Sprint(len(issueAssigneesList))) + for _, issueAssigneesRecord := range issueAssigneesList { + if _, ok := resultMap[issueAssigneesRecord.AssigneeID]; !ok { + resultMap[issueAssigneesRecord.AssigneeID] = 1 + } else { + resultMap[issueAssigneesRecord.AssigneeID] += 1 + } + } + return resultMap + +} + +func queryAction(start_unix int64, end_unix int64, actionType int64) map[int64]int { + sess := x.NewSession() + defer sess.Close() + sess.Select("id,user_id,op_type,act_user_id").Table("action").Where("op_type=" + fmt.Sprint(actionType) + " and created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + actionList := make([]*Action, 0) + sess.Find(&actionList) + resultMap := make(map[int64]int) + log.Info("query action size=" + fmt.Sprint(len(actionList))) + for _, actionRecord := range actionList { + if _, ok := resultMap[actionRecord.UserID]; !ok { + resultMap[actionRecord.UserID] = 1 + } else { + resultMap[actionRecord.UserID] += 1 + } + } + return resultMap +} + +func queryComment(start_unix int64, end_unix int64) map[int64]int { + + sess := x.NewSession() + defer sess.Close() + sess.Select("id,type,poster_id").Table("comment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + commentList := make([]*Comment, 0) + sess.Find(&commentList) + resultMap := make(map[int64]int) + log.Info("query Comment size=" + fmt.Sprint(len(commentList))) + for _, commentRecord := range commentList { + if _, ok := resultMap[commentRecord.PosterID]; !ok { + resultMap[commentRecord.PosterID] = 1 + } else { + resultMap[commentRecord.PosterID] += 1 + } + } + return resultMap +} + +func queryWatch(start_unix int64, end_unix int64) map[int64]int { + + sess := x.NewSession() + defer sess.Close() + sess.Select("id,user_id,repo_id").Table("watch").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + watchList := make([]*Watch, 0) + sess.Find(&watchList) + resultMap := make(map[int64]int) + log.Info("query Watch size=" + fmt.Sprint(len(watchList))) + for _, watchRecord := range watchList { + if _, ok := resultMap[watchRecord.UserID]; !ok { + resultMap[watchRecord.UserID] = 1 + } else { + resultMap[watchRecord.UserID] += 1 + } + } + return resultMap + +} + +func queryStar(start_unix int64, end_unix int64) map[int64]int { + + sess := x.NewSession() + defer sess.Close() + sess.Select("id,uid,repo_id").Table("star").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + starList := make([]*Star, 0) + sess.Find(&starList) + resultMap := make(map[int64]int) + log.Info("query Star size=" + fmt.Sprint(len(starList))) + for _, starRecord := range starList { + if _, ok := resultMap[starRecord.UID]; !ok { + resultMap[starRecord.UID] = 1 + } else { + resultMap[starRecord.UID] += 1 + } + } + return resultMap + +} + +func queryFollow(start_unix int64, end_unix int64) map[int64]int { + + sess := x.NewSession() + defer sess.Close() + sess.Select("id,user_id,follow_id").Table("follow").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + followList := make([]*Follow, 0) + sess.Find(&followList) + resultMap := make(map[int64]int) + log.Info("query Follow size=" + fmt.Sprint(len(followList))) + for _, followRecord := range followList { + if _, ok := resultMap[followRecord.UserID]; !ok { + resultMap[followRecord.UserID] = 1 + } else { + resultMap[followRecord.UserID] += 1 + } + } + return resultMap +} + +func queryDatasetSize(start_unix int64, end_unix int64) map[int64]int { + sess := x.NewSession() + defer sess.Close() + sess.Select("id,uploader_id,size").Table("attachment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) + attachmentList := make([]*Attachment, 0) + sess.Find(&attachmentList) + resultMap := make(map[int64]int) + log.Info("query Attachment size=" + fmt.Sprint(len(attachmentList))) + for _, attachRecord := range attachmentList { + if _, ok := resultMap[attachRecord.UploaderID]; !ok { + resultMap[attachRecord.UploaderID] = int(attachRecord.Size / (1024 * 1024)) //MB + } else { + resultMap[attachRecord.UploaderID] += int(attachRecord.Size / (1024 * 1024)) //MB + } + } + return resultMap + +} + +func subMonth(t1, t2 time.Time) (month int) { + y1 := t1.Year() + y2 := t2.Year() + m1 := int(t1.Month()) + m2 := int(t2.Month()) + d1 := t1.Day() + d2 := t2.Day() + + yearInterval := y1 - y2 + // 如果 d1的 月-日 小于 d2的 月-日 那么 yearInterval-- 这样就得到了相差的年数 + if m1 < m2 || m1 == m2 && d1 < d2 { + yearInterval-- + } + // 获取月数差值 + monthInterval := (m1 + 12) - m2 + if d1 < d2 { + monthInterval-- + } + monthInterval %= 12 + month = yearInterval*12 + monthInterval + return month +} + +func QueryAllRepo() []*Repository { + sess := x.NewSession() + defer sess.Close() + sess.Select("*").Table("repository") + repositoryList := make([]*Repository, 0) + sess.Find(&repositoryList) + + return repositoryList +} diff --git a/models/user_follow.go b/models/user_follow.go index 4bde71cb9..b63535bb9 100644 --- a/models/user_follow.go +++ b/models/user_follow.go @@ -4,11 +4,14 @@ package models +import "code.gitea.io/gitea/modules/timeutil" + // Follow represents relations of user and his/her followers. type Follow struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(follow)"` - FollowID int64 `xorm:"UNIQUE(follow)"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(follow)"` + FollowID int64 `xorm:"UNIQUE(follow)"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } // IsFollowing returns true if user is following followID. diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index c5f6d6cdd..a51a402c6 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -206,6 +206,15 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { return commits.Front().Value.(*Commit), nil } +func (repo *Repository) GetCommitByPathAndDays(relpath string, days int) (*list.List, error) { + stdout, err := NewCommand("log", "-1", prettyLogFormat, "--since="+fmt.Sprint(days)+".days").RunInDirBytes(relpath) + if err != nil { + return nil, err + } + + return repo.parsePrettyFormatLogToList(stdout) +} + // CommitsRangeSize the default commits range size var CommitsRangeSize = 50 diff --git a/routers/repo/user_data_analysis.go b/routers/repo/user_data_analysis.go new file mode 100644 index 000000000..13850af7a --- /dev/null +++ b/routers/repo/user_data_analysis.go @@ -0,0 +1,55 @@ +package repo + +import ( + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +func TimeingCountData() { + //query wiki data + log.Info("start to time count data") + wikiMap := make(map[string]int) + + currentTimeNow := time.Now() + log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05")) + + yesterday := currentTimeNow.AddDate(0, 0, -1) + + repoList := models.QueryAllRepo() + log.Info("start to query wiki data") + for _, repoRecord := range repoList { + wikiPath := models.WikiPath(repoRecord.OwnerName, repoRecord.Name) + time, err := git.GetLatestCommitTime(wikiPath) + if err == nil { + log.Info("last commit time:" + time.Format("2006-01-02 15:04:05") + " wikiPath=" + wikiPath) + if time.After(yesterday) { + wikiRepo, _, err := FindWikiRepoCommitByWikiPath(wikiPath) + if err != nil { + log.Error("wiki not exist. wikiPath=" + wikiPath) + } else { + log.Info("wiki exist, wikiPath=" + wikiPath) + list, err := wikiRepo.GetCommitByPathAndDays(wikiPath, 1) + if err != nil { + log.Info("err,err=v%", err) + } else { + for logEntry := list.Front(); logEntry != nil; logEntry = logEntry.Next() { + commit := logEntry.Value.(*git.Commit) + log.Info("commit msg=" + commit.CommitMessage + " time=" + commit.Committer.When.Format("2006-01-02 15:04:05") + " user=" + commit.Committer.Name) + if _, ok := wikiMap[commit.Committer.Name]; !ok { + wikiMap[commit.Committer.Name] = 1 + } else { + wikiMap[commit.Committer.Name] += 1 + } + } + } + + } + } + } + } + //other user info data + models.CountData(wikiMap) +} diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 5da01f21a..03ab42036 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -82,6 +82,20 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) return commit.GetTreeEntryByPath(unescapedTarget) } +func FindWikiRepoCommitByWikiPath(wikiPath string) (*git.Repository, *git.Commit, error) { + wikiRepo, err := git.OpenRepository(wikiPath) + if err != nil { + log.Info("get wiki error.") + return nil, nil, err + } + + commit, err := wikiRepo.GetBranchCommit("master") + if err != nil { + return wikiRepo, nil, err + } + return wikiRepo, commit, nil +} + func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) { wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath()) if err != nil { @@ -150,6 +164,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if !entry.IsRegular() { continue } + wikiName, err := wiki_service.FilenameToName(entry.Name()) if err != nil { if models.IsErrWikiInvalidFileName(err) {