1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-12-22 12:54:53 -05:00

Almost done issue label #200

This commit is contained in:
Unknown 2014-05-24 02:31:58 -04:00
parent 50ba08e2c6
commit b1bdbd7f94
9 changed files with 266 additions and 96 deletions

View file

@ -76,7 +76,6 @@ func runWeb(*cli.Context) {
m.Get("/issues", reqSignIn, user.Issues)
m.Get("/pulls", reqSignIn, user.Pulls)
m.Get("/stars", reqSignIn, user.Stars)
m.Get("/help", routers.Help)
m.Group("/api", func(r martini.Router) {
m.Group("/v1", func(r martini.Router) {
@ -191,9 +190,12 @@ func runWeb(*cli.Context) {
r.Get("/new", repo.CreateIssue)
r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
r.Post("/:index/assignee", repo.UpdateAssignee)
r.Post("/:index/label", repo.UpdateIssueLabel)
r.Post("/:index/milestone", repo.UpdateIssueMilestone)
r.Post("/:index/assignee", repo.UpdateAssignee)
r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
r.Post("/labels/delete", repo.DeleteLabel)
r.Get("/milestones", repo.Milestones)
r.Get("/milestones/new", repo.NewMilestone)
r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)

View file

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/base"
)
const APP_VER = "0.3.5.0521 Alpha"
const APP_VER = "0.3.5.0523 Alpha"
func init() {
base.AppVer = APP_VER

View file

@ -17,6 +17,7 @@ import (
var (
ErrIssueNotExist = errors.New("Issue does not exist")
ErrLabelNotExist = errors.New("Label does not exist")
ErrMilestoneNotExist = errors.New("Milestone does not exist")
)
@ -29,13 +30,14 @@ type Issue struct {
Repo *Repository `xorm:"-"`
PosterId int64
Poster *User `xorm:"-"`
LabelIds string `xorm:"TEXT"`
Labels []*Label `xorm:"-"`
MilestoneId int64
AssigneeId int64
Assignee *User `xorm:"-"`
IsRead bool `xorm:"-"`
IsPull bool // Indicates whether is a pull request or not.
IsClosed bool
Labels string `xorm:"TEXT"`
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Priority int
@ -54,11 +56,37 @@ func (i *Issue) GetPoster() (err error) {
return err
}
func (i *Issue) GetLabels() error {
if len(i.LabelIds) < 3 {
return nil
}
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
i.Labels = make([]*Label, 0, len(strIds))
for _, strId := range strIds {
id, _ := base.StrTo(strId).Int64()
if id > 0 {
l, err := GetLabelById(id)
if err != nil {
if err == ErrLabelNotExist {
continue
}
return err
}
i.Labels = append(i.Labels, l)
}
}
return nil
}
func (i *Issue) GetAssignee() (err error) {
if i.AssigneeId == 0 {
return nil
}
i.Assignee, err = GetUserById(i.AssigneeId)
if err == ErrUserNotExist {
return nil
}
return err
}
@ -108,7 +136,7 @@ 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, labels, sortType string) ([]Issue, error) {
func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
sess := orm.Limit(20, (page-1)*20)
if rid > 0 {
@ -127,9 +155,9 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy
sess.And("milestone_id=?", mid)
}
if len(labels) > 0 {
for _, label := range strings.Split(labels, ",") {
sess.And("labels like '%$" + label + "|%'")
if len(labelIds) > 0 {
for _, label := range strings.Split(labelIds, ",") {
sess.And("label_ids like '%$" + label + "|%'")
}
}
@ -155,6 +183,13 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy
return issues, err
}
// GetIssuesByLabel returns a list of issues by given label and repository.
func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
issues := make([]*Issue, 0, 10)
err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
return issues, err
}
// GetIssueCountByPoster returns number of issues of repository by poster.
func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
@ -175,7 +210,6 @@ type IssueUser struct {
IssueId int64
RepoId int64
MilestoneId int64
Labels string `xorm:"TEXT"`
IsRead bool
IsAssigned bool
IsMentioned bool
@ -400,6 +434,98 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
return nil
}
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
// | |___ / __ \| \_\ \ ___/| |__
// |_______ (____ /___ /\___ >____/
// \/ \/ \/ \/
// Label represents a label of repository for issues.
type Label struct {
Id int64
RepoId int64 `xorm:"INDEX"`
Name string
Color string `xorm:"VARCHAR(7)"`
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsChecked bool `xorm:"-"`
}
// CalOpenIssues calculates the open issues of label.
func (m *Label) CalOpenIssues() {
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
}
// NewLabel creates new label of repository.
func NewLabel(l *Label) error {
_, err := orm.Insert(l)
return err
}
// GetLabelById returns a label by given ID.
func GetLabelById(id int64) (*Label, error) {
l := &Label{Id: id}
has, err := orm.Get(l)
if err != nil {
return nil, err
} else if !has {
return nil, ErrLabelNotExist
}
return l, nil
}
// GetLabels returns a list of labels of given repository ID.
func GetLabels(repoId int64) ([]*Label, error) {
labels := make([]*Label, 0, 10)
err := orm.Where("repo_id=?", repoId).Find(&labels)
return labels, err
}
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
_, err := orm.Id(l.Id).Update(l)
return err
}
// DeleteLabel delete a label of given repository.
func DeleteLabel(repoId int64, strId string) error {
id, _ := base.StrTo(strId).Int64()
l, err := GetLabelById(id)
if err != nil {
if err == ErrLabelNotExist {
return nil
}
return err
}
issues, err := GetIssuesByLabel(repoId, strId)
if err != nil {
return err
}
sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
for _, issue := range issues {
issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
sess.Rollback()
return err
}
}
if _, err = sess.Delete(l); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
}
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
@ -611,42 +737,6 @@ func DeleteMilestone(m *Milestone) (err error) {
return sess.Commit()
}
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
// | |___ / __ \| \_\ \ ___/| |__
// |_______ (____ /___ /\___ >____/
// \/ \/ \/ \/
// Label represents a label of repository for issues.
type Label struct {
Id int64
RepoId int64 `xorm:"INDEX"`
Name string
Color string `xorm:"VARCHAR(7)"`
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
}
// CalOpenIssues calculates the open issues of label.
func (m *Label) CalOpenIssues() {
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
}
// NewLabel creates new label of repository.
func NewLabel(l *Label) error {
_, err := orm.Insert(l)
return err
}
// GetLabels returns a list of labels of given repository ID.
func GetLabels(repoId int64) ([]*Label, error) {
labels := make([]*Label, 0, 10)
err := orm.Where("repo_id=?", repoId).Find(&labels)
return labels, err
}
// _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\

View file

@ -32,9 +32,8 @@ func Home(ctx *middleware.Context) {
}
for _, repo := range repos {
repo.Owner, err = models.GetUserById(repo.OwnerId)
if err != nil {
ctx.Handle(500, "dashboard.Home(GetUserById)", err)
if err = repo.GetOwner(); err != nil {
ctx.Handle(500, "dashboard.Home(GetOwner)", err)
return
}
}
@ -43,12 +42,6 @@ func Home(ctx *middleware.Context) {
ctx.HTML(200, "home")
}
func Help(ctx *middleware.Context) {
ctx.Data["PageIsHelp"] = true
ctx.Data["Title"] = "Help"
ctx.HTML(200, "help")
}
func NotFound(ctx *middleware.Context) {
ctx.Data["PageIsNotFound"] = true
ctx.Data["Title"] = "Page Not Found"

View file

@ -25,7 +25,6 @@ import (
"github.com/gogits/gogs/modules/social"
)
// Check run mode(Default of martini is Dev).
func checkRunMode() {
switch base.Cfg.MustValue("", "RUN_MODE") {
case "prod":

View file

@ -197,7 +197,7 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
PosterId: ctx.User.Id,
MilestoneId: form.MilestoneId,
AssigneeId: form.AssigneeId,
Labels: form.Labels,
LabelIds: form.Labels,
Content: form.Content,
}
if err := models.NewIssue(issue); err != nil {
@ -269,6 +269,17 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
}
func checkLabels(labels, allLabels []*models.Label) {
for _, l := range labels {
for _, l2 := range allLabels {
if l.Id == l2.Id {
l2.IsChecked = true
break
}
}
}
}
func ViewIssue(ctx *middleware.Context, params martini.Params) {
idx, _ := base.StrTo(params["index"]).Int64()
if idx == 0 {
@ -286,6 +297,19 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return
}
// Get labels.
if err = issue.GetLabels(); err != nil {
ctx.Handle(500, "issue.ViewIssue(GetLabels)", err)
return
}
labels, err := models.GetLabels(ctx.Repo.Repository.Id)
if err != nil {
ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err)
return
}
checkLabels(issue.Labels, labels)
ctx.Data["Labels"] = labels
// Get assigned milestone.
if issue.MilestoneId > 0 {
ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId)
@ -364,13 +388,13 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
}
func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
idx, err := base.StrTo(params["index"]).Int()
if err != nil {
idx, _ := base.StrTo(params["index"]).Int64()
if idx <= 0 {
ctx.Error(404)
return
}
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(idx))
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
if err != nil {
if err == models.ErrIssueNotExist {
ctx.Handle(404, "issue.UpdateIssue", err)
@ -381,14 +405,14 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
}
if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
ctx.Handle(404, "issue.UpdateIssue", nil)
ctx.Error(403)
return
}
issue.Name = form.IssueName
issue.MilestoneId = form.MilestoneId
issue.AssigneeId = form.AssigneeId
issue.Labels = form.Labels
issue.LabelIds = form.Labels
issue.Content = form.Content
// try get content from text, ignore conflict with preview ajax
if form.Content == "" {
@ -406,6 +430,55 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
})
}
func UpdateIssueLabel(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsOwner {
ctx.Error(403)
return
}
idx, _ := base.StrTo(params["index"]).Int64()
if idx <= 0 {
ctx.Error(404)
return
}
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
if err != nil {
if err == models.ErrIssueNotExist {
ctx.Handle(404, "issue.UpdateIssueLabel", err)
} else {
ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
}
return
}
isAttach := ctx.Query("action") == "attach"
labelStrId := ctx.Query("id")
isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|")
isNeedUpdate := false
if isAttach {
if !isHad {
issue.LabelIds += "$" + labelStrId + "|"
isNeedUpdate = true
}
} else {
if isHad {
issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1)
isNeedUpdate = true
}
}
if isNeedUpdate {
if err = models.UpdateIssue(issue); err != nil {
ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err)
return
}
}
ctx.JSON(200, map[string]interface{}{
"ok": true,
})
}
func UpdateIssueMilestone(ctx *middleware.Context) {
if !ctx.Repo.IsOwner {
ctx.Error(403)
@ -622,8 +695,37 @@ func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
ctx.Redirect(ctx.Repo.RepoLink + "/issues")
}
func UpdateLabel(ctx *middleware.Context, params martini.Params) {
func UpdateLabel(ctx *middleware.Context, params martini.Params, form auth.CreateLabelForm) {
id, _ := base.StrTo(ctx.Query("id")).Int64()
if id == 0 {
ctx.Error(404)
return
}
l := &models.Label{
Id: id,
Name: form.Title,
Color: form.Color,
}
if err := models.UpdateLabel(l); err != nil {
ctx.Handle(500, "issue.UpdateLabel(UpdateLabel)", err)
return
}
ctx.Redirect(ctx.Repo.RepoLink + "/issues")
}
func DeleteLabel(ctx *middleware.Context) {
strIds := strings.Split(ctx.Query("remove"), ",")
for _, strId := range strIds {
if err := models.DeleteLabel(ctx.Repo.Repository.Id, strId); err != nil {
ctx.Handle(500, "issue.DeleteLabel(DeleteLabel)", err)
return
}
}
ctx.JSON(200, map[string]interface{}{
"ok": true,
})
}
func Milestones(ctx *middleware.Context) {

View file

@ -1,11 +0,0 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="body-nav">
<div class="container">
<h3>Help</h3>
</div>
</div>
<div id="body" class="container" data-page="user">
{{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}}
</div>
{{template "base/footer" .}}

View file

@ -15,7 +15,7 @@
</div>
<div class="label-filter">
<h4>Label</h4>
<ul class="list-unstyled" id="label-list" data-ajax="/{url}">
<ul class="list-unstyled" id="label-list" data-ajax="{{$.RepoLink}}/issues/labels/delete">
{{range .Labels}}
<li class="label-item" id="label-{{.Id}}" data-id="{{.Id}}"><a href="#">
<span class="pull-right count">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</span>
@ -60,7 +60,7 @@
{{template "base/alert" .}}
<div class="filter-option">
<div class="btn-group">
<a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}">{{..IssueStats.OpenCount}} Open</a>
<a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}">{{.IssueStats.OpenCount}} Open</a>
<a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}&state=closed">{{.IssueStats.ClosedCount}} Closed</a>
</div>
</div>

View file

@ -118,7 +118,7 @@
</div>
<div class="issue-bar col-md-2">
<div class="labels" data-ajax="{url}">
<div class="labels" data-ajax="{{.Issue.Index}}/label">
<div class="pull-right action">
<button class="btn btn-default btn-sm" data-toggle="dropdown">
<i class="fa fa-tags"></i>
@ -126,26 +126,24 @@
</button>
<div class="dropdown-menu dropdown-menu-right no">
<ul class="list-unstyled">
<li class="checked" data-id="1">
<span class="check pull-left"><i class="fa fa-check"></i></span>
<span class="color" style="background-color: #f59e00"></span>
<span class="name">bug</span>
</li>
<li class="no-checked" data-id="2">
<span class="color" style="background-color: #f59e00"></span>
<span class="name">bug</span>
</li>
<li class="no-checked" data-id="3">
<span class="color" style="background-color: #f59e00"></span>
<span class="name">bug</span>
{{range .Labels}}
<li class="{{if not .IsChecked}}no-{{end}}checked" data-id="{{.Id}}">
{{if .IsChecked}}<span class="check pull-left"><i class="fa fa-check"></i></span>{{end}}
<span class="color" style="background-color: {{.Color}}"></span>
<span class="name">{{.Name}}</span>
</li>
{{end}}
</ul>
</div>
</div>
<h4>Labels</h4>
<p id="label-1" class="label-item label-white" style="background-color: #e75316"><strong>bug</strong></p>
<p id="label-2" class="label-item label-white" style="background-color: #e8f0ff"><strong>bug</strong></p>
<p>Not yet</p>
{{if .Issue.Labels}}
{{range .Issue.Labels}}
<p id="label-{{.Id}}" class="label-item label-white" style="background-color: {{.Color}}"><strong>{{.Name}}</strong></p>
{{end}}
{{else}}
<p>None yet</p>
{{end}}
</div>
<div class="milestone" data-milestone="{{.Milestone.Id}}" data-ajax="{{.Issue.Index}}/milestone">
<div class="pull-right action">
@ -223,10 +221,7 @@
<h4>Assignee</h4>
<p>{{if .Issue.Assignee}}<img src="{{.Issue.Assignee.AvatarLink}}"><strong>{{.Issue.Assignee.Name}}</strong>{{else}}No one assigned{{end}}</p>
</div>
</div><!--
<div class="col-md-3">
label dashboard
</div>-->
</div>
</div>
</div>
</div>