diff --git a/models/issue.go b/models/issue.go
index 7c2eecadbcf35..d6d083a6f0eec 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -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
@@ -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:
@@ -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
}
@@ -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()
@@ -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.
@@ -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)
@@ -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 {
@@ -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
}
@@ -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)
@@ -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)
}
@@ -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.
@@ -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
@@ -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)
}
@@ -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
@@ -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 {
@@ -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))
@@ -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 {
@@ -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))
diff --git a/models/issue_severity.go b/models/issue_severity.go
new file mode 100644
index 0000000000000..4dd3d7c6dd3d0
--- /dev/null
+++ b/models/issue_severity.go
@@ -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
+}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 96f7a77589012..40720e94fd6c0 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -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
diff --git a/models/migrations/v103_1.go b/models/migrations/v103_1.go
new file mode 100644
index 0000000000000..313c5ce882cce
--- /dev/null
+++ b/models/migrations/v103_1.go
@@ -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))
+}
diff --git a/models/migrations/v103_2.go b/models/migrations/v103_2.go
new file mode 100644
index 0000000000000..b90caa1f23b21
--- /dev/null
+++ b/models/migrations/v103_2.go
@@ -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
+}
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index a9985fdcbc8c8..c21478a094baa 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -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
@@ -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
}
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 58fd7344b4f27..a873fda767a92 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -14,12 +14,50 @@ type StateType string
const (
// StateOpen pr is opend
StateOpen StateType = "open"
+ // StateInProgress pr is in-progress
+ StateInProgress StateType = "in-progress"
+ // StateReview pr is review
+ StateReview StateType = "review"
// StateClosed pr is closed
StateClosed StateType = "closed"
// StateAll is all
StateAll StateType = "all"
)
+// Severity issue severity type
+type SeverityType struct {
+ Name string
+ Color string
+}
+type Severity string
+
+const (
+ SeverityLow Severity = "Low"
+ SeverityMedium Severity = "Medium"
+ SeverityHigh Severity = "High"
+ SeverityUrgent Severity = "Urgent"
+)
+
+var (
+ SeverityTypeLow = SeverityType{Name: "Low", Color: "#009800"}
+ SeverityTypeMedium = SeverityType{Name: "Medium", Color: "#fbca04"}
+ SeverityTypeHigh = SeverityType{Name: "High", Color: "#eb6420"}
+ SeverityTypeUrgent = SeverityType{Name: "Urgent", Color: "#e11d21"}
+
+ SeverityTypesMap = map[Severity]SeverityType{
+ SeverityLow: SeverityTypeLow,
+ SeverityMedium: SeverityTypeMedium,
+ SeverityHigh: SeverityTypeHigh,
+ SeverityUrgent: SeverityTypeUrgent,
+ }
+ SeverityTypesSlice = []SeverityType{
+ SeverityTypeLow,
+ SeverityTypeMedium,
+ SeverityTypeHigh,
+ SeverityTypeUrgent,
+ }
+)
+
// PullRequestMeta PR info if an issue is a PR
type PullRequestMeta struct {
HasMerged bool `json:"merged"`
@@ -38,13 +76,14 @@ type Issue struct {
Title string `json:"title"`
Body string `json:"body"`
Labels []*Label `json:"labels"`
+ Severity string `json:"severity"`
Milestone *Milestone `json:"milestone"`
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
- // Whether the issue is open or closed
+ // Whether the issue is open, in-progress, review or closed
//
// type: string
- // enum: open,closed
+ // enum: open, in-progress, review, closed
State StateType `json:"state"`
Comments int `json:"comments"`
// swagger:strfmt date-time
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 94ded93c59c83..027f9eac7f7c0 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -754,6 +754,7 @@ ext_issues.desc = Link to an external issue tracker.
issues.desc = Organize bug reports, tasks and milestones.
issues.new = New Issue
issues.new.title_empty = Title cannot be empty
+issues.severity = Severity
issues.new.labels = Labels
issues.new.no_label = No Label
issues.new.clear_labels = Clear labels
@@ -789,7 +790,11 @@ issues.remove_self_assignment = `removed their assignment %s`
issues.change_title_at = `changed title from %s to %s %s`
issues.delete_branch_at = `deleted branch %s %s`
issues.open_tab = %d Open
+issues.in_progress_tab = %d In-Progress
+issues.review_tab = %d Review
issues.close_tab = %d Closed
+issues.filter_severity = Severity
+issues.filter_severity_no_select = All severities
issues.filter_label = Label
issues.filter_label_no_select = All labels
issues.filter_milestone = Milestone
@@ -815,6 +820,8 @@ issues.filter_sort.feweststars = Fewest stars
issues.filter_sort.mostforks = Most forks
issues.filter_sort.fewestforks = Fewest forks
issues.action_open = Open
+issues.action_in_progress = In-Progress
+issues.action_review = Review
issues.action_close = Close
issues.action_label = Label
issues.action_milestone = Milestone
@@ -830,6 +837,8 @@ issues.closed_by_fake = closed %[1]s by %[2]s
issues.previous = Previous
issues.next = Next
issues.open_title = Open
+issues.in_progress_title = In-Progress
+issues.review_title = Review
issues.closed_title = Closed
issues.num_comments = %d comments
issues.commented_at = `commented %s`
@@ -837,7 +846,11 @@ issues.delete_comment_confirm = Are you sure you want to delete this comment?
issues.no_content = There is no content yet.
issues.close_issue = Close
issues.close_comment_issue = Comment and Close
+issues.review_comment_issue = Comment and Review
+issues.in_progress_comment_issue = Comment and In-Progress
issues.reopen_issue = Reopen
+issues.review_issue = Review
+issues.in_progress_issue = In-Progress
issues.reopen_comment_issue = Comment and Reopen
issues.create_comment = Comment
issues.closed_at = `closed %[2]s`
diff --git a/public/js/index.js b/public/js/index.js
index 966fc05eff753..0fe98b39caf91 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -262,7 +262,7 @@ function initRepoStatusChecker() {
location.reload();
return
}
-
+
setTimeout(function () {
initRepoStatusChecker()
}, 2000);
@@ -572,6 +572,10 @@ function initCommentForm() {
).then(reload);
}
switch (input_id) {
+ case '#severity_id':
+ $list.find('.selected').html('' +
+ htmlEncode($(this).text()) + '');
+ break;
case '#milestone_id':
$list.find('.selected').html('' +
htmlEncode($(this).text()) + '');
@@ -605,6 +609,7 @@ function initCommentForm() {
}
// Milestone and assignee
+ selectItem('.select-severity', '#severity_id');
selectItem('.select-milestone', '#milestone_id');
selectItem('.select-assignee', '#assignee_id');
}
@@ -931,17 +936,39 @@ function initRepository() {
// Change status
const $statusButton = $('#status-button');
+ const $statusButtonInProgress = $('#status-button-in-progress');
+ const $statusButtonReview = $('#status-button-review');
+ const $statusButtonClose = $('#status-button-close');
+
$('#comment-form .edit_area').keyup(function () {
if ($(this).val().length == 0) {
$statusButton.text($statusButton.data('status'))
+ $statusButtonInProgress.text($statusButtonInProgress.data('status'))
+ $statusButtonReview.text($statusButtonReview.data('status'))
+ $statusButtonClose.text($statusButtonClose.data('status'))
} else {
$statusButton.text($statusButton.data('status-and-comment'))
+ $statusButtonInProgress.text($statusButtonInProgress.data('status-and-comment'))
+ $statusButtonReview.text($statusButtonReview.data('status-and-comment'))
+ $statusButtonClose.text($statusButtonClose.data('status-and-comment'))
}
});
$statusButton.click(function () {
$('#status').val($statusButton.data('status-val'));
$('#comment-form').submit();
});
+ $statusButtonInProgress.click(function () {
+ $('#status').val($statusButtonInProgress.data('status-val'));
+ $('#comment-form').submit();
+ });
+ $statusButtonReview.click(function () {
+ $('#status').val($statusButtonReview.data('status-val'));
+ $('#comment-form').submit();
+ });
+ $statusButtonClose.click(function () {
+ $('#status').val($statusButtonClose.data('status-val'));
+ $('#comment-form').submit();
+ });
// Pull Request merge button
const $mergeButton = $('.merge-button > button');
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 8cbad47f2d8a6..aba6f7a28796a 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -107,6 +107,12 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
var err error
viewType := ctx.Query("type")
sortType := ctx.Query("sort")
+ severity := ctx.Query("severity")
+ stateType := ctx.Query("state")
+ if stateType == "" {
+ stateType = "open"
+ }
+
types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned"}
if !com.IsSliceContainsStr(types, viewType) {
viewType = "all"
@@ -163,6 +169,7 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
} else {
issueStats, err = models.GetIssueStats(&models.IssueStatsOptions{
RepoID: repo.ID,
+ Severity: severity,
Labels: selectLabels,
MilestoneID: milestoneID,
AssigneeID: assigneeID,
@@ -182,9 +189,14 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
}
var total int
- if !isShowClosed {
+ switch api.StateType(stateType) {
+ case api.StateOpen:
total = int(issueStats.OpenCount)
- } else {
+ case api.StateInProgress:
+ total = int(issueStats.InProgressCount)
+ case api.StateReview:
+ total = int(issueStats.ReviewCount)
+ case api.StateClosed:
total = int(issueStats.ClosedCount)
}
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
@@ -201,8 +213,10 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
MilestoneID: milestoneID,
Page: pager.Paginater.Current(),
PageSize: setting.UI.IssuePagingNum,
+ StateType: api.StateType(stateType),
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: isPullOption,
+ Severity: severity,
LabelIDs: labelIDs,
SortType: sortType,
IssueIDs: issueIDs,
@@ -261,6 +275,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
}
ctx.Data["IssueStats"] = issueStats
+ ctx.Data["Severities"] = api.SeverityTypesSlice
+ ctx.Data["Severity"] = severity
ctx.Data["SelLabelIDs"] = labelIDs
ctx.Data["SelectLabels"] = selectLabels
ctx.Data["ViewType"] = viewType
@@ -269,16 +285,13 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
ctx.Data["AssigneeID"] = assigneeID
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["Keyword"] = keyword
- if isShowClosed {
- ctx.Data["State"] = "closed"
- } else {
- ctx.Data["State"] = "open"
- }
+ ctx.Data["State"] = stateType
pager.AddParam(ctx, "q", "Keyword")
pager.AddParam(ctx, "type", "ViewType")
pager.AddParam(ctx, "sort", "SortType")
pager.AddParam(ctx, "state", "State")
+ pager.AddParam(ctx, "severity", "Severity")
pager.AddParam(ctx, "labels", "SelectLabels")
pager.AddParam(ctx, "milestone", "MilestoneID")
pager.AddParam(ctx, "assignee", "AssigneeID")
@@ -426,6 +439,8 @@ func NewIssue(ctx *context.Context) {
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
body := ctx.Query("body")
ctx.Data["BodyQuery"] = body
+ ctx.Data["Severities"] = api.SeverityTypesSlice
+ ctx.Data["Severity"] = api.SeverityTypesMap[api.SeverityLow]
milestoneID := ctx.QueryInt64("milestone")
if milestoneID > 0 {
@@ -544,8 +559,9 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
repo = ctx.Repo.Repository
attachments []string
)
-
+ severity := form.SeverityID
labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, false)
+
if ctx.Written() {
return
}
@@ -570,6 +586,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
PosterID: ctx.User.ID,
Poster: ctx.User,
MilestoneID: milestoneID,
+ Severity: severity,
Content: form.Content,
Ref: form.Ref,
}
@@ -696,6 +713,11 @@ func ViewIssue(ctx *context.Context) {
}
// Metas.
+ // Check severities.
+ severity := api.Severity(issue.Severity)
+ ctx.Data["Severities"] = api.SeverityTypesSlice
+ ctx.Data["Severity"] = api.SeverityTypesMap[severity]
+
// Check labels.
labelIDMark := make(map[int64]bool)
for i := range issue.Labels {
@@ -1083,6 +1105,30 @@ func UpdateIssueContent(ctx *context.Context) {
})
}
+// UpdateIssueSeverity change issue's severity
+func UpdateIssueSeverity(ctx *context.Context) {
+ issues := getActionIssues(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ severityID := ctx.Query("id")
+ for _, issue := range issues {
+ oldSeverityID := issue.Severity
+ if oldSeverityID == severityID {
+ continue
+ }
+ issue.Severity = severityID
+ if err := models.ChangeSeverity(issue, ctx.User); err != nil {
+ return
+ }
+ }
+
+ ctx.JSON(200, map[string]interface{}{
+ "ok": true,
+ })
+}
+
// UpdateIssueMilestone change issue's milestone
func UpdateIssueMilestone(ctx *context.Context) {
issues := getActionIssues(ctx)
@@ -1144,12 +1190,21 @@ func UpdateIssueStatus(ctx *context.Context) {
return
}
+ var state api.StateType
var isClosed bool
switch action := ctx.Query("action"); action {
case "open":
isClosed = false
+ state = api.StateOpen
+ case "in-progress":
+ isClosed = false
+ state = api.StateInProgress
+ case "review":
+ isClosed = false
+ state = api.StateReview
case "close":
isClosed = true
+ state = api.StateClosed
default:
log.Warn("Unrecognized action: %s", action)
}
@@ -1173,6 +1228,12 @@ func UpdateIssueStatus(ctx *context.Context) {
notification.NotifyIssueChangeStatus(ctx.User, issue, isClosed)
}
+ if issue.StateType != state {
+ if err := issue.ChangeState(state); err != nil {
+ ctx.ServerError("ChangeState", err)
+ return
+ }
+ }
}
ctx.JSON(200, map[string]interface{}{
"ok": true,
@@ -1225,11 +1286,23 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return
}
+ var state api.StateType
+ switch form.Status {
+ case "reopen":
+ state = api.StateOpen
+ case "in-progress":
+ state = api.StateInProgress
+ case "review":
+ state = api.StateReview
+ case "close":
+ state = api.StateClosed
+ }
+
var comment *models.Comment
defer func() {
// Check if issue admin/poster changes the status of issue.
if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) &&
- (form.Status == "reopen" || form.Status == "close") &&
+ (form.Status == "reopen" || form.Status == "close" || form.Status == "in-progress" || form.Status == "review") &&
!(issue.IsPull && issue.PullRequest.HasMerged) {
// Duplication and conflict check should apply to reopen pull request.
@@ -1281,6 +1354,13 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return
}
+ if issue.StateType != state {
+ if err := issue.ChangeState(state); err != nil {
+ ctx.ServerError("ChangeState", err)
+ return
+ }
+ }
+
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
notification.NotifyIssueChangeStatus(ctx.User, issue, isClosed)
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 8dfcdb9c9b64c..5c2cbbcf7c8cf 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -712,6 +712,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
}, context.RepoMustNotBeArchived())
+ m.Post("/severity", reqRepoIssuesOrPullsWriter, repo.UpdateIssueSeverity)
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
diff --git a/routers/user/home.go b/routers/user/home.go
index 40b3bc3fc1b81..4d1e9f5f11a34 100644
--- a/routers/user/home.go
+++ b/routers/user/home.go
@@ -7,6 +7,7 @@ package user
import (
"bytes"
+ api "code.gitea.io/gitea/modules/structs"
"fmt"
"sort"
"strings"
@@ -196,7 +197,10 @@ func Issues(ctx *context.Context) {
repoID := ctx.QueryInt64("repo")
isShowClosed := ctx.Query("state") == "closed"
-
+ stateType := ctx.Query("state")
+ if stateType == "" {
+ stateType = "open"
+ }
// Get repositories.
var err error
var userRepoIDs []int64
@@ -227,9 +231,10 @@ func Issues(ctx *context.Context) {
}
opts := &models.IssuesOptions{
- IsClosed: util.OptionalBoolOf(isShowClosed),
- IsPull: util.OptionalBoolOf(isPullList),
- SortType: sortType,
+ StateType: api.StateType(stateType),
+ IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsPull: util.OptionalBoolOf(isPullList),
+ SortType: sortType,
}
if repoID > 0 {
@@ -369,12 +374,7 @@ func Issues(ctx *context.Context) {
ctx.Data["SortType"] = sortType
ctx.Data["RepoID"] = repoID
ctx.Data["IsShowClosed"] = isShowClosed
-
- if isShowClosed {
- ctx.Data["State"] = "closed"
- } else {
- ctx.Data["State"] = "open"
- }
+ ctx.Data["State"] = stateType
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
pager.AddParam(ctx, "type", "ViewType")
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index 5572df671e260..114b5b72a9539 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -27,30 +27,53 @@