Skip to content

Commit 82ecd3b

Browse files
fantashleylunny
andauthored
Update milestone counters when issue is deleted (#21459)
When actions besides "delete" are performed on issues, the milestone counter is updated. However, since deleting issues goes through a different code path, the associated milestone's count wasn't being updated, resulting in inaccurate counts until another issue in the same milestone had a non-delete action performed on it. I verified this change fixes the inaccurate counts using a local docker build. Fixes #21254 Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 parent 154efa5 commit 82ecd3b

File tree

7 files changed

+163
-0
lines changed

7 files changed

+163
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# type Milestone struct {
2+
# ID int64 `xorm:"pk autoincr"`
3+
# IsClosed bool
4+
# NumIssues int
5+
# NumClosedIssues int
6+
# Completeness int // Percentage(1-100).
7+
# }
8+
-
9+
id: 1
10+
is_closed: false
11+
num_issues: 3
12+
num_closed_issues: 1
13+
completeness: 33
14+
-
15+
id: 2
16+
is_closed: true
17+
num_issues: 5
18+
num_closed_issues: 5
19+
completeness: 100
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# type Issue struct {
2+
# ID int64 `xorm:"pk autoincr"`
3+
# RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
4+
# Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
5+
# MilestoneID int64 `xorm:"INDEX"`
6+
# IsClosed bool `xorm:"INDEX"`
7+
# }
8+
-
9+
id: 1
10+
repo_id: 1
11+
index: 1
12+
milestone_id: 1
13+
is_closed: false
14+
-
15+
id: 2
16+
repo_id: 1
17+
index: 2
18+
milestone_id: 1
19+
is_closed: true
20+
-
21+
id: 4
22+
repo_id: 1
23+
index: 3
24+
milestone_id: 1
25+
is_closed: false
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# type Milestone struct {
2+
# ID int64 `xorm:"pk autoincr"`
3+
# IsClosed bool
4+
# NumIssues int
5+
# NumClosedIssues int
6+
# Completeness int // Percentage(1-100).
7+
# }
8+
-
9+
id: 1
10+
is_closed: false
11+
num_issues: 4
12+
num_closed_issues: 2
13+
completeness: 50
14+
-
15+
id: 2
16+
is_closed: true
17+
num_issues: 5
18+
num_closed_issues: 5
19+
completeness: 100

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,8 @@ var migrations = []Migration{
419419
NewMigration("Create key/value table for system settings", createSystemSettingsTable),
420420
// v228 -> v229
421421
NewMigration("Add TeamInvite table", addTeamInviteTable),
422+
// v229 -> v230
423+
NewMigration("Update counts of all open milestones", updateOpenMilestoneCounts),
422424
}
423425

424426
// GetCurrentDBVersion returns the current db version

models/migrations/v229.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/models/issues"
11+
12+
"xorm.io/builder"
13+
"xorm.io/xorm"
14+
)
15+
16+
func updateOpenMilestoneCounts(x *xorm.Engine) error {
17+
var openMilestoneIDs []int64
18+
err := x.Table("milestone").Select("id").Where(builder.Neq{"is_closed": 1}).Find(&openMilestoneIDs)
19+
if err != nil {
20+
return fmt.Errorf("error selecting open milestone IDs: %w", err)
21+
}
22+
23+
for _, id := range openMilestoneIDs {
24+
_, err := x.ID(id).
25+
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
26+
builder.Eq{"milestone_id": id},
27+
)).
28+
SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where(
29+
builder.Eq{
30+
"milestone_id": id,
31+
"is_closed": true,
32+
},
33+
)).
34+
Update(&issues.Milestone{})
35+
if err != nil {
36+
return fmt.Errorf("error updating issue counts in milestone %d: %w", id, err)
37+
}
38+
_, err = x.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?",
39+
id,
40+
)
41+
if err != nil {
42+
return fmt.Errorf("error setting completeness on milestone %d: %w", id, err)
43+
}
44+
}
45+
46+
return nil
47+
}

models/migrations/v229_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"testing"
9+
10+
"code.gitea.io/gitea/models/issues"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func Test_updateOpenMilestoneCounts(t *testing.T) {
16+
type ExpectedMilestone issues.Milestone
17+
18+
// Prepare and load the testing database
19+
x, deferable := prepareTestEnv(t, 0, new(issues.Milestone), new(ExpectedMilestone), new(issues.Issue))
20+
defer deferable()
21+
if x == nil || t.Failed() {
22+
return
23+
}
24+
25+
if err := updateOpenMilestoneCounts(x); err != nil {
26+
assert.NoError(t, err)
27+
return
28+
}
29+
30+
expected := []ExpectedMilestone{}
31+
if err := x.Table("expected_milestone").Asc("id").Find(&expected); !assert.NoError(t, err) {
32+
return
33+
}
34+
35+
got := []issues.Milestone{}
36+
if err := x.Table("milestone").Asc("id").Find(&got); !assert.NoError(t, err) {
37+
return
38+
}
39+
40+
for i, e := range expected {
41+
got := got[i]
42+
assert.Equal(t, e.ID, got.ID)
43+
assert.Equal(t, e.NumIssues, got.NumIssues)
44+
assert.Equal(t, e.NumClosedIssues, got.NumClosedIssues)
45+
}
46+
}

services/issue/issue.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ func deleteIssue(issue *issues_model.Issue) error {
224224
return err
225225
}
226226

227+
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
228+
return fmt.Errorf("error updating counters for milestone id %d: %w",
229+
issue.MilestoneID, err)
230+
}
231+
227232
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
228233
return err
229234
}

0 commit comments

Comments
 (0)