Skip to content

Commit 7e0654b

Browse files
ethantkoeniglunny
authored andcommitted
Fix counts on issues dashboard (#2215)
* Fix counts on issues dashboard * setupSess -> setupSession * Unit test * Load repo owners for issues
1 parent f29458b commit 7e0654b

File tree

12 files changed

+329
-90
lines changed

12 files changed

+329
-90
lines changed

models/issue.go

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ type IssuesOptions struct {
10571057
MilestoneID int64
10581058
RepoIDs []int64
10591059
Page int
1060+
PageSize int
10601061
IsClosed util.OptionalBool
10611062
IsPull util.OptionalBool
10621063
Labels string
@@ -1085,21 +1086,16 @@ func sortIssuesSession(sess *xorm.Session, sortType string) {
10851086
}
10861087
}
10871088

1088-
// Issues returns a list of issues by given conditions.
1089-
func Issues(opts *IssuesOptions) ([]*Issue, error) {
1090-
var sess *xorm.Session
1091-
if opts.Page >= 0 {
1089+
func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
1090+
if opts.Page >= 0 && opts.PageSize > 0 {
10921091
var start int
10931092
if opts.Page == 0 {
10941093
start = 0
10951094
} else {
1096-
start = (opts.Page - 1) * setting.UI.IssuePagingNum
1095+
start = (opts.Page - 1) * opts.PageSize
10971096
}
1098-
sess = x.Limit(setting.UI.IssuePagingNum, start)
1099-
} else {
1100-
sess = x.NewSession()
1097+
sess.Limit(opts.PageSize, start)
11011098
}
1102-
defer sess.Close()
11031099

11041100
if len(opts.IssueIDs) > 0 {
11051101
sess.In("issue.id", opts.IssueIDs)
@@ -1144,19 +1140,56 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
11441140
sess.And("issue.is_pull=?", false)
11451141
}
11461142

1147-
sortIssuesSession(sess, opts.SortType)
1148-
11491143
if len(opts.Labels) > 0 && opts.Labels != "0" {
11501144
labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
11511145
if err != nil {
1152-
return nil, err
1146+
return err
11531147
}
11541148
if len(labelIDs) > 0 {
11551149
sess.
11561150
Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
11571151
In("issue_label.label_id", labelIDs)
11581152
}
11591153
}
1154+
return nil
1155+
}
1156+
1157+
// CountIssuesByRepo map from repoID to number of issues matching the options
1158+
func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
1159+
sess := x.NewSession()
1160+
defer sess.Close()
1161+
1162+
if err := opts.setupSession(sess); err != nil {
1163+
return nil, err
1164+
}
1165+
1166+
countsSlice := make([]*struct {
1167+
RepoID int64
1168+
Count int64
1169+
}, 0, 10)
1170+
if err := sess.GroupBy("issue.repo_id").
1171+
Select("issue.repo_id AS repo_id, COUNT(*) AS count").
1172+
Table("issue").
1173+
Find(&countsSlice); err != nil {
1174+
return nil, err
1175+
}
1176+
1177+
countMap := make(map[int64]int64, len(countsSlice))
1178+
for _, c := range countsSlice {
1179+
countMap[c.RepoID] = c.Count
1180+
}
1181+
return countMap, nil
1182+
}
1183+
1184+
// Issues returns a list of issues by given conditions.
1185+
func Issues(opts *IssuesOptions) ([]*Issue, error) {
1186+
sess := x.NewSession()
1187+
defer sess.Close()
1188+
1189+
if err := opts.setupSession(sess); err != nil {
1190+
return nil, err
1191+
}
1192+
sortIssuesSession(sess, opts.SortType)
11601193

11611194
issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
11621195
if err := sess.Find(&issues); err != nil {

models/issue_indexer.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ func populateIssueIndexer() error {
133133
RepoID: repo.ID,
134134
IsClosed: util.OptionalBoolNone,
135135
IsPull: util.OptionalBoolNone,
136-
Page: -1, // do not page
137136
})
138137
if err != nil {
139138
return fmt.Errorf("Issues: %v", err)

models/main_test.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ import (
88

99
"code.gitea.io/gitea/modules/setting"
1010

11-
"github.com/go-xorm/core"
12-
"github.com/go-xorm/xorm"
1311
_ "github.com/mattn/go-sqlite3" // for the test engine
1412
"github.com/stretchr/testify/assert"
15-
"gopkg.in/testfixtures.v2"
1613
)
1714

1815
// TestFixturesAreConsistent assert that test fixtures are consistent
@@ -21,23 +18,8 @@ func TestFixturesAreConsistent(t *testing.T) {
2118
CheckConsistencyForAll(t)
2219
}
2320

24-
// CreateTestEngine create an xorm engine for testing
25-
func CreateTestEngine() error {
26-
var err error
27-
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
28-
if err != nil {
29-
return err
30-
}
31-
x.SetMapper(core.GonicMapper{})
32-
if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
33-
return err
34-
}
35-
36-
return InitFixtures(&testfixtures.SQLite{}, "fixtures/")
37-
}
38-
3921
func TestMain(m *testing.M) {
40-
if err := CreateTestEngine(); err != nil {
22+
if err := CreateTestEngine("fixtures/"); err != nil {
4123
fmt.Printf("Error creating test engine: %v\n", err)
4224
os.Exit(1)
4325
}

models/repo_list.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import (
1515
// RepositoryList contains a list of repositories
1616
type RepositoryList []*Repository
1717

18+
// RepositoryListOfMap make list from values of map
19+
func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList {
20+
return RepositoryList(valuesRepository(repoMap))
21+
}
22+
1823
func (repos RepositoryList) loadAttributes(e Engine) error {
1924
if len(repos) == 0 {
2025
return nil

models/unit_tests.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,31 @@ package models
77
import (
88
"testing"
99

10+
"github.com/go-xorm/core"
1011
"github.com/go-xorm/xorm"
1112
"github.com/stretchr/testify/assert"
13+
"gopkg.in/testfixtures.v2"
1214
)
1315

1416
// NonexistentID an ID that will never exist
1517
const NonexistentID = 9223372036854775807
1618

19+
// CreateTestEngine create in-memory sqlite database for unit tests
20+
// Any package that calls this must import github.com/mattn/go-sqlite3
21+
func CreateTestEngine(fixturesDir string) error {
22+
var err error
23+
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
24+
if err != nil {
25+
return err
26+
}
27+
x.SetMapper(core.GonicMapper{})
28+
if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
29+
return err
30+
}
31+
32+
return InitFixtures(&testfixtures.SQLite{}, fixturesDir)
33+
}
34+
1735
// PrepareTestDatabase load test fixtures into test database
1836
func PrepareTestDatabase() error {
1937
return LoadFixtures()

modules/test/context_tests.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright 2017 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 test
6+
7+
import (
8+
"net/http"
9+
"net/url"
10+
"testing"
11+
12+
"code.gitea.io/gitea/modules/context"
13+
14+
"github.com/stretchr/testify/assert"
15+
macaron "gopkg.in/macaron.v1"
16+
)
17+
18+
// MockContext mock context for unit tests
19+
func MockContext(t *testing.T) *context.Context {
20+
var macaronContext *macaron.Context
21+
mac := macaron.New()
22+
mac.Get("*/", func(ctx *macaron.Context) {
23+
macaronContext = ctx
24+
})
25+
req, err := http.NewRequest("GET", "star", nil)
26+
assert.NoError(t, err)
27+
req.Form = url.Values{}
28+
mac.ServeHTTP(&mockResponseWriter{}, req)
29+
assert.NotNil(t, macaronContext)
30+
assert.EqualValues(t, req, macaronContext.Req.Request)
31+
macaronContext.Locale = &mockLocale{}
32+
macaronContext.Resp = &mockResponseWriter{}
33+
macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
34+
return &context.Context{
35+
Context: macaronContext,
36+
}
37+
}
38+
39+
type mockLocale struct{}
40+
41+
func (l mockLocale) Language() string {
42+
return "en"
43+
}
44+
45+
func (l mockLocale) Tr(s string, _ ...interface{}) string {
46+
return "test translation"
47+
}
48+
49+
type mockResponseWriter struct {
50+
status int
51+
size int
52+
}
53+
54+
func (rw *mockResponseWriter) Header() http.Header {
55+
return map[string][]string{}
56+
}
57+
58+
func (rw *mockResponseWriter) Write(b []byte) (int, error) {
59+
rw.size += len(b)
60+
return len(b), nil
61+
}
62+
63+
func (rw *mockResponseWriter) WriteHeader(status int) {
64+
rw.status = status
65+
}
66+
67+
func (rw *mockResponseWriter) Flush() {
68+
}
69+
70+
func (rw *mockResponseWriter) Status() int {
71+
return rw.status
72+
}
73+
74+
func (rw *mockResponseWriter) Written() bool {
75+
return rw.status > 0
76+
}
77+
78+
func (rw *mockResponseWriter) Size() int {
79+
return rw.size
80+
}
81+
82+
func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) {
83+
b(rw)
84+
}
85+
86+
type mockRender struct {
87+
http.ResponseWriter
88+
}
89+
90+
func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) {
91+
tr.ResponseWriter = rw
92+
}
93+
94+
func (tr *mockRender) JSON(int, interface{}) {
95+
}
96+
97+
func (tr *mockRender) JSONString(interface{}) (string, error) {
98+
return "", nil
99+
}
100+
101+
func (tr *mockRender) RawData(status int, _ []byte) {
102+
tr.Status(status)
103+
}
104+
105+
func (tr *mockRender) PlainText(status int, _ []byte) {
106+
tr.Status(status)
107+
}
108+
109+
func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
110+
tr.Status(status)
111+
}
112+
113+
func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
114+
tr.Status(status)
115+
}
116+
117+
func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) {
118+
return "", nil
119+
}
120+
121+
func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) {
122+
return "", nil
123+
}
124+
125+
func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
126+
return nil, nil
127+
}
128+
129+
func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
130+
return nil, nil
131+
}
132+
133+
func (tr *mockRender) XML(status int, _ interface{}) {
134+
tr.Status(status)
135+
}
136+
137+
func (tr *mockRender) Error(status int, _ ...string) {
138+
tr.Status(status)
139+
}
140+
141+
func (tr *mockRender) Status(status int) {
142+
tr.ResponseWriter.WriteHeader(status)
143+
}
144+
145+
func (tr *mockRender) SetTemplatePath(string, string) {
146+
}
147+
148+
func (tr *mockRender) HasTemplateSet(string) bool {
149+
return true
150+
}

routers/api/v1/repo/issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func ListIssues(ctx *context.APIContext) {
3131
issues, err := models.Issues(&models.IssuesOptions{
3232
RepoID: ctx.Repo.Repository.ID,
3333
Page: ctx.QueryInt("page"),
34+
PageSize: setting.UI.IssuePagingNum,
3435
IsClosed: isClosed,
3536
})
3637
if err != nil {

routers/repo/issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ func Issues(ctx *context.Context) {
193193
MentionedID: mentionedID,
194194
MilestoneID: milestoneID,
195195
Page: pager.Current(),
196+
PageSize: setting.UI.IssuePagingNum,
196197
IsClosed: util.OptionalBoolOf(isShowClosed),
197198
IsPull: util.OptionalBoolOf(isPullList),
198199
Labels: selectLabels,

0 commit comments

Comments
 (0)