* 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"> | |||