Fix counts on issues dashboard (#2215)
* Fix counts on issues dashboard * setupSess -> setupSession * Unit test * Load repo owners for issues
This commit is contained in:
		
							parent
							
								
									f29458bd3a
								
							
						
					
					
						commit
						7e0654bd9e
					
				
					 12 changed files with 336 additions and 97 deletions
				
			
		|  | @ -1057,6 +1057,7 @@ type IssuesOptions struct { | ||||||
| 	MilestoneID int64 | 	MilestoneID int64 | ||||||
| 	RepoIDs     []int64 | 	RepoIDs     []int64 | ||||||
| 	Page        int | 	Page        int | ||||||
|  | 	PageSize    int | ||||||
| 	IsClosed    util.OptionalBool | 	IsClosed    util.OptionalBool | ||||||
| 	IsPull      util.OptionalBool | 	IsPull      util.OptionalBool | ||||||
| 	Labels      string | 	Labels      string | ||||||
|  | @ -1085,21 +1086,16 @@ func sortIssuesSession(sess *xorm.Session, sortType string) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Issues returns a list of issues by given conditions.
 | func (opts *IssuesOptions) setupSession(sess *xorm.Session) error { | ||||||
| func Issues(opts *IssuesOptions) ([]*Issue, error) { | 	if opts.Page >= 0 && opts.PageSize > 0 { | ||||||
| 	var sess *xorm.Session |  | ||||||
| 	if opts.Page >= 0 { |  | ||||||
| 		var start int | 		var start int | ||||||
| 		if opts.Page == 0 { | 		if opts.Page == 0 { | ||||||
| 			start = 0 | 			start = 0 | ||||||
| 		} else { | 		} else { | ||||||
| 			start = (opts.Page - 1) * setting.UI.IssuePagingNum | 			start = (opts.Page - 1) * opts.PageSize | ||||||
| 		} | 		} | ||||||
| 		sess = x.Limit(setting.UI.IssuePagingNum, start) | 		sess.Limit(opts.PageSize, start) | ||||||
| 	} else { |  | ||||||
| 		sess = x.NewSession() |  | ||||||
| 	} | 	} | ||||||
| 	defer sess.Close() |  | ||||||
| 
 | 
 | ||||||
| 	if len(opts.IssueIDs) > 0 { | 	if len(opts.IssueIDs) > 0 { | ||||||
| 		sess.In("issue.id", opts.IssueIDs) | 		sess.In("issue.id", opts.IssueIDs) | ||||||
|  | @ -1144,12 +1140,10 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { | ||||||
| 		sess.And("issue.is_pull=?", false) | 		sess.And("issue.is_pull=?", false) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sortIssuesSession(sess, opts.SortType) |  | ||||||
| 
 |  | ||||||
| 	if len(opts.Labels) > 0 && opts.Labels != "0" { | 	if len(opts.Labels) > 0 && opts.Labels != "0" { | ||||||
| 		labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ",")) | 		labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ",")) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if len(labelIDs) > 0 { | 		if len(labelIDs) > 0 { | ||||||
| 			sess. | 			sess. | ||||||
|  | @ -1157,6 +1151,45 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { | ||||||
| 				In("issue_label.label_id", labelIDs) | 				In("issue_label.label_id", labelIDs) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CountIssuesByRepo map from repoID to number of issues matching the options
 | ||||||
|  | func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { | ||||||
|  | 	sess := x.NewSession() | ||||||
|  | 	defer sess.Close() | ||||||
|  | 
 | ||||||
|  | 	if err := opts.setupSession(sess); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	countsSlice := make([]*struct { | ||||||
|  | 		RepoID int64 | ||||||
|  | 		Count  int64 | ||||||
|  | 	}, 0, 10) | ||||||
|  | 	if err := sess.GroupBy("issue.repo_id"). | ||||||
|  | 		Select("issue.repo_id AS repo_id, COUNT(*) AS count"). | ||||||
|  | 		Table("issue"). | ||||||
|  | 		Find(&countsSlice); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	countMap := make(map[int64]int64, len(countsSlice)) | ||||||
|  | 	for _, c := range countsSlice { | ||||||
|  | 		countMap[c.RepoID] = c.Count | ||||||
|  | 	} | ||||||
|  | 	return countMap, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Issues returns a list of issues by given conditions.
 | ||||||
|  | func Issues(opts *IssuesOptions) ([]*Issue, error) { | ||||||
|  | 	sess := x.NewSession() | ||||||
|  | 	defer sess.Close() | ||||||
|  | 
 | ||||||
|  | 	if err := opts.setupSession(sess); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	sortIssuesSession(sess, opts.SortType) | ||||||
| 
 | 
 | ||||||
| 	issues := make([]*Issue, 0, setting.UI.IssuePagingNum) | 	issues := make([]*Issue, 0, setting.UI.IssuePagingNum) | ||||||
| 	if err := sess.Find(&issues); err != nil { | 	if err := sess.Find(&issues); err != nil { | ||||||
|  |  | ||||||
|  | @ -133,7 +133,6 @@ func populateIssueIndexer() error { | ||||||
| 				RepoID:   repo.ID, | 				RepoID:   repo.ID, | ||||||
| 				IsClosed: util.OptionalBoolNone, | 				IsClosed: util.OptionalBoolNone, | ||||||
| 				IsPull:   util.OptionalBoolNone, | 				IsPull:   util.OptionalBoolNone, | ||||||
| 				Page:     -1, // do not page
 |  | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return fmt.Errorf("Issues: %v", err) | 				return fmt.Errorf("Issues: %v", err) | ||||||
|  |  | ||||||
|  | @ -8,11 +8,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-xorm/core" |  | ||||||
| 	"github.com/go-xorm/xorm" |  | ||||||
| 	_ "github.com/mattn/go-sqlite3" // for the test engine
 | 	_ "github.com/mattn/go-sqlite3" // for the test engine
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"gopkg.in/testfixtures.v2" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // TestFixturesAreConsistent assert that test fixtures are consistent
 | // TestFixturesAreConsistent assert that test fixtures are consistent
 | ||||||
|  | @ -21,23 +18,8 @@ func TestFixturesAreConsistent(t *testing.T) { | ||||||
| 	CheckConsistencyForAll(t) | 	CheckConsistencyForAll(t) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateTestEngine create an xorm engine for testing
 |  | ||||||
| func CreateTestEngine() error { |  | ||||||
| 	var err error |  | ||||||
| 	x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	x.SetMapper(core.GonicMapper{}) |  | ||||||
| 	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return InitFixtures(&testfixtures.SQLite{}, "fixtures/") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestMain(m *testing.M) { | func TestMain(m *testing.M) { | ||||||
| 	if err := CreateTestEngine(); err != nil { | 	if err := CreateTestEngine("fixtures/"); err != nil { | ||||||
| 		fmt.Printf("Error creating test engine: %v\n", err) | 		fmt.Printf("Error creating test engine: %v\n", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -15,6 +15,11 @@ import ( | ||||||
| // RepositoryList contains a list of repositories
 | // RepositoryList contains a list of repositories
 | ||||||
| type RepositoryList []*Repository | type RepositoryList []*Repository | ||||||
| 
 | 
 | ||||||
|  | // RepositoryListOfMap make list from values of map
 | ||||||
|  | func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { | ||||||
|  | 	return RepositoryList(valuesRepository(repoMap)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (repos RepositoryList) loadAttributes(e Engine) error { | func (repos RepositoryList) loadAttributes(e Engine) error { | ||||||
| 	if len(repos) == 0 { | 	if len(repos) == 0 { | ||||||
| 		return nil | 		return nil | ||||||
|  |  | ||||||
|  | @ -7,13 +7,31 @@ package models | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/go-xorm/core" | ||||||
| 	"github.com/go-xorm/xorm" | 	"github.com/go-xorm/xorm" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"gopkg.in/testfixtures.v2" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NonexistentID an ID that will never exist
 | // NonexistentID an ID that will never exist
 | ||||||
| const NonexistentID = 9223372036854775807 | const NonexistentID = 9223372036854775807 | ||||||
| 
 | 
 | ||||||
|  | // CreateTestEngine create in-memory sqlite database for unit tests
 | ||||||
|  | // Any package that calls this must import github.com/mattn/go-sqlite3
 | ||||||
|  | func CreateTestEngine(fixturesDir string) error { | ||||||
|  | 	var err error | ||||||
|  | 	x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	x.SetMapper(core.GonicMapper{}) | ||||||
|  | 	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return InitFixtures(&testfixtures.SQLite{}, fixturesDir) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // PrepareTestDatabase load test fixtures into test database
 | // PrepareTestDatabase load test fixtures into test database
 | ||||||
| func PrepareTestDatabase() error { | func PrepareTestDatabase() error { | ||||||
| 	return LoadFixtures() | 	return LoadFixtures() | ||||||
|  |  | ||||||
							
								
								
									
										150
									
								
								modules/test/context_tests.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								modules/test/context_tests.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | ||||||
|  | // Copyright 2017 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	macaron "gopkg.in/macaron.v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // MockContext mock context for unit tests
 | ||||||
|  | func MockContext(t *testing.T) *context.Context { | ||||||
|  | 	var macaronContext *macaron.Context | ||||||
|  | 	mac := macaron.New() | ||||||
|  | 	mac.Get("*/", func(ctx *macaron.Context) { | ||||||
|  | 		macaronContext = ctx | ||||||
|  | 	}) | ||||||
|  | 	req, err := http.NewRequest("GET", "star", nil) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	req.Form = url.Values{} | ||||||
|  | 	mac.ServeHTTP(&mockResponseWriter{}, req) | ||||||
|  | 	assert.NotNil(t, macaronContext) | ||||||
|  | 	assert.EqualValues(t, req, macaronContext.Req.Request) | ||||||
|  | 	macaronContext.Locale = &mockLocale{} | ||||||
|  | 	macaronContext.Resp = &mockResponseWriter{} | ||||||
|  | 	macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp} | ||||||
|  | 	return &context.Context{ | ||||||
|  | 		Context: macaronContext, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockLocale struct{} | ||||||
|  | 
 | ||||||
|  | func (l mockLocale) Language() string { | ||||||
|  | 	return "en" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l mockLocale) Tr(s string, _ ...interface{}) string { | ||||||
|  | 	return "test translation" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockResponseWriter struct { | ||||||
|  | 	status int | ||||||
|  | 	size   int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Header() http.Header { | ||||||
|  | 	return map[string][]string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Write(b []byte) (int, error) { | ||||||
|  | 	rw.size += len(b) | ||||||
|  | 	return len(b), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) WriteHeader(status int) { | ||||||
|  | 	rw.status = status | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Flush() { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Status() int { | ||||||
|  | 	return rw.status | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Written() bool { | ||||||
|  | 	return rw.status > 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Size() int { | ||||||
|  | 	return rw.size | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) { | ||||||
|  | 	b(rw) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockRender struct { | ||||||
|  | 	http.ResponseWriter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) { | ||||||
|  | 	tr.ResponseWriter = rw | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) JSON(int, interface{}) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) JSONString(interface{}) (string, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) RawData(status int, _ []byte) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) PlainText(status int, _ []byte) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) { | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) { | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) XML(status int, _ interface{}) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) Error(status int, _ ...string) { | ||||||
|  | 	tr.Status(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) Status(status int) { | ||||||
|  | 	tr.ResponseWriter.WriteHeader(status) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) SetTemplatePath(string, string) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *mockRender) HasTemplateSet(string) bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | @ -31,6 +31,7 @@ func ListIssues(ctx *context.APIContext) { | ||||||
| 	issues, err := models.Issues(&models.IssuesOptions{ | 	issues, err := models.Issues(&models.IssuesOptions{ | ||||||
| 		RepoID:   ctx.Repo.Repository.ID, | 		RepoID:   ctx.Repo.Repository.ID, | ||||||
| 		Page:     ctx.QueryInt("page"), | 		Page:     ctx.QueryInt("page"), | ||||||
|  | 		PageSize: setting.UI.IssuePagingNum, | ||||||
| 		IsClosed: isClosed, | 		IsClosed: isClosed, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -193,6 +193,7 @@ func Issues(ctx *context.Context) { | ||||||
| 			MentionedID: mentionedID, | 			MentionedID: mentionedID, | ||||||
| 			MilestoneID: milestoneID, | 			MilestoneID: milestoneID, | ||||||
| 			Page:        pager.Current(), | 			Page:        pager.Current(), | ||||||
|  | 			PageSize:    setting.UI.IssuePagingNum, | ||||||
| 			IsClosed:    util.OptionalBoolOf(isShowClosed), | 			IsClosed:    util.OptionalBoolOf(isShowClosed), | ||||||
| 			IsPull:      util.OptionalBoolOf(isPullList), | 			IsPull:      util.OptionalBoolOf(isPullList), | ||||||
| 			Labels:      selectLabels, | 			Labels:      selectLabels, | ||||||
|  |  | ||||||
|  | @ -270,94 +270,77 @@ func Issues(ctx *context.Context) { | ||||||
| 		userRepoIDs = []int64{-1} | 		userRepoIDs = []int64{-1} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var issues []*models.Issue | 	opts := &models.IssuesOptions{ | ||||||
| 	switch filterMode { | 		RepoID:   repoID, | ||||||
| 	case models.FilterModeAll: | 		IsClosed: util.OptionalBoolOf(isShowClosed), | ||||||
| 		// Get all issues from repositories from this user.
 | 		IsPull:   util.OptionalBoolOf(isPullList), | ||||||
| 		issues, err = models.Issues(&models.IssuesOptions{ | 		SortType: sortType, | ||||||
| 			RepoIDs:  userRepoIDs, |  | ||||||
| 			RepoID:   repoID, |  | ||||||
| 			Page:     page, |  | ||||||
| 			IsClosed: util.OptionalBoolOf(isShowClosed), |  | ||||||
| 			IsPull:   util.OptionalBoolOf(isPullList), |  | ||||||
| 			SortType: sortType, |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 	case models.FilterModeAssign: |  | ||||||
| 		// Get all issues assigned to this user.
 |  | ||||||
| 		issues, err = models.Issues(&models.IssuesOptions{ |  | ||||||
| 			RepoID:     repoID, |  | ||||||
| 			AssigneeID: ctxUser.ID, |  | ||||||
| 			Page:       page, |  | ||||||
| 			IsClosed:   util.OptionalBoolOf(isShowClosed), |  | ||||||
| 			IsPull:     util.OptionalBoolOf(isPullList), |  | ||||||
| 			SortType:   sortType, |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 	case models.FilterModeCreate: |  | ||||||
| 		// Get all issues created by this user.
 |  | ||||||
| 		issues, err = models.Issues(&models.IssuesOptions{ |  | ||||||
| 			RepoID:   repoID, |  | ||||||
| 			PosterID: ctxUser.ID, |  | ||||||
| 			Page:     page, |  | ||||||
| 			IsClosed: util.OptionalBoolOf(isShowClosed), |  | ||||||
| 			IsPull:   util.OptionalBoolOf(isPullList), |  | ||||||
| 			SortType: sortType, |  | ||||||
| 		}) |  | ||||||
| 	case models.FilterModeMention: |  | ||||||
| 		// Get all issues created by this user.
 |  | ||||||
| 		issues, err = models.Issues(&models.IssuesOptions{ |  | ||||||
| 			RepoID:      repoID, |  | ||||||
| 			MentionedID: ctxUser.ID, |  | ||||||
| 			Page:        page, |  | ||||||
| 			IsClosed:    util.OptionalBoolOf(isShowClosed), |  | ||||||
| 			IsPull:      util.OptionalBoolOf(isPullList), |  | ||||||
| 			SortType:    sortType, |  | ||||||
| 		}) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	switch filterMode { | ||||||
|  | 	case models.FilterModeAll: | ||||||
|  | 		opts.RepoIDs = userRepoIDs | ||||||
|  | 	case models.FilterModeAssign: | ||||||
|  | 		opts.AssigneeID = ctxUser.ID | ||||||
|  | 	case models.FilterModeCreate: | ||||||
|  | 		opts.PosterID = ctxUser.ID | ||||||
|  | 	case models.FilterModeMention: | ||||||
|  | 		opts.MentionedID = ctxUser.ID | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	counts, err := models.CountIssuesByRepo(opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Handle(500, "CountIssuesByRepo", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	opts.Page = page | ||||||
|  | 	opts.PageSize = setting.UI.IssuePagingNum | ||||||
|  | 	issues, err := models.Issues(opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Handle(500, "Issues", err) | 		ctx.Handle(500, "Issues", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	showRepos, err := models.IssueList(issues).LoadRepositories() | 	showReposMap := make(map[int64]*models.Repository, len(counts)) | ||||||
| 	if err != nil { | 	for repoID := range counts { | ||||||
| 		ctx.Handle(500, "LoadRepositories", fmt.Errorf("%v", err)) | 		repo, err := models.GetRepositoryByID(repoID) | ||||||
| 		return | 		if err != nil { | ||||||
|  | 			ctx.Handle(500, "GetRepositoryByID", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		showReposMap[repoID] = repo | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if repoID > 0 { | 	if repoID > 0 { | ||||||
| 		var theRepo *models.Repository | 		if _, ok := showReposMap[repoID]; !ok { | ||||||
| 		for _, repo := range showRepos { | 			repo, err := models.GetRepositoryByID(repoID) | ||||||
| 			if repo.ID == repoID { |  | ||||||
| 				theRepo = repo |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if theRepo == nil { |  | ||||||
| 			theRepo, err = models.GetRepositoryByID(repoID) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", repoID, err)) | 				ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[%d]%v", repoID, err)) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			showRepos = append(showRepos, theRepo) | 			showReposMap[repoID] = repo | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		repo := showReposMap[repoID] | ||||||
|  | 
 | ||||||
| 		// Check if user has access to given repository.
 | 		// Check if user has access to given repository.
 | ||||||
| 		if !theRepo.IsOwnedBy(ctxUser.ID) && !theRepo.HasAccess(ctxUser) { | 		if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser) { | ||||||
| 			ctx.Handle(404, "Issues", fmt.Errorf("#%d", repoID)) | 			ctx.Status(404) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = models.RepositoryList(showRepos).LoadAttributes() | 	showRepos := models.RepositoryListOfMap(showReposMap) | ||||||
| 	if err != nil { | 	if err = showRepos.LoadAttributes(); err != nil { | ||||||
| 		ctx.Handle(500, "LoadAttributes", fmt.Errorf("%v", err)) | 		ctx.Handle(500, "LoadAttributes", fmt.Errorf("%v", err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	for _, issue := range issues { | ||||||
|  | 		issue.Repo = showReposMap[issue.RepoID] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) | 	issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) | ||||||
| 
 | 
 | ||||||
| 	var total int | 	var total int | ||||||
|  | @ -369,6 +352,7 @@ func Issues(ctx *context.Context) { | ||||||
| 
 | 
 | ||||||
| 	ctx.Data["Issues"] = issues | 	ctx.Data["Issues"] = issues | ||||||
| 	ctx.Data["Repos"] = showRepos | 	ctx.Data["Repos"] = showRepos | ||||||
|  | 	ctx.Data["Counts"] = counts | ||||||
| 	ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) | 	ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) | ||||||
| 	ctx.Data["IssueStats"] = issueStats | 	ctx.Data["IssueStats"] = issueStats | ||||||
| 	ctx.Data["ViewType"] = viewType | 	ctx.Data["ViewType"] = viewType | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								routers/user/home_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								routers/user/home_test.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | // Copyright 2017 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package user | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/test" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestIssues(t *testing.T) { | ||||||
|  | 	setting.UI.IssuePagingNum = 1 | ||||||
|  | 	assert.NoError(t, models.LoadFixtures()) | ||||||
|  | 
 | ||||||
|  | 	ctx := test.MockContext(t) | ||||||
|  | 	ctx.User = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | ||||||
|  | 	ctx.SetParams(":type", "issues") | ||||||
|  | 	ctx.Req.Form.Set("state", "closed") | ||||||
|  | 	Issues(ctx) | ||||||
|  | 	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | ||||||
|  | 
 | ||||||
|  | 	assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"]) | ||||||
|  | 	assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) | ||||||
|  | 	assert.Len(t, ctx.Data["Issues"], 1) | ||||||
|  | 	assert.Len(t, ctx.Data["Repos"], 2) | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								routers/user/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								routers/user/main_test.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | // Copyright 2017 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package user | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 
 | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" // for the test engine
 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	if err := models.CreateTestEngine("../../models/fixtures/"); err != nil { | ||||||
|  | 		fmt.Printf("Error creating test engine: %v\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	setting.AppURL = "https://try.gitea.io/" | ||||||
|  | 	setting.RunUser = "runuser" | ||||||
|  | 	setting.SSH.Port = 3000 | ||||||
|  | 	setting.SSH.Domain = "try.gitea.io" | ||||||
|  | 	setting.RepoRootPath = filepath.Join(os.TempDir(), "repos") | ||||||
|  | 	setting.AppDataPath = filepath.Join(os.TempDir(), "appdata") | ||||||
|  | 
 | ||||||
|  | 	os.Exit(m.Run()) | ||||||
|  | } | ||||||
|  | @ -23,7 +23,7 @@ | ||||||
| 					{{range .Repos}} | 					{{range .Repos}} | ||||||
| 						<a class="{{if eq $.RepoID .ID}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&sort={{$.SortType}}&state={{$.State}}"> | 						<a class="{{if eq $.RepoID .ID}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&sort={{$.SortType}}&state={{$.State}}"> | ||||||
| 							<span class="text truncate">{{.FullName}}</span> | 							<span class="text truncate">{{.FullName}}</span> | ||||||
| 							<div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{if $.IsShowClosed}}{{if $.PageIsPulls}}{{.NumClosedPulls}}{{else}}{{.NumClosedIssues}}{{end}}{{else}}{{if $.PageIsPulls}}{{.NumOpenPulls}}{{else}}{{.NumOpenIssues}}{{end}}{{end}}</div> | 							<div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{index $.Counts .ID}}</div> | ||||||
| 						</a> | 						</a> | ||||||
| 					{{end}} | 					{{end}} | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue