Skip to content

Add Activity page to repository #2674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (

// UnitEnabled if this repository has the given unit enabled
func (repo *Repository) UnitEnabled(tp UnitType) bool {
repo.getUnits(x)
if err := repo.getUnits(x); err != nil {
log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
}
for _, unit := range repo.Units {
if unit.Type == tp {
return true
Expand All @@ -392,6 +394,21 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool {
return false
}

// AnyUnitEnabled if this repository has the any of the given units enabled
func (repo *Repository) AnyUnitEnabled(tps ...UnitType) bool {
if err := repo.getUnits(x); err != nil {
log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
}
for _, unit := range repo.Units {
for _, tp := range tps {
if unit.Type == tp {
return true
}
}
}
return false
}

var (
// ErrUnitNotExist organization does not exist
ErrUnitNotExist = errors.New("Unit does not exist")
Expand Down
242 changes: 242 additions & 0 deletions models/repo_activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
"time"

"github.com/go-xorm/xorm"
)

// ActivityStats represets issue and pull request information.
type ActivityStats struct {
OpenedPRs PullRequestList
OpenedPRAuthorCount int64
MergedPRs PullRequestList
MergedPRAuthorCount int64
OpenedIssues IssueList
OpenedIssueAuthorCount int64
ClosedIssues IssueList
ClosedIssueAuthorCount int64
UnresolvedIssues IssueList
PublishedReleases []*Release
PublishedReleaseAuthorCount int64
}

// ActivePRCount returns total active pull request count
func (stats *ActivityStats) ActivePRCount() int {
return stats.OpenedPRCount() + stats.MergedPRCount()
}

// OpenedPRCount returns opened pull request count
func (stats *ActivityStats) OpenedPRCount() int {
return len(stats.OpenedPRs)
}

// OpenedPRPerc returns opened pull request percents from total active
func (stats *ActivityStats) OpenedPRPerc() int {
return int(float32(stats.OpenedPRCount()) / float32(stats.ActivePRCount()) * 100.0)
}

// MergedPRCount returns merged pull request count
func (stats *ActivityStats) MergedPRCount() int {
return len(stats.MergedPRs)
}

// MergedPRPerc returns merged pull request percent from total active
func (stats *ActivityStats) MergedPRPerc() int {
return int(float32(stats.MergedPRCount()) / float32(stats.ActivePRCount()) * 100.0)
}

// ActiveIssueCount returns total active issue count
func (stats *ActivityStats) ActiveIssueCount() int {
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
}

// OpenedIssueCount returns open issue count
func (stats *ActivityStats) OpenedIssueCount() int {
return len(stats.OpenedIssues)
}

// OpenedIssuePerc returns open issue count percent from total active
func (stats *ActivityStats) OpenedIssuePerc() int {
return int(float32(stats.OpenedIssueCount()) / float32(stats.ActiveIssueCount()) * 100.0)
}

// ClosedIssueCount returns closed issue count
func (stats *ActivityStats) ClosedIssueCount() int {
return len(stats.ClosedIssues)
}

// ClosedIssuePerc returns closed issue count percent from total active
func (stats *ActivityStats) ClosedIssuePerc() int {
return int(float32(stats.ClosedIssueCount()) / float32(stats.ActiveIssueCount()) * 100.0)
}

// UnresolvedIssueCount returns unresolved issue and pull request count
func (stats *ActivityStats) UnresolvedIssueCount() int {
return len(stats.UnresolvedIssues)
}

// PublishedReleaseCount returns published release count
func (stats *ActivityStats) PublishedReleaseCount() int {
return len(stats.PublishedReleases)
}

// FillPullRequestsForActivity returns pull request information for activity page
func FillPullRequestsForActivity(stats *ActivityStats, baseRepoID int64, fromTime time.Time) error {
var err error
var count int64

// Merged pull requests
sess := pullRequestsForActivityStatement(baseRepoID, fromTime, true)
sess.OrderBy("pull_request.merged_unix DESC")
stats.MergedPRs = make(PullRequestList, 0)
if err = sess.Find(&stats.MergedPRs); err != nil {
return err
}
if err = stats.MergedPRs.LoadAttributes(); err != nil {
return err
}

// Merged pull request authors
sess = pullRequestsForActivityStatement(baseRepoID, fromTime, true)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
return err
}
stats.MergedPRAuthorCount = count

// Opened pull requests
sess = pullRequestsForActivityStatement(baseRepoID, fromTime, false)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedPRs = make(PullRequestList, 0)
if err = sess.Find(&stats.OpenedPRs); err != nil {
return err
}
if err = stats.OpenedPRs.LoadAttributes(); err != nil {
return err
}

// Opened pull request authors
sess = pullRequestsForActivityStatement(baseRepoID, fromTime, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
return err
}
stats.OpenedPRAuthorCount = count

return nil
}

func pullRequestsForActivityStatement(baseRepoID int64, fromTime time.Time, merged bool) *xorm.Session {
sess := x.Where("pull_request.base_repo_id=?", baseRepoID).
Join("INNER", "issue", "pull_request.issue_id = issue.id")

if merged {
sess.And("pull_request.has_merged = ?", true)
sess.And("pull_request.merged_unix >= ?", fromTime.Unix())
} else {
sess.And("issue.is_closed = ?", false)
sess.And("issue.created_unix >= ?", fromTime.Unix())
}

return sess
}

// FillIssuesForActivity returns issue information for activity page
func FillIssuesForActivity(stats *ActivityStats, baseRepoID int64, fromTime time.Time) error {
var err error
var count int64

// Closed issues
sess := issuesForActivityStatement(baseRepoID, fromTime, true, false)
sess.OrderBy("issue.updated_unix DESC")
stats.ClosedIssues = make(IssueList, 0)
if err = sess.Find(&stats.ClosedIssues); err != nil {
return err
}

// Closed issue authors
sess = issuesForActivityStatement(baseRepoID, fromTime, true, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
return err
}
stats.ClosedIssueAuthorCount = count

// New issues
sess = issuesForActivityStatement(baseRepoID, fromTime, false, false)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedIssues = make(IssueList, 0)
if err = sess.Find(&stats.OpenedIssues); err != nil {
return err
}

// Opened issue authors
sess = issuesForActivityStatement(baseRepoID, fromTime, false, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
return err
}
stats.OpenedIssueAuthorCount = count

return nil
}

// FillUnresolvedIssuesForActivity returns unresolved issue and pull request information for activity page
func FillUnresolvedIssuesForActivity(stats *ActivityStats, baseRepoID int64, fromTime time.Time, issues, prs bool) error {
// Check if we need to select anything
if !issues && !prs {
return nil
}
sess := issuesForActivityStatement(baseRepoID, fromTime, false, true)
if !issues || !prs {
sess.And("issue.is_pull = ?", prs)
}
sess.OrderBy("issue.updated_unix DESC")
stats.UnresolvedIssues = make(IssueList, 0)
return sess.Find(&stats.UnresolvedIssues)
}

func issuesForActivityStatement(baseRepoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := x.Where("issue.repo_id = ?", baseRepoID).
And("issue.is_closed = ?", closed)

if !unresolved {
sess.And("issue.is_pull = ?", false)
sess.And("issue.created_unix >= ?", fromTime.Unix())
} else {
sess.And("issue.created_unix < ?", fromTime.Unix())
sess.And("issue.updated_unix >= ?", fromTime.Unix())
}

return sess
}

// FillReleasesForActivity returns release information for activity page
func FillReleasesForActivity(stats *ActivityStats, baseRepoID int64, fromTime time.Time) error {
var err error
var count int64

// Published releases list
sess := releasesForActivityStatement(baseRepoID, fromTime)
sess.OrderBy("release.created_unix DESC")
stats.PublishedReleases = make([]*Release, 0)
if err = sess.Find(&stats.PublishedReleases); err != nil {
return err
}

// Published releases authors
sess = releasesForActivityStatement(baseRepoID, fromTime)
if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil {
return err
}
stats.PublishedReleaseAuthorCount = count

return nil
}

func releasesForActivityStatement(baseRepoID int64, fromTime time.Time) *xorm.Session {
return x.Where("release.repo_id = ?", baseRepoID).
And("release.is_draft = ?", false).
And("release.created_unix >= ?", fromTime.Unix())
}
11 changes: 10 additions & 1 deletion modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ func LoadRepoUnits() macaron.Handler {
}
}

// CheckUnit will check whether
// CheckUnit will check whether unit type is enabled
func CheckUnit(unitType models.UnitType) macaron.Handler {
return func(ctx *Context) {
if !ctx.Repo.Repository.UnitEnabled(unitType) {
Expand All @@ -582,6 +582,15 @@ func CheckUnit(unitType models.UnitType) macaron.Handler {
}
}

// CheckAnyUnit will check whether any of the unit types are enabled
func CheckAnyUnit(unitTypes ...models.UnitType) macaron.Handler {
return func(ctx *Context) {
if !ctx.Repo.Repository.AnyUnitEnabled(unitTypes...) {
ctx.Handle(404, "CheckAnyUnit", fmt.Errorf("%s: %v", ctx.Tr("units.error.unit_not_allowed"), unitTypes))
}
}
}

// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler {
return func(ctx *Context) {
Expand Down
58 changes: 58 additions & 0 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func NewFuncMap() []template.FuncMap {
"DisableGitHooks": func() bool {
return setting.DisableGitHooks
},
"TrN": TrN,
}}
}

Expand Down Expand Up @@ -342,3 +343,60 @@ func DiffLineTypeToStr(diffType int) string {
}
return "same"
}

// Language specific rules for translating plural texts
var trNLangRules = map[string]func(int64) int{
"en-US": func(cnt int64) int {
if cnt == 1 {
return 0
}
return 1
},
"lv-LV": func(cnt int64) int {
if cnt%10 == 1 && cnt%100 != 11 {
return 0
}
return 1
},
"ru-RU": func(cnt int64) int {
if cnt%10 == 1 && cnt%100 != 11 {
return 0
}
return 1
},
"zh-CN": func(cnt int64) int {
return 0
},
"zh-HK": func(cnt int64) int {
return 0
},
"zh-TW": func(cnt int64) int {
return 0
},
}

// TrN returns key to be used for plural text translation
func TrN(lang string, cnt interface{}, key1, keyN string) string {
var c int64
if t, ok := cnt.(int); ok {
c = int64(t)
} else if t, ok := cnt.(int16); ok {
c = int64(t)
} else if t, ok := cnt.(int32); ok {
c = int64(t)
} else if t, ok := cnt.(int64); ok {
c = t
} else {
return keyN
}

ruleFunc, ok := trNLangRules[lang]
if !ok {
ruleFunc = trNLangRules["en-US"]
}

if ruleFunc(c) == 0 {
return key1
}
return keyN
}
Loading