new issue and label page

release/v1.15
Unknwon 2015-07-25 02:52:25 +08:00
parent 54b58e988d
commit 1ba837a055
11 changed files with 149 additions and 141 deletions

View File

@ -12,6 +12,10 @@ RUN_MODE = dev
ROOT =
SCRIPT_TYPE = bash
[ui]
; Number of issues that are showed in one page
ISSUE_PAGING_NUM = 10
[server]
PROTOCOL = http
DOMAIN = localhost

View File

@ -370,6 +370,7 @@ issues.new_label_placeholder = Label name...
issues.open_tab = %d Open
issues.close_tab = %d Closed
issues.filter_label = Label
issues.filter_label_no_select = No selected label
issues.filter_milestone = Milestone
issues.filter_assignee = Assignee
issues.filter_type = Type

View File

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting"
)
const APP_VER = "0.6.2.0724 Beta"
const APP_VER = "0.6.2.0725 Beta"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())

View File

@ -14,7 +14,6 @@ import (
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
@ -73,7 +72,7 @@ func (i *Issue) GetLabels() error {
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
i.Labels = make([]*Label, 0, len(strIds))
for _, strId := range strIds {
id, _ := com.StrTo(strId).Int64()
id := com.StrTo(strId).MustInt64()
if id > 0 {
l, err := GetLabelById(id)
if err != nil {
@ -186,29 +185,29 @@ func GetIssueById(id int64) (*Issue, error) {
}
// GetIssues returns a list of issues by given conditions.
func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
func GetIssues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isClosed, isMention bool, labelIds, sortType string) ([]Issue, error) {
sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
if rid > 0 {
sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
if repoID > 0 {
sess.Where("issue.repo_id=?", repoID).And("issue.is_closed=?", isClosed)
} else {
sess.Where("is_closed=?", isClosed)
sess.Where("issue.is_closed=?", isClosed)
}
if uid > 0 {
sess.And("assignee_id=?", uid)
} else if pid > 0 {
sess.And("poster_id=?", pid)
if assigneeID > 0 {
sess.And("issue.assignee_id=?", assigneeID)
} else if posterID > 0 {
sess.And("issue.poster_id=?", posterID)
}
if mid > 0 {
sess.And("milestone_id=?", mid)
if milestoneID > 0 {
sess.And("issue.milestone_id=?", milestoneID)
}
if len(labelIds) > 0 {
for _, label := range strings.Split(labelIds, ",") {
if com.StrTo(label).MustInt() > 0 {
sess.And("label_ids like ?", "'%$"+label+"|%'")
sess.And("label_ids like ?", "%$"+label+"|%")
}
}
}
@ -230,6 +229,14 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sort
sess.Desc("created")
}
if isMention {
queryStr := "issue.id == issue_user.issue_id AND issue_user.is_mentioned=1"
if uid > 0 {
queryStr += " AND issue_user.uid = " + com.ToStr(uid)
}
sess.Join("INNER", "issue_user", queryStr)
}
var issues []Issue
return issues, sess.Find(&issues)
}
@ -394,53 +401,42 @@ type IssueStats struct {
// Filter modes.
const (
FM_ASSIGN = iota + 1
FM_ALL = iota
FM_ASSIGN
FM_CREATE
FM_MENTION
)
// GetIssueStats returns issue statistic information by given conditions.
func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
func GetIssueStats(repoID, uid, labelID int64, isShowClosed bool, filterMode int) *IssueStats {
stats := &IssueStats{}
issue := new(Issue)
tmpSess := &xorm.Session{}
sess := x.Where("repo_id=?", rid)
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
if isShowClosed {
stats.AllCount = stats.ClosedCount
} else {
stats.AllCount = stats.OpenCount
}
queryStr := "repo_id=? AND is_closed=?"
switch filterMode {
case FM_ALL:
stats.OpenCount, _ = x.Where(queryStr, repoID, false).Count(issue)
stats.ClosedCount, _ = x.Where(queryStr, repoID, true).Count(issue)
return stats
if filterMode != FM_MENTION {
sess = x.Where("repo_id=?", rid)
switch filterMode {
case FM_ASSIGN:
sess.And("assignee_id=?", uid)
case FM_CREATE:
sess.And("poster_id=?", uid)
default:
goto nofilter
}
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
} else {
sess := x.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
case FM_ASSIGN:
queryStr += " AND assignee_id=?"
stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
return stats
case FM_CREATE:
queryStr += " AND poster_id=?"
stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
return stats
case FM_MENTION:
queryStr += " AND uid=? AND is_mentioned=?"
stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid, true).Count(new(IssueUser))
stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid, true).Count(new(IssueUser))
return stats
}
nofilter:
stats.AssignCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
stats.CreateCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
stats.MentionCount, _ = x.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
return stats
}
@ -894,7 +890,7 @@ type Comment struct {
// CreateComment creates comment of issue or commit.
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
sess := x.NewSession()
defer sess.Close()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return nil, err
}
@ -903,7 +899,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
CommitId: commitId, Line: line, Content: content}
if _, err := sess.Insert(comment); err != nil {
sess.Rollback()
return nil, err
}
@ -912,7 +907,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
case COMMENT_TYPE_COMMENT:
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, issueId); err != nil {
sess.Rollback()
return nil, err
}
@ -926,20 +920,17 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
}
if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
sess.Rollback()
return nil, err
}
}
case COMMENT_TYPE_REOPEN:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback()
return nil, err
}
case COMMENT_TYPE_CLOSE:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback()
return nil, err
}
}

File diff suppressed because one or more lines are too long

View File

@ -82,9 +82,11 @@ var (
}
// Repository settings.
RepoRootPath string
ScriptType string
IssuePagingNum int = 10
RepoRootPath string
ScriptType string
// UI settings.
IssuePagingNum int
// Picture settings.
PictureService string
@ -311,6 +313,9 @@ func NewConfigContext() {
}
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
// UI settings.
IssuePagingNum = Cfg.Section("ui").Key("ISSUE_PAGING_NUM").MustInt(10)
sec = Cfg.Section("picture")
PictureService = sec.Key("SERVICE").In("server", []string{"server"})
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars")

File diff suppressed because one or more lines are too long

View File

@ -78,8 +78,20 @@
}
}
}
.filter.menu .label.color {
padding: 0 8px;
.filter.menu {
.label.color {
margin-left: 17px;
padding: 0 8px;
}
.octicon {
float: left;
margin-left: -5px;
margin-right: -7px;
}
.menu {
max-height: 300px;
overflow-x: auto;
}
}
.type.item .menu {
right: 0!important;

View File

@ -74,32 +74,26 @@ func Issues(ctx *middleware.Context) {
return
}
var assigneeId, posterId int64
var filterMode int
var assigneeID, posterID int64
filterMode := models.FM_ALL
switch viewType {
case "assigned":
assigneeId = ctx.User.Id
assigneeID = ctx.User.Id
filterMode = models.FM_ASSIGN
case "created_by":
posterId = ctx.User.Id
posterID = ctx.User.Id
filterMode = models.FM_CREATE
case "mentioned":
filterMode = models.FM_MENTION
}
repo := ctx.Repo.Repository
var mid int64
midx := ctx.QueryInt64("milestone")
if midx > 0 {
mile, err := models.GetMilestoneByIndex(repo.Id, midx)
if err != nil {
ctx.Handle(500, "GetMilestoneByIndex: %v", err)
return
}
mid = mile.Id
var uid int64 = -1
if ctx.IsSigned {
uid = ctx.User.Id
}
repo := ctx.Repo.Repository
milestoneID := ctx.QueryInt64("milestone")
page := ctx.QueryInt("page")
if page <= 1 {
page = 1
@ -114,15 +108,15 @@ func Issues(ctx *middleware.Context) {
selectLabels := ctx.Query("labels")
// Get issues.
issues, err := models.GetIssues(assigneeId, repo.Id, posterId, mid, page,
isShowClosed, selectLabels, ctx.Query("sortType"))
issues, err := models.GetIssues(uid, assigneeID, repo.Id, posterID, milestoneID,
page, isShowClosed, filterMode == models.FM_MENTION, selectLabels, ctx.Query("sortType"))
if err != nil {
ctx.Handle(500, "GetIssues: %v", err)
return
}
// Get issue-user pairs.
pairs, err := models.GetIssueUserPairs(repo.Id, posterId, isShowClosed)
pairs, err := models.GetIssueUserPairs(repo.Id, posterID, isShowClosed)
if err != nil {
ctx.Handle(500, "GetIssueUserPairs: %v", err)
return
@ -130,36 +124,31 @@ func Issues(ctx *middleware.Context) {
// Get posters.
for i := range issues {
if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
if err = issues[i].GetLabels(); err != nil {
ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
if ctx.IsSigned {
idx := models.PairsContains(pairs, issues[i].ID, ctx.User.Id)
if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
continue
}
if idx > -1 {
issues[i].IsRead = pairs[idx].IsRead
} else {
issues[i].IsRead = true
}
if !ctx.IsSigned {
issues[i].IsRead = true
continue
}
if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
// Check read status.
idx := models.PairsContains(pairs, issues[i].ID, ctx.User.Id)
if idx > -1 {
issues[i].IsRead = pairs[idx].IsRead
} else {
issues[i].IsRead = true
}
}
var uid int64 = -1
if ctx.User != nil {
uid = ctx.User.Id
}
issueStats := models.GetIssueStats(repo.Id, uid, isShowClosed, filterMode)
issueStats := models.GetIssueStats(repo.Id, uid, com.StrTo(selectLabels).MustInt64(), isShowClosed, filterMode)
ctx.Data["IssueStats"] = issueStats
ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
ctx.Data["ViewType"] = viewType
@ -169,8 +158,10 @@ func Issues(ctx *middleware.Context) {
ctx.Data["State"] = "closed"
ctx.Data["ShowCount"] = issueStats.ClosedCount
} else {
ctx.Data["State"] = "open"
ctx.Data["ShowCount"] = issueStats.OpenCount
}
ctx.HTML(200, ISSUES)
}

View File

@ -1 +1 @@
0.6.2.0724 Beta
0.6.2.0725 Beta

View File

@ -4,20 +4,18 @@
<div class="ui middle page grid body">
<div class="navbar">
{{template "repo/issue/navbar" .}}
{{if .IsRepositoryAdmin}}
<div class="ui right floated secondary menu">
<a class="ui green button" href="{{$.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a>
</div>
{{end}}
</div>
<div class="ui divider"></div>
<div class="ui left">
<div class="ui tiny buttons">
<a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}">
<a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{$.ViewType}}&state=open&labels={{.SelectLabels}}">
<i class="octicon octicon-issue-opened"></i>
{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
</a>
<a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}&state=closed">
<a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}&state=closed&labels={{.SelectLabels}}">
<i class="octicon octicon-issue-closed"></i>
{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}}
</a>
@ -30,12 +28,13 @@
<i class="dropdown icon"></i>
</span>
<div class="menu">
<a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
<a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
{{end}}
</div>
</div>
<div class="ui {{if not .Milestones}}disabled{{end}} pointing dropdown jump item">
<!-- <div class="ui {{if not .Milestones}}disabled{{end}} pointing dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_milestone"}}
<i class="dropdown icon"></i>
@ -45,8 +44,8 @@
<a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a>
{{end}}
</div>
</div>
<div class="ui {{if not .Assignees}}disabled{{end}} pointing dropdown jump item">
</div> -->
<!-- <div class="ui {{if not .Assignees}}disabled{{end}} pointing dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_assignee"}}
<i class="dropdown icon"></i>
@ -56,17 +55,17 @@
<a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a>
{{end}}
</div>
</div>
</div> -->
<div class="ui pointing dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_type"}}
<i class="dropdown icon"></i>
</span>
<div class="menu">
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a>
<a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=all&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=assigned&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=created_by&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=mentioned&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a>
</div>
</div>
</div>
@ -77,6 +76,11 @@
<li class="item">
<div class="ui {{if .IsRead}}black{{else}}green{{end}} label">#{{.Index}}</div>
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Name}}</a>
{{range .Labels}}
<a class="ui label" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}" style="background-color: {{.Color}}">{{.Name}}</a>
{{end}}
{{if .NumComments}}<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span>{{end}}
<p class="desc">{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.Name|Str2html}}</p>
</li>