Browse Source

Almost done issue label #200

Unknown 11 years ago
parent
commit
b1bdbd7f94
9 changed files with 266 additions and 96 deletions
  1. 4 2
      cmd/web.go
  2. 1 1
      gogs.go
  3. 133 43
      models/issue.go
  4. 2 9
      routers/dashboard.go
  5. 0 1
      routers/install.go
  6. 109 7
      routers/repo/issue.go
  7. 0 11
      templates/help.tmpl
  8. 2 2
      templates/issue/list.tmpl
  9. 15 20
      templates/issue/view.tmpl

+ 4 - 2
cmd/web.go

@@ -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)

+ 1 - 1
gogs.go

@@ -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

+ 133 - 43
models/issue.go

@@ -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")
 )
 
@@ -28,14 +29,15 @@ type Issue struct {
 	Name            string
 	Repo            *Repository `xorm:"-"`
 	PosterId        int64
-	Poster          *User `xorm:"-"`
+	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
-}
-
 // _________                                       __
 // \_   ___ \  ____   _____   _____   ____   _____/  |_
 // /    \  \/ /  _ \ /     \ /     \_/ __ \ /    \   __\

+ 2 - 9
routers/dashboard.go

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

+ 0 - 1
routers/install.go

@@ -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":

+ 109 - 7
routers/repo/issue.go

@@ -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) {

+ 0 - 11
templates/help.tmpl

@@ -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" .}}

+ 2 - 2
templates/issue/list.tmpl

@@ -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>

+ 15 - 20
templates/issue/view.tmpl

@@ -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>