* Add Activity page to repository * Add request data for activity * Add issue data for activity * Add user unit right checks * Add releases to activity * Log repository unit loading errortags/v1.3.0-rc1
@@ -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 | |||
@@ -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") |
@@ -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()) | |||
} |
@@ -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) { | |||
@@ -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) { |
@@ -158,6 +158,7 @@ func NewFuncMap() []template.FuncMap { | |||
"DisableGitHooks": func() bool { | |||
return setting.DisableGitHooks | |||
}, | |||
"TrN": TrN, | |||
}} | |||
} | |||
@@ -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 | |||
} |
@@ -806,6 +806,48 @@ wiki.page_already_exists = A wiki page with the same name already exists. | |||
wiki.pages = Pages | |||
wiki.last_updated = Last updated %s | |||
activity = Activity | |||
activity.period.filter_label = Period: | |||
activity.period.daily = 1 day | |||
activity.period.halfweekly = 3 days | |||
activity.period.weekly = 1 week | |||
activity.period.monthly = 1 month | |||
activity.overview = Overview | |||
activity.active_prs_count_1 = <strong>%d</strong> Active Pull Request | |||
activity.active_prs_count_n = <strong>%d</strong> Active Pull Requests | |||
activity.merged_prs_count_1 = Merged Pull Request | |||
activity.merged_prs_count_n = Merged Pull Requests | |||
activity.opened_prs_count_1 = Proposed Pull Request | |||
activity.opened_prs_count_n = Proposed Pull Requests | |||
activity.title.user_1 = %d user | |||
activity.title.user_n = %d users | |||
activity.title.prs_1 = %d Pull request | |||
activity.title.prs_n = %d Pull requests | |||
activity.title.prs_merged_by = %s merged by %s | |||
activity.title.prs_opened_by = %s proposed by %s | |||
activity.merged_prs_label = Merged | |||
activity.opened_prs_label = Proposed | |||
activity.active_issues_count_1 = <strong>%d</strong> Active Issue | |||
activity.active_issues_count_n = <strong>%d</strong> Active Issues | |||
activity.closed_issues_count_1 = Closed Issue | |||
activity.closed_issues_count_n = Closed Issues | |||
activity.title.issues_1 = %d Issue | |||
activity.title.issues_n = %d Issues | |||
activity.title.issues_closed_by = %s closed by %s | |||
activity.title.issues_created_by = %s created by %s | |||
activity.closed_issue_label = Closed | |||
activity.new_issues_count_1 = New Issue | |||
activity.new_issues_count_n = New Issues | |||
activity.new_issue_label = Opened | |||
activity.title.unresolved_conv_1 = %d Unresolved conversation | |||
activity.title.unresolved_conv_n = %d Unresolved conversations | |||
activity.unresolved_conv_desc = List of all old issues and pull requests that have changed recently but has not been resloved yet. | |||
activity.unresolved_conv_label = Open | |||
activity.title.releases_1 = %d Release | |||
activity.title.releases_n = %d Releases | |||
activity.title.releases_published_by = %s published by %s | |||
activity.published_release_label = Published | |||
settings = Settings | |||
settings.desc = Settings is where you can manage the settings for the repository | |||
settings.options = Options |
@@ -299,8 +299,40 @@ pre, code { | |||
padding: 8px 15px; | |||
font-weight: normal; | |||
} | |||
.background { | |||
&.red { | |||
background-color: #d95c5c !important; | |||
} | |||
&.blue { | |||
background-color: #428bca !important; | |||
} | |||
&.black { | |||
background-color: #444; | |||
} | |||
&.grey { | |||
background-color: #767676 !important; | |||
} | |||
&.light.grey { | |||
background-color: #888 !important; | |||
} | |||
&.green { | |||
background-color: #6cc644 !important; | |||
} | |||
&.purple { | |||
background-color: #6e5494 !important; | |||
} | |||
&.yellow { | |||
background-color: #FBBD08 !important; | |||
} | |||
&.gold { | |||
background-color: #a1882b !important; | |||
} | |||
} | |||
} | |||
.overflow.menu { | |||
.items { | |||
max-height: 300px; | |||
@@ -477,4 +509,4 @@ footer { | |||
margin-top: 0 !important; | |||
border-bottom-width: 0 !important; | |||
margin-bottom: 2px !important; | |||
} | |||
} |
@@ -1554,3 +1554,14 @@ | |||
} | |||
.generate-tab-size(@n, (@i + 1)); | |||
} | |||
.table { | |||
display: table; | |||
width: 100%; | |||
.table-cell { | |||
display: table-cell; | |||
&.tiny { | |||
height: .5em; | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
// 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 repo | |||
import ( | |||
"time" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
) | |||
const ( | |||
tplActivity base.TplName = "repo/activity" | |||
) | |||
// Activity render the page to show repository latest changes | |||
func Activity(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("repo.activity") | |||
ctx.Data["PageIsActivity"] = true | |||
ctx.Data["Period"] = ctx.Params("period") | |||
timeUntil := time.Now() | |||
var timeFrom time.Time | |||
switch ctx.Data["Period"] { | |||
case "daily": | |||
timeFrom = timeUntil.Add(-time.Hour * 24) | |||
case "halfweekly": | |||
timeFrom = timeUntil.Add(-time.Hour * 72) | |||
case "weekly": | |||
timeFrom = timeUntil.Add(-time.Hour * 168) | |||
case "monthly": | |||
timeFrom = timeUntil.AddDate(0, -1, 0) | |||
default: | |||
ctx.Data["Period"] = "weekly" | |||
timeFrom = timeUntil.Add(-time.Hour * 168) | |||
} | |||
ctx.Data["DateFrom"] = timeFrom.Format("January 2, 2006") | |||
ctx.Data["DateUntil"] = timeUntil.Format("January 2, 2006") | |||
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string)) | |||
stats := &models.ActivityStats{} | |||
if ctx.Repo.Repository.UnitEnabled(models.UnitTypeReleases) { | |||
if err := models.FillReleasesForActivity(stats, ctx.Repo.Repository.ID, timeFrom); err != nil { | |||
ctx.Handle(500, "FillReleasesForActivity", err) | |||
return | |||
} | |||
} | |||
if ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) { | |||
if err := models.FillPullRequestsForActivity(stats, ctx.Repo.Repository.ID, timeFrom); err != nil { | |||
ctx.Handle(500, "FillPullRequestsForActivity", err) | |||
return | |||
} | |||
} | |||
if ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) { | |||
if err := models.FillIssuesForActivity(stats, ctx.Repo.Repository.ID, timeFrom); err != nil { | |||
ctx.Handle(500, "FillIssuesForActivity", err) | |||
return | |||
} | |||
} | |||
if err := models.FillUnresolvedIssuesForActivity(stats, ctx.Repo.Repository.ID, timeFrom, | |||
ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues), | |||
ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests)); err != nil { | |||
ctx.Handle(500, "FillUnresolvedIssuesForActivity", err) | |||
return | |||
} | |||
ctx.Data["Activity"] = stats | |||
ctx.HTML(200, tplActivity) | |||
} |
@@ -613,6 +613,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/*", repo.WikiRaw) | |||
}, repo.MustEnableWiki) | |||
m.Group("/activity", func() { | |||
m.Get("", repo.Activity) | |||
m.Get("/:period", repo.Activity) | |||
}, context.RepoRef(), repo.MustBeNotBare, context.CheckAnyUnit(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases)) | |||
m.Get("/archive/*", repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.Download) | |||
m.Group("/pulls/:index", func() { |
@@ -0,0 +1,184 @@ | |||
{{template "base/head" .}} | |||
<div class="repository commits"> | |||
{{template "repo/header" .}} | |||
<div class="ui container"> | |||
<h2 class="ui header">{{.DateFrom}} - {{.DateUntil}} | |||
<div class="ui right"> | |||
<!-- Period --> | |||
<div class="ui floating dropdown jump filter"> | |||
<div class="ui basic compact button"> | |||
<span class="text"> | |||
{{.i18n.Tr "repo.activity.period.filter_label"}} <strong>{{.PeriodText}}</strong> | |||
<i class="dropdown icon"></i> | |||
</span> | |||
</div> | |||
<div class="menu"> | |||
<a class="{{if eq .Period "daily"}}active {{end}}item" href="{{$.RepoLink}}/activity/daily">{{.i18n.Tr "repo.activity.period.daily"}}</a> | |||
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{.i18n.Tr "repo.activity.period.halfweekly"}}</a> | |||
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{.i18n.Tr "repo.activity.period.weekly"}}</a> | |||
<a class="{{if eq .Period "monthly"}}active {{end}}item" href="{{$.RepoLink}}/activity/monthly">{{.i18n.Tr "repo.activity.period.monthly"}}</a> | |||
</div> | |||
</div> | |||
</div> | |||
</h2> | |||
<div class="ui divider"></div> | |||
{{if (or (.Repository.UnitEnabled $.UnitTypeIssues) (.Repository.UnitEnabled $.UnitTypePullRequests))}} | |||
<h4 class="ui top attached header">{{.i18n.Tr "repo.activity.overview"}}</h4> | |||
<div class="ui attached segment two column grid"> | |||
{{if .Repository.UnitEnabled $.UnitTypePullRequests}} | |||
<div class="column"> | |||
{{if gt .Activity.ActivePRCount 0}} | |||
<div class="table"> | |||
<a href="#merged-pull-requests" class="table-cell tiny background purple" style="width: {{.Activity.MergedPRPerc}}%"></a> | |||
<a href="#proposed-pull-requests" class="table-cell tiny background green"></a> | |||
</div> | |||
{{end}} | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n") .Activity.ActivePRCount | Safe }} | |||
</div> | |||
{{end}} | |||
{{if .Repository.UnitEnabled $.UnitTypeIssues}} | |||
<div class="column"> | |||
{{if gt .Activity.ActiveIssueCount 0}} | |||
<div class="table"> | |||
<a href="#closed-issues" class="table-cell tiny background red" style="width: {{.Activity.ClosedIssuePerc}}%"></a> | |||
<a href="#new-issues" class="table-cell tiny background green"></a> | |||
</div> | |||
{{end}} | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n") .Activity.ActiveIssueCount | Safe }} | |||
</div> | |||
{{end}} | |||
</div> | |||
<div class="ui attached segment horizontal segments"> | |||
{{if .Repository.UnitEnabled $.UnitTypePullRequests}} | |||
<a href="#merged-pull-requests" class="ui attached segment text center"> | |||
<i class="text purple octicon octicon-git-pull-request"></i> <strong>{{.Activity.MergedPRCount}}</strong><br> | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n") }} | |||
</a> | |||
<a href="#proposed-pull-requests" class="ui attached segment text center"> | |||
<i class="text green octicon octicon-git-branch"></i> <strong>{{.Activity.OpenedPRCount}}</strong><br> | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n") }} | |||
</a> | |||
{{end}} | |||
{{if .Repository.UnitEnabled $.UnitTypeIssues}} | |||
<a href="#closed-issues" class="ui attached segment text center"> | |||
<i class="text red octicon octicon-issue-closed"></i> <strong>{{.Activity.ClosedIssueCount}}</strong><br> | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n") }} | |||
</a> | |||
<a href="#new-issues" class="ui attached segment text center"> | |||
<i class="text green octicon octicon-issue-opened"></i> <strong>{{.Activity.OpenedIssueCount}}</strong><br> | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n") }} | |||
</a> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.PublishedReleaseCount 0}} | |||
<h4 class="ui horizontal divider header" id="published-releases"> | |||
<i class="text octicon octicon-tag"></i> | |||
{{.i18n.Tr "repo.activity.title.releases_published_by" (.i18n.Tr (TrN .i18n.Lang .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n") .Activity.PublishedReleaseCount) (.i18n.Tr (TrN .i18n.Lang .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.PublishedReleaseAuthorCount) }} | |||
</h4> | |||
<div class="list"> | |||
{{range .Activity.PublishedReleases}} | |||
<p class="desc"> | |||
<div class="ui green label">{{$.i18n.Tr "repo.activity.published_release_label"}}</div> | |||
{{.TagName}} | |||
{{if not .IsTag}} | |||
<a class="title has-emoji" href="{{$.Repository.HTMLURL}}/src/{{.TagName}}">{{.Title}}</a> | |||
{{end}} | |||
{{TimeSince .Created $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.MergedPRCount 0}} | |||
<h4 class="ui horizontal divider header" id="merged-pull-requests"> | |||
<i class="text octicon octicon-git-pull-request"></i> | |||
{{.i18n.Tr "repo.activity.title.prs_merged_by" (.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n") .Activity.MergedPRCount) (.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.MergedPRAuthorCount) }} | |||
</h4> | |||
<div class="list"> | |||
{{range .Activity.MergedPRs}} | |||
<p class="desc"> | |||
<div class="ui purple label">{{$.i18n.Tr "repo.activity.merged_prs_label"}}</div> | |||
#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/pulls/{{.Index}}">{{.Issue.Title}}</a> | |||
{{TimeSince .Merged $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.OpenedPRCount 0}} | |||
<h4 class="ui horizontal divider header" id="proposed-pull-requests"> | |||
<i class="text octicon octicon-git-branch"></i> | |||
{{.i18n.Tr "repo.activity.title.prs_opened_by" (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n") .Activity.OpenedPRCount) (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.OpenedPRAuthorCount) }} | |||
</h4> | |||
<div class="list"> | |||
{{range .Activity.OpenedPRs}} | |||
<p class="desc"> | |||
<div class="ui green label">{{$.i18n.Tr "repo.activity.opened_prs_label"}}</div> | |||
#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/pulls/{{.Index}}">{{.Issue.Title}}</a> | |||
{{TimeSince .Issue.Created $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.ClosedIssueCount 0}} | |||
<h4 class="ui horizontal divider header" id="closed-issues"> | |||
<i class="text octicon octicon-issue-closed"></i> | |||
{{.i18n.Tr "repo.activity.title.issues_closed_by" (.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n") .Activity.ClosedIssueCount) (.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.ClosedIssueAuthorCount) }} | |||
</h4> | |||
<div class="list"> | |||
{{range .Activity.ClosedIssues}} | |||
<p class="desc"> | |||
<div class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</div> | |||
#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a> | |||
{{TimeSince .Updated $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.OpenedIssueCount 0}} | |||
<h4 class="ui horizontal divider header" id="new-issues"> | |||
<i class="text octicon octicon-issue-opened"></i> | |||
{{.i18n.Tr "repo.activity.title.issues_created_by" (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n") .Activity.OpenedIssueCount) (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.OpenedIssueAuthorCount) }} | |||
</h4> | |||
<div class="list"> | |||
{{range .Activity.OpenedIssues}} | |||
<p class="desc"> | |||
<div class="ui green label">{{$.i18n.Tr "repo.activity.new_issue_label"}}</div> | |||
#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a> | |||
{{TimeSince .Created $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{if gt .Activity.UnresolvedIssueCount 0}} | |||
<h4 class="ui horizontal divider header" id="unresolved-conversations"> | |||
<i class="text octicon octicon-comment-discussion"></i> | |||
{{.i18n.Tr (TrN .i18n.Lang .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n") .Activity.UnresolvedIssueCount }} | |||
</h4> | |||
<div class="text center desc"> | |||
{{.i18n.Tr "repo.activity.unresolved_conv_desc"}} | |||
</div> | |||
<div class="list"> | |||
{{range .Activity.UnresolvedIssues}} | |||
<p class="desc"> | |||
<div class="ui green label">{{$.i18n.Tr "repo.activity.unresolved_conv_label"}}</div> | |||
#{{.Index}} | |||
{{if .IsPull}} | |||
<a class="title has-emoji" href="{{$.Repository.HTMLURL}}/pulls/{{.Index}}">{{.Title}}</a> | |||
{{else}} | |||
<a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a> | |||
{{end}} | |||
{{TimeSince .Updated $.Lang}} | |||
</p> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -91,6 +91,12 @@ | |||
</a> | |||
{{end}} | |||
{{if and (.Repository.AnyUnitEnabled $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsBareRepo)}} | |||
<a class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | |||
<i class="octicon octicon-pulse"></i> {{.i18n.Tr "repo.activity"}} | |||
</a> | |||
{{end}} | |||
{{if .IsRepositoryAdmin}} | |||
<div class="right menu"> | |||
<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings"> |