Browse Source

add milestone changed traker on issue view (#804)

tags/v1.1.0
Lunny Xiao 7 years ago
parent
commit
081485ecfd

+ 21
- 8
models/issue.go View File

IsPull bool IsPull bool
} }


func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
opts.Issue.Index = opts.Repo.NextIssueIndex() opts.Issue.Index = opts.Repo.NextIssueIndex()


if milestone != nil { if milestone != nil {
opts.Issue.MilestoneID = milestone.ID opts.Issue.MilestoneID = milestone.ID
opts.Issue.Milestone = milestone opts.Issue.Milestone = milestone
if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil {
return err
}
} }
} }


return err return err
} }


if opts.Issue.MilestoneID > 0 {
if err = changeMilestoneAssign(e, doer, opts.Issue, -1); err != nil {
return err
}
}

if opts.IsPull { if opts.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID) _, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
} else { } else {
return err return err
} }


if err = newIssue(sess, NewIssueOptions{
if err = newIssue(sess, issue.Poster, NewIssueOptions{
Repo: repo, Repo: repo,
Issue: issue, Issue: issue,
LableIDs: labelIDs, LableIDs: labelIDs,
return sess.Commit() return sess.Commit()
} }


func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error {
func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error {
if oldMilestoneID > 0 { if oldMilestoneID > 0 {
m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID)
if err != nil { if err != nil {
} }
} }


if err := issue.loadRepo(e); err != nil {
return err
}

if oldMilestoneID > 0 || issue.MilestoneID > 0 {
if _, err := createMilestoneComment(e, doer, issue.Repo, issue, oldMilestoneID, issue.MilestoneID); err != nil {
return err
}
}

return updateIssue(e, issue) return updateIssue(e, issue)
} }


// ChangeMilestoneAssign changes assignment of milestone for issue. // ChangeMilestoneAssign changes assignment of milestone for issue.
func ChangeMilestoneAssign(issue *Issue, oldMilestoneID int64) (err error) {
func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {
return err return err
} }


if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil {
if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil {
return err return err
} }
return sess.Commit() return sess.Commit()

+ 66
- 15
models/issue_comment.go View File

CommentTypePullRef CommentTypePullRef
// Labels changed // Labels changed
CommentTypeLabel CommentTypeLabel
// Milestone changed
CommentTypeMilestone
) )


// CommentTag defines comment tag type // CommentTag defines comment tag type
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"` Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
CommitID int64
LabelID int64 LabelID int64
Label *Label `xorm:"-"` Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
CommitID int64
Line int64 Line int64
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
return nil return nil
} }


// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func (c *Comment) LoadMilestone() error {
if c.OldMilestoneID > 0 {
var oldMilestone Milestone
has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone)
if err != nil {
return err
} else if !has {
return ErrMilestoneNotExist{
ID: c.OldMilestoneID,
}
}
c.OldMilestone = &oldMilestone
}

if c.MilestoneID > 0 {
var milestone Milestone
has, err := x.ID(c.MilestoneID).Get(&milestone)
if err != nil {
return err
} else if !has {
return ErrMilestoneNotExist{
ID: c.MilestoneID,
}
}
c.Milestone = &milestone
}
return nil
}

// MailParticipants sends new comment emails to repository watchers // MailParticipants sends new comment emails to repository watchers
// and mentioned people. // and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
LabelID = opts.Label.ID LabelID = opts.Label.ID
} }
comment := &Comment{ comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
} }
if _, err = e.Insert(comment); err != nil { if _, err = e.Insert(comment); err != nil {
return nil, err return nil, err
}) })
} }


func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeMilestone,
Doer: doer,
Repo: repo,
Issue: issue,
OldMilestoneID: oldMilestoneID,
MilestoneID: milestoneID,
})
}

// CreateCommentOptions defines options for creating comment // CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct { type CreateCommentOptions struct {
Type CommentType Type CommentType
Issue *Issue Issue *Issue
Label *Label Label *Label


CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
OldMilestoneID int64
MilestoneID int64
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
} }


// CreateComment creates comment of issue or commit. // CreateComment creates comment of issue or commit.

+ 1
- 1
models/pull.go View File

return err return err
} }


if err = newIssue(sess, NewIssueOptions{
if err = newIssue(sess, pull.Poster, NewIssueOptions{
Repo: repo, Repo: repo,
Issue: pull, Issue: pull,
LableIDs: labelIDs, LableIDs: labelIDs,

+ 3
- 0
options/locale/locale_en-US.ini View File

issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v
issues.add_label_at = `added the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s` issues.add_label_at = `added the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s`
issues.remove_label_at = `removed the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s` issues.remove_label_at = `removed the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s`
issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`
issues.open_tab = %d Open issues.open_tab = %d Open
issues.close_tab = %d Closed issues.close_tab = %d Closed
issues.filter_label = Label issues.filter_label = Label

+ 3
- 0
options/locale/locale_zh-CN.ini View File

issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v
issues.add_label_at = ` %[4]s 添加了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>` issues.add_label_at = ` %[4]s 添加了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>`
issues.remove_label_at = ` %[4]s 删除了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>` issues.remove_label_at = ` %[4]s 删除了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>`
issues.add_milestone_at = ` %[2]s 添加了里程碑 <b>%[1]s</b>`
issues.change_milestone_at = `%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
issues.remove_milestone_at = `%[2]s 删除了里程碑 <b>%[1]s</b>`
issues.open_tab=%d 个开启中 issues.open_tab=%d 个开启中
issues.close_tab=%d 个已关闭 issues.close_tab=%d 个已关闭
issues.filter_label=标签筛选 issues.filter_label=标签筛选

+ 6
- 0
public/js/index.js View File

var $list = $('.ui' + select_id + '.list'); var $list = $('.ui' + select_id + '.list');
var hasUpdateAction = $menu.data('action') == 'update'; var hasUpdateAction = $menu.data('action') == 'update';


$(select_id).dropdown('setting', 'onHide', function(){
if (hasUpdateAction) {
location.reload();
}
});

$menu.find('.item:not(.no-select)').click(function () { $menu.find('.item:not(.no-select)').click(function () {
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
$(this).removeClass('selected active') $(this).removeClass('selected active')

+ 1
- 1
routers/api/v1/repo/issue.go View File

issue.MilestoneID != *form.Milestone { issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone issue.MilestoneID = *form.Milestone
if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Error(500, "ChangeMilestoneAssign", err) ctx.Error(500, "ChangeMilestoneAssign", err)
return return
} }

+ 1
- 1
routers/api/v1/repo/pull.go View File

issue.MilestoneID != form.Milestone { issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone issue.MilestoneID = form.Milestone
if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Error(500, "ChangeMilestoneAssign", err) ctx.Error(500, "ChangeMilestoneAssign", err)
return return
} }

+ 22
- 13
routers/repo/issue.go View File

return nil, 0, 0 return nil, 0, 0
} }


// Check labels.
labelIDs, err := base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
if err != nil {
return nil, 0, 0
}
labelIDMark := base.Int64sToMap(labelIDs)
var labelIDs []int64
hasSelected := false hasSelected := false
for i := range labels {
if labelIDMark[labels[i].ID] {
labels[i].IsChecked = true
hasSelected = true
// Check labels.
if len(form.LabelIDs) > 0 {
labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
if err != nil {
return nil, 0, 0
}
labelIDMark := base.Int64sToMap(labelIDs)

for i := range labels {
if labelIDMark[labels[i].ID] {
labels[i].IsChecked = true
hasSelected = true
}
} }
} }

ctx.Data["Labels"] = labels
ctx.Data["HasSelectedLabel"] = hasSelected ctx.Data["HasSelectedLabel"] = hasSelected
ctx.Data["label_ids"] = form.LabelIDs ctx.Data["label_ids"] = form.LabelIDs
ctx.Data["Labels"] = labels


// Check milestone. // Check milestone.
milestoneID := form.MilestoneID milestoneID := form.MilestoneID
ctx.Handle(500, "LoadLabel", err) ctx.Handle(500, "LoadLabel", err)
return return
} }
} else if comment.Type == models.CommentTypeMilestone {
if err = comment.LoadMilestone(); err != nil {
ctx.Handle(500, "LoadMilestone", err)
return
}
} }
} }


canDelete := false canDelete := false


if ctx.IsSigned && pull.HeadBranch != "master" { if ctx.IsSigned && pull.HeadBranch != "master" {

if err := pull.GetHeadRepo(); err != nil { if err := pull.GetHeadRepo(); err != nil {
log.Error(4, "GetHeadRepo: %v", err) log.Error(4, "GetHeadRepo: %v", err)
} else if ctx.User.IsWriterOfRepo(pull.HeadRepo) { } else if ctx.User.IsWriterOfRepo(pull.HeadRepo) {


// Not check for invalid milestone id and give responsibility to owners. // Not check for invalid milestone id and give responsibility to owners.
issue.MilestoneID = milestoneID issue.MilestoneID = milestoneID
if err := models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Handle(500, "ChangeMilestoneAssign", err) ctx.Handle(500, "ChangeMilestoneAssign", err)
return return
} }

+ 10
- 1
templates/repo/issue/view_content.tmpl View File

<img src="{{.Poster.RelAvatarLink}}"> <img src="{{.Poster.RelAvatarLink}}">
</a> </a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span>
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span>
</div>
{{else if eq .Type 8}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" .OldMilestone.Name .Milestone.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" .OldMilestone.Name $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" .Milestone.Name $createdStr | Safe}}{{end}}</span>
</div> </div>
{{end}} {{end}}



Loading…
Cancel
Save