Skip to content

Commit 88784a1

Browse files
committed
Start implementing issue weights
1 parent 2fc347b commit 88784a1

File tree

27 files changed

+383
-20
lines changed

27 files changed

+383
-20
lines changed

models/issues/comment.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ const (
114114

115115
CommentTypePin // 36 pin Issue
116116
CommentTypeUnpin // 37 unpin Issue
117+
118+
CommentTypeAddedWeight // 38 Added a weight
119+
CommentTypeModifiedWeight // 39 Modified the weight
120+
CommentTypeRemovedWeight // 40 Removed a weight
117121
)
118122

119123
var commentStrings = []string{
@@ -960,6 +964,40 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is
960964
return comment, nil
961965
}
962966

967+
func CreateWeightComment(ctx context.Context, doer *user_model.User, issue *Issue, newWeight int) (*Comment, error) {
968+
var content string
969+
var commentType CommentType
970+
971+
// weight = 0 means deleting
972+
if newWeight == 0 {
973+
commentType = CommentTypeRemovedWeight
974+
content = fmt.Sprintf("%d", issue.Weight)
975+
} else if issue.Weight == 0 || issue.Weight == newWeight {
976+
commentType = CommentTypeAddedWeight
977+
content = fmt.Sprintf("%d", newWeight)
978+
} else { // Otherwise modified
979+
commentType = CommentTypeModifiedWeight
980+
content = fmt.Sprintf("%d|%d", newWeight, issue.Weight)
981+
}
982+
983+
if err := issue.LoadRepo(ctx); err != nil {
984+
return nil, err
985+
}
986+
987+
opts := &CreateCommentOptions{
988+
Type: commentType,
989+
Doer: doer,
990+
Repo: issue.Repo,
991+
Issue: issue,
992+
Content: content,
993+
}
994+
comment, err := CreateComment(ctx, opts)
995+
if err != nil {
996+
return nil, err
997+
}
998+
return comment, nil
999+
}
1000+
9631001
// Creates issue dependency comment
9641002
func createIssueDependencyComment(ctx context.Context, doer *user_model.User, issue, dependentIssue *Issue, add bool) (err error) {
9651003
cType := CommentTypeAddDependency

models/issues/issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ type Issue struct {
127127
NumComments int
128128
Ref string
129129
PinOrder int `xorm:"DEFAULT 0"`
130+
Weight int
130131

131132
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
132133

models/issues/issue_update.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,31 @@ func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeuti
459459
return committer.Commit()
460460
}
461461

462+
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
463+
func UpdateIssueWeight(ctx context.Context, issue *Issue, weight int, doer *user_model.User) (err error) {
464+
// if the weight hasn't changed do nothing
465+
if issue.Weight == weight {
466+
return nil
467+
}
468+
ctx, committer, err := db.TxContext(ctx)
469+
if err != nil {
470+
return err
471+
}
472+
defer committer.Close()
473+
474+
// Update the weight
475+
if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, Weight: weight}, "weight"); err != nil {
476+
return err
477+
}
478+
479+
// Make the comment
480+
if _, err = CreateWeightComment(ctx, doer, issue, weight); err != nil {
481+
return fmt.Errorf("createWeightComment: %w", err)
482+
}
483+
484+
return committer.Commit()
485+
}
486+
462487
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
463488
func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
464489
rawMentions := references.FindAllMentionsMarkdown(content)

models/issues/milestone.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type Milestone struct {
6565
DeadlineString string `xorm:"-"`
6666

6767
TotalTrackedTime int64 `xorm:"-"`
68+
TotalWeight int `xorm:"-"`
6869
}
6970

7071
func init() {
@@ -355,6 +356,28 @@ func (m *Milestone) LoadTotalTrackedTime(ctx context.Context) error {
355356
return nil
356357
}
357358

359+
// LoadTotalWeight loads the total weight for the milestone
360+
func (m *Milestone) LoadTotalWeight(ctx context.Context) error {
361+
type totalTimesByMilestone struct {
362+
MilestoneID int64
363+
Weight int
364+
}
365+
totalTime := &totalTimesByMilestone{MilestoneID: m.ID}
366+
has, err := db.GetEngine(ctx).Table("issue").
367+
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
368+
Select("milestone_id, sum(weight) as weight").
369+
Where("milestone_id = ?", m.ID).
370+
GroupBy("milestone_id").
371+
Get(totalTime)
372+
if err != nil {
373+
return err
374+
} else if !has {
375+
return nil
376+
}
377+
m.TotalWeight = totalTime.Weight
378+
return nil
379+
}
380+
358381
// InsertMilestones creates milestones of repository.
359382
func InsertMilestones(ctx context.Context, ms ...*Milestone) (err error) {
360383
if len(ms) == 0 {

models/issues/milestone_list.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,45 @@ func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error
130130
return nil
131131
}
132132

133+
// LoadTotalWeight loads for every milestone in the list the TotalWeight by a batch request
134+
func (milestones MilestoneList) LoadTotalWeight(ctx context.Context) error {
135+
type totalWeightByMilestone struct {
136+
MilestoneID int64
137+
Weight int
138+
}
139+
if len(milestones) == 0 {
140+
return nil
141+
}
142+
trackedTimes := make(map[int64]int, len(milestones))
143+
144+
// Get total tracked time by milestone_id
145+
rows, err := db.GetEngine(ctx).Table("issue").
146+
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
147+
Select("milestone_id, sum(weight) as weight").
148+
In("milestone_id", milestones.getMilestoneIDs()).
149+
GroupBy("milestone_id").
150+
Rows(new(totalWeightByMilestone))
151+
if err != nil {
152+
return err
153+
}
154+
155+
defer rows.Close()
156+
157+
for rows.Next() {
158+
var totalTime totalWeightByMilestone
159+
err = rows.Scan(&totalTime)
160+
if err != nil {
161+
return err
162+
}
163+
trackedTimes[totalTime.MilestoneID] = totalTime.Weight
164+
}
165+
166+
for _, milestone := range milestones {
167+
milestone.TotalWeight = trackedTimes[milestone.ID]
168+
}
169+
return nil
170+
}
171+
133172
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
134173
func CountMilestonesMap(ctx context.Context, opts FindMilestoneOptions) (map[int64]int64, error) {
135174
sess := db.GetEngine(ctx).Where(opts.ToConds())

models/migrations/v1_23/v305.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_23 //nolint
5+
6+
import "xorm.io/xorm"
7+
8+
func AssIssueWeight(x *xorm.Engine) error {
9+
type Issue struct {
10+
Weight int
11+
}
12+
return x.Sync(new(Issue))
13+
}

models/project/column.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,6 @@ func (Column) TableName() string {
5757
return "project_board" // TODO: the legacy table name should be project_column
5858
}
5959

60-
// NumIssues return counter of all issues assigned to the column
61-
func (c *Column) NumIssues(ctx context.Context) int {
62-
total, err := db.GetEngine(ctx).Table("project_issue").
63-
Where("project_id=?", c.ProjectID).
64-
And("project_board_id=?", c.ID).
65-
GroupBy("issue_id").
66-
Cols("issue_id").
67-
Count()
68-
if err != nil {
69-
return 0
70-
}
71-
return int(total)
72-
}
73-
7460
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
7561
issues := make([]*ProjectIssue, 0, 5)
7662
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).

modules/structs/issue.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,23 @@ type EditDeadlineOption struct {
122122
Deadline *time.Time `json:"due_date"`
123123
}
124124

125+
type EditWeightOption struct {
126+
// required:true
127+
Weight int `json:"weight"`
128+
}
129+
125130
// IssueDeadline represents an issue deadline
126131
// swagger:model
127132
type IssueDeadline struct {
128133
// swagger:strfmt date-time
129134
Deadline *time.Time `json:"due_date"`
130135
}
131136

137+
// IssueWeight represents an issue weight
138+
type IssueWeight struct {
139+
Weight int `json:"weight"`
140+
}
141+
132142
// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes"
133143
type IssueFormFieldType string
134144

options/locale/locale_en-US.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ language = Language
1818
notifications = Notifications
1919
active_stopwatch = Active Time Tracker
2020
tracked_time_summary = Summary of tracked time based on filters of issue list
21+
weight_summary = Total weight of all issues in the milestone
2122
create_new = Create…
2223
user_profile_and_more = Profile and Settings…
2324
signed_in_as = Signed in as
@@ -1698,6 +1699,14 @@ issues.due_date_modified = "modified the due date from %[2]s to %[1]s %[3]s"
16981699
issues.due_date_remove = "removed the due date %s %s"
16991700
issues.due_date_overdue = "Overdue"
17001701
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'."
1702+
issues.weight_added = "added weight of %s %s"
1703+
issues.weight_modified = " changed weight to %s from %s %s"
1704+
issues.weight_removed = "removed weight of %s %s"
1705+
1706+
issues.weight.title = Weight
1707+
issues.weight.no_weight = No Weight
1708+
issues.weight.remove_weight = remove weight
1709+
17011710
issues.dependency.title = Dependencies
17021711
issues.dependency.issue_no_dependencies = No dependencies set.
17031712
issues.dependency.pr_no_dependencies = No dependencies set.

routers/api/v1/repo/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ func CreateIssue(ctx *context.APIContext) {
707707
form.Labels = make([]int64, 0)
708708
}
709709

710-
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, 0); err != nil {
710+
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, 0, 0); err != nil {
711711
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
712712
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
713713
} else if errors.Is(err, user_model.ErrBlockedUser) {

routers/web/repo/issue.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1277,9 +1277,10 @@ func NewIssuePost(ctx *context.Context) {
12771277
MilestoneID: milestoneID,
12781278
Content: content,
12791279
Ref: form.Ref,
1280+
Weight: form.Weight,
12801281
}
12811282

1282-
if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs, projectID); err != nil {
1283+
if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs, projectID, form.Weight); err != nil {
12831284
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
12841285
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
12851286
} else if errors.Is(err, user_model.ErrBlockedUser) {
@@ -2360,6 +2361,34 @@ func UpdateIssueDeadline(ctx *context.Context) {
23602361
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
23612362
}
23622363

2364+
// UpdateIssueWeight updates an issue weight
2365+
func UpdateIssueWeight(ctx *context.Context) {
2366+
form := web.GetForm(ctx).(*api.EditWeightOption)
2367+
2368+
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
2369+
if err != nil {
2370+
if issues_model.IsErrIssueNotExist(err) {
2371+
ctx.NotFound("GetIssueByIndex", err)
2372+
} else {
2373+
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error())
2374+
}
2375+
return
2376+
}
2377+
2378+
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
2379+
ctx.Error(http.StatusForbidden, "", "Not repo writer")
2380+
return
2381+
}
2382+
2383+
if err := issues_model.UpdateIssueWeight(ctx, issue, form.Weight, ctx.Doer); err != nil {
2384+
ctx.Error(http.StatusInternalServerError, "UpdateIssueWeight", err.Error())
2385+
return
2386+
}
2387+
2388+
ctx.Redirect(issue.Link())
2389+
// ctx.JSON(http.StatusCreated, api.IssueWeight{Weight: form.Weight})
2390+
}
2391+
23632392
// UpdateIssueMilestone change issue's milestone
23642393
func UpdateIssueMilestone(ctx *context.Context) {
23652394
issues := getActionIssues(ctx)

routers/web/repo/milestone.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ func Milestones(ctx *context.Context) {
7979
return
8080
}
8181
}
82+
83+
if err := issues_model.MilestoneList(miles).LoadTotalWeight(ctx); err != nil {
84+
ctx.ServerError("LoadTotalWeight", err)
85+
return
86+
}
87+
8288
for _, m := range miles {
8389
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
8490
Links: markup.Links{
@@ -302,5 +308,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
302308
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
303309
ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)
304310

311+
if err := milestone.LoadTotalWeight(ctx); err != nil {
312+
ctx.ServerError("LoadTotalWeight", err)
313+
return
314+
}
315+
316+
ctx.Data["TotalWeight"] = milestone.TotalWeight
317+
305318
ctx.HTML(http.StatusOK, tplMilestoneIssues)
306319
}

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ func registerRoutes(m *web.Router) {
12041204
m.Post("/title", repo.UpdateIssueTitle)
12051205
m.Post("/content", repo.UpdateIssueContent)
12061206
m.Post("/deadline", web.Bind(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline)
1207+
m.Post("/weight", web.Bind(structs.EditWeightOption{}), repo.UpdateIssueWeight)
12071208
m.Post("/watch", repo.IssueWatch)
12081209
m.Post("/ref", repo.UpdateIssueRef)
12091210
m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin)

services/forms/repo_form.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ type CreateIssueForm struct {
450450
MilestoneID int64
451451
ProjectID int64
452452
AssigneeID int64
453+
Weight int
453454
Content string
454455
Files []string
455456
AllowMaintainerEdit bool

services/issue/issue.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
activities_model "code.gitea.io/gitea/models/activities"
1111
"code.gitea.io/gitea/models/db"
12+
issue_model "code.gitea.io/gitea/models/issues"
1213
issues_model "code.gitea.io/gitea/models/issues"
1314
access_model "code.gitea.io/gitea/models/perm/access"
1415
project_model "code.gitea.io/gitea/models/project"
@@ -23,7 +24,7 @@ import (
2324
)
2425

2526
// NewIssue creates new issue with labels for repository.
26-
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64, projectID int64) error {
27+
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64, projectID int64, weight int) error {
2728
if err := issue.LoadPoster(ctx); err != nil {
2829
return err
2930
}
@@ -46,6 +47,12 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
4647
return err
4748
}
4849
}
50+
51+
if weight != 0 {
52+
if _, err := issue_model.CreateWeightComment(ctx, issue.Poster, issue, weight); err != nil {
53+
return err
54+
}
55+
}
4956
return nil
5057
}); err != nil {
5158
return err

templates/projects/view.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,13 @@
145145
<div class="project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
146146
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
147147
<div class="ui circular label project-column-issue-count">
148-
{{.NumIssues ctx}}
148+
149149
</div>
150150
<div class="project-column-title-label gt-ellipsis">{{.Title}}</div>
151+
<div class="flex-text-block tw-hidden">
152+
{{svg "octicon-briefcase"}}
153+
<span class="project-column-weight"></span>
154+
</div>
151155
{{if $canWriteProject}}
152156
<div class="ui dropdown tw-p-1">
153157
{{svg "octicon-kebab-horizontal"}}

0 commit comments

Comments
 (0)