Skip to content

Add severity/priority feature for issues #2616 #10035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 151 additions & 17 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ type Issue struct {
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
Severity string `xorm:"VARCHAR(6) INDEX DEFAULT 'Low'"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
StateType api.StateType `xorm:"VARCHAR(11) INDEX DEFAULT 'open'"`
AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string

Expand Down Expand Up @@ -355,10 +357,7 @@ func (issue *Issue) PatchURL() string {

// State returns string representation of issue status.
func (issue *Issue) State() api.StateType {
if issue.IsClosed {
return api.StateClosed
}
return api.StateOpen
return issue.StateType
}

// APIFormat assumes some fields assigned with values:
Expand Down Expand Up @@ -756,11 +755,13 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (er
issue.IsClosed = isClosed
if isClosed {
issue.ClosedUnix = timeutil.TimeStampNow()
issue.StateType = api.StateClosed
} else {
issue.ClosedUnix = 0
issue.StateType = api.StateOpen
}

if err = updateIssueCols(e, issue, "is_closed", "closed_unix"); err != nil {
if err = updateIssueCols(e, issue, "is_closed", "closed_unix", "state_type"); err != nil {
return err
}

Expand Down Expand Up @@ -852,6 +853,47 @@ func (issue *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
return nil
}

func (issue *Issue) changeState(e *xorm.Session, state api.StateType) (err error) {
// Reload the issue
currentIssue, err := getIssueByID(e, issue.ID)
if err != nil {
return err
}

// Nothing should be performed if current state is same as target state
if currentIssue.StateType == state {
return nil
}

issue.StateType = state

if err = updateIssueCols(e, issue, "state_type"); err != nil {
return err
}

return nil
}

// ChangeState changes issue state to open, in-progress, review or closed.
func (issue *Issue) ChangeState(state api.StateType) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

if err = issue.changeState(sess, state); err != nil {
return err
}

if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
sess.Close()

return nil
}

// ChangeTitle changes the title of this issue, as the given user.
func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) {
sess := x.NewSession()
Expand Down Expand Up @@ -1010,6 +1052,7 @@ func (issue *Issue) GetLastEventLabelFake() string {
type NewIssueOptions struct {
Repo *Repository
Issue *Issue
Severity string
LabelIDs []int64
AssigneeIDs []int64
Attachments []string // In UUID format.
Expand All @@ -1018,6 +1061,7 @@ type NewIssueOptions struct {

func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
opts.Issue.StateType = api.StateOpen

if opts.Issue.MilestoneID > 0 {
milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID)
Expand Down Expand Up @@ -1185,6 +1229,7 @@ func newIssueAttempt(repo *Repository, issue *Issue, labelIDs []int64, assigneeI
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
Severity: issue.Severity,
Attachments: uuids,
AssigneeIDs: assigneeIDs,
}); err != nil {
Expand Down Expand Up @@ -1280,9 +1325,11 @@ type IssuesOptions struct {
MilestoneID int64
Page int
PageSize int
StateType api.StateType
IsClosed util.OptionalBool
IsPull util.OptionalBool
LabelIDs []int64
Severity string
SortType string
IssueIDs []int64
}
Expand Down Expand Up @@ -1332,6 +1379,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
sess.In("issue.repo_id", opts.RepoIDs)
}

sess.And("issue.state_type=?", opts.StateType)

switch opts.IsClosed {
case util.OptionalBoolTrue:
sess.And("issue.is_closed=?", true)
Expand All @@ -1354,6 +1403,10 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
And("issue_user.uid = ?", opts.MentionedID)
}

if opts.Severity != "" {
sess.And("issue.severity=?", opts.Severity)
}

if opts.MilestoneID > 0 {
sess.And("issue.milestone_id=?", opts.MilestoneID)
}
Expand Down Expand Up @@ -1461,11 +1514,11 @@ func UpdateIssueMentions(ctx DBContext, issueID int64, mentions []*User) error {

// IssueStats represents issue statistic information.
type IssueStats struct {
OpenCount, ClosedCount int64
YourRepositoriesCount int64
AssignCount int64
CreateCount int64
MentionCount int64
OpenCount, InProgressCount, ReviewCount, ClosedCount int64
YourRepositoriesCount int64
AssignCount int64
CreateCount int64
MentionCount int64
}

// Filter modes.
Expand All @@ -1490,6 +1543,7 @@ func parseCountResult(results []map[string][]byte) int64 {
type IssueStatsOptions struct {
RepoID int64
Labels string
Severity string
MilestoneID int64
AssigneeID int64
MentionedID int64
Expand All @@ -1506,6 +1560,10 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
sess := x.
Where("issue.repo_id = ?", opts.RepoID)

if opts.Severity != "" {
sess.In("issue.severity", opts.Severity)
}

if len(opts.IssueIDs) > 0 {
sess.In("issue.id", opts.IssueIDs)
}
Expand Down Expand Up @@ -1553,7 +1611,19 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {

var err error
stats.OpenCount, err = countSession(opts).
And("issue.is_closed = ?", false).
And("issue.state_type = ?", api.StateOpen).
Count(new(Issue))
if err != nil {
return stats, err
}
stats.InProgressCount, err = countSession(opts).
And("issue.state_type = ?", api.StateInProgress).
Count(new(Issue))
if err != nil {
return stats, err
}
stats.ReviewCount, err = countSession(opts).
And("issue.state_type = ?", api.StateReview).
Count(new(Issue))
if err != nil {
return stats, err
Expand Down Expand Up @@ -1588,6 +1658,21 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
switch opts.FilterMode {
case FilterModeAll:
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateOpen).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.InProgressCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateInProgress).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ReviewCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateReview).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
Expand All @@ -1601,6 +1686,23 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
}
case FilterModeAssign:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateOpen).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.InProgressCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateInProgress).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ReviewCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateReview).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
Expand All @@ -1616,6 +1718,21 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
}
case FilterModeCreate:
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateOpen).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.InProgressCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateInProgress).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ReviewCount, err = x.Where(cond).And("is_closed = ?", false).
And("state_type = ?", api.StateReview).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
Expand All @@ -1629,6 +1746,23 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
}
case FilterModeMention:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateOpen).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.InProgressCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateInProgress).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ReviewCount, err = x.Where(cond).And("issue.is_closed = ?", false).
And("state_type = ?", api.StateReview).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
Expand Down
32 changes: 32 additions & 0 deletions models/issue_severity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package models

import (
"fmt"
"github.com/go-xorm/xorm"
)

// ChangeSeverity changes severity of issue.
func ChangeSeverity(issue *Issue, doer *User) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

if err = changeSeverity(sess, doer, issue); err != nil {
return err
}

if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
return nil
}

func changeSeverity(e *xorm.Session, doer *User, issue *Issue) error {
if err := updateIssueCols(e, issue, "severity"); err != nil {
return err
}

return nil
}
4 changes: 4 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ var migrations = []Migration{
NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser),
// v102 -> v103
NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest),
// v103 -> v103_1
NewMigration("add state_type column to issue", addStateTypeColumnToIssue),
// v103_1 -> v103_2
NewMigration("update state_type column from issue", updateStateTypeColumnFromIssue),
}

// Migrate database to current version
Expand Down
14 changes: 14 additions & 0 deletions models/migrations/v103_1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package migrations

import (
api "code.gitea.io/gitea/modules/structs"
"github.com/go-xorm/xorm"
)

func addStateTypeColumnToIssue(x *xorm.Engine) (err error) {
type Issue struct {
StateType api.StateType `xorm:"VARCHAR(11) INDEX DEFAULT 'open'"`
}

return x.Sync2(new(Issue))
}
17 changes: 17 additions & 0 deletions models/migrations/v103_2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package migrations

import (
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)

func updateStateTypeColumnFromIssue(x *xorm.Engine) (err error) {
if _, err := x.Exec("UPDATE issue SET state_type = 'closed' WHERE is_closed = true;"); err != nil {
log.Warn("UPDATE state_type: %v", err)
}
if _, err := x.Exec("UPDATE issue SET state_type = 'open' WHERE is_closed = false;"); err != nil {
log.Warn("UPDATE state_type: %v", err)
}

return nil
}
3 changes: 2 additions & 1 deletion modules/auth/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ type CreateIssueForm struct {
LabelIDs string `form:"label_ids"`
AssigneeIDs string `form:"assignee_ids"`
Ref string `form:"ref"`
SeverityID string
MilestoneID int64
AssigneeID int64
Content string
Expand All @@ -326,7 +327,7 @@ func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
// CreateCommentForm form for creating comment
type CreateCommentForm struct {
Content string
Status string `binding:"OmitEmpty;In(reopen,close)"`
Status string `binding:"OmitEmpty;In(reopen,in-progress,review,close)"`
Files []string
}

Expand Down
Loading