mirror of https://github.com/go-gitea/gitea.git
Remove the useless function `GetUserIssueStats` and move relevant tests to `indexer_test.go` (#27067)
Since the issue indexer has been refactored, the issue overview webpage is built by the `buildIssueOverview` function and underlying `indexer.Search` function and `GetIssueStats` instead of `GetUserIssueStats`. So the function is no longer used. I moved the relevant tests to `indexer_test.go` and since the search option changed from `IssueOptions` to `SearchOptions`, most of the tests are useless now. We need more tests about the db indexer because those tests are highly connected with the issue overview webpage and now this page has several bugs. Any advice about those test cases is appreciated. --------- Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>pull/27092/head
parent
8d0343e028
commit
0de09d3afc
|
@ -5,7 +5,6 @@ package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -181,195 +180,6 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
|
||||||
func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) {
|
|
||||||
if opts.User == nil {
|
|
||||||
return nil, errors.New("issue stats without user")
|
|
||||||
}
|
|
||||||
if opts.IsPull.IsNone() {
|
|
||||||
return nil, errors.New("unaccepted ispull option")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
stats := &IssueStats{}
|
|
||||||
|
|
||||||
cond := builder.NewCond()
|
|
||||||
|
|
||||||
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
|
|
||||||
|
|
||||||
if len(opts.RepoIDs) > 0 {
|
|
||||||
cond = cond.And(builder.In("issue.repo_id", opts.RepoIDs))
|
|
||||||
}
|
|
||||||
if len(opts.IssueIDs) > 0 {
|
|
||||||
cond = cond.And(builder.In("issue.id", opts.IssueIDs))
|
|
||||||
}
|
|
||||||
if opts.RepoCond != nil {
|
|
||||||
cond = cond.And(opts.RepoCond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.User != nil {
|
|
||||||
cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()))
|
|
||||||
}
|
|
||||||
|
|
||||||
sess := func(cond builder.Cond) *xorm.Session {
|
|
||||||
s := db.GetEngine(db.DefaultContext).
|
|
||||||
Join("INNER", "repository", "`issue`.repo_id = `repository`.id").
|
|
||||||
Where(cond)
|
|
||||||
if len(opts.LabelIDs) > 0 {
|
|
||||||
s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id").
|
|
||||||
In("issue_label.label_id", opts.LabelIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.IsArchived != util.OptionalBoolNone {
|
|
||||||
s.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
switch filterMode {
|
|
||||||
case FilterModeAll, FilterModeYourRepositories:
|
|
||||||
stats.OpenCount, err = sess(cond).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = sess(cond).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case FilterModeAssign:
|
|
||||||
stats.OpenCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case FilterModeCreate:
|
|
||||||
stats.OpenCount, err = applyPosterCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = applyPosterCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case FilterModeMention:
|
|
||||||
stats.OpenCount, err = applyMentionedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = applyMentionedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case FilterModeReviewRequested:
|
|
||||||
stats.OpenCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case FilterModeReviewed:
|
|
||||||
stats.OpenCount, err = applyReviewedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", false).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stats.ClosedCount, err = applyReviewedCondition(sess(cond), opts.User.ID).
|
|
||||||
And("issue.is_closed = ?", true).
|
|
||||||
Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed.IsTrue()})
|
|
||||||
stats.AssignCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.CreateCount, err = applyPosterCondition(sess(cond), opts.User.ID).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.MentionCount, err = applyMentionedCondition(sess(cond), opts.User.ID).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.YourRepositoriesCount, err = sess(cond).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.ReviewRequestedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.ReviewedCount, err = applyReviewedCondition(sess(cond), opts.User.ID).Count(new(Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
|
|
||||||
func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen, numClosed int64) {
|
|
||||||
countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session {
|
|
||||||
sess := db.GetEngine(db.DefaultContext).
|
|
||||||
Where("is_closed = ?", isClosed).
|
|
||||||
And("is_pull = ?", isPull).
|
|
||||||
And("repo_id = ?", repoID)
|
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
|
||||||
|
|
||||||
openCountSession := countSession(false, isPull, repoID)
|
|
||||||
closedCountSession := countSession(true, isPull, repoID)
|
|
||||||
|
|
||||||
switch filterMode {
|
|
||||||
case FilterModeAssign:
|
|
||||||
applyAssigneeCondition(openCountSession, uid)
|
|
||||||
applyAssigneeCondition(closedCountSession, uid)
|
|
||||||
case FilterModeCreate:
|
|
||||||
applyPosterCondition(openCountSession, uid)
|
|
||||||
applyPosterCondition(closedCountSession, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
openResult, _ := openCountSession.Count(new(Issue))
|
|
||||||
closedResult, _ := closedCountSession.Count(new(Issue))
|
|
||||||
|
|
||||||
return openResult, closedResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountOrphanedIssues count issues without a repo
|
// CountOrphanedIssues count issues without a repo
|
||||||
func CountOrphanedIssues(ctx context.Context) (int64, error) {
|
func CountOrphanedIssues(ctx context.Context) (int64, error) {
|
||||||
return db.GetEngine(ctx).
|
return db.GetEngine(ctx).
|
||||||
|
|
|
@ -13,12 +13,10 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -204,128 +202,6 @@ func TestIssues(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserIssueStats(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
for _, test := range []struct {
|
|
||||||
FilterMode int
|
|
||||||
Opts issues_model.IssuesOptions
|
|
||||||
ExpectedIssueStats issues_model.IssueStats
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
issues_model.FilterModeAll,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
RepoIDs: []int64{1},
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 6
|
|
||||||
AssignCount: 1, // 6
|
|
||||||
CreateCount: 1, // 6
|
|
||||||
OpenCount: 1, // 6
|
|
||||||
ClosedCount: 1, // 1
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeAll,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
RepoIDs: []int64{1},
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
IsClosed: util.OptionalBoolTrue,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 6
|
|
||||||
AssignCount: 0,
|
|
||||||
CreateCount: 0,
|
|
||||||
OpenCount: 1, // 6
|
|
||||||
ClosedCount: 1, // 1
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeAssign,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 6
|
|
||||||
AssignCount: 1, // 6
|
|
||||||
CreateCount: 1, // 6
|
|
||||||
OpenCount: 1, // 6
|
|
||||||
ClosedCount: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeCreate,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 6
|
|
||||||
AssignCount: 1, // 6
|
|
||||||
CreateCount: 1, // 6
|
|
||||||
OpenCount: 1, // 6
|
|
||||||
ClosedCount: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeMention,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 6
|
|
||||||
AssignCount: 1, // 6
|
|
||||||
CreateCount: 1, // 6
|
|
||||||
MentionCount: 0,
|
|
||||||
OpenCount: 0,
|
|
||||||
ClosedCount: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeCreate,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
|
||||||
IssueIDs: []int64{1},
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 1, // 1
|
|
||||||
AssignCount: 1, // 1
|
|
||||||
CreateCount: 1, // 1
|
|
||||||
OpenCount: 1, // 1
|
|
||||||
ClosedCount: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
issues_model.FilterModeAll,
|
|
||||||
issues_model.IssuesOptions{
|
|
||||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}),
|
|
||||||
Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}),
|
|
||||||
Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}),
|
|
||||||
IsPull: util.OptionalBoolFalse,
|
|
||||||
},
|
|
||||||
issues_model.IssueStats{
|
|
||||||
YourRepositoriesCount: 2,
|
|
||||||
AssignCount: 1,
|
|
||||||
CreateCount: 1,
|
|
||||||
OpenCount: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) {
|
|
||||||
stats, err := issues_model.GetUserIssueStats(test.FilterMode, test.Opts)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(t, test.ExpectedIssueStats, *stats)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue_loadTotalTimes(t *testing.T) {
|
func TestIssue_loadTotalTimes(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
ms, err := issues_model.GetIssueByID(db.DefaultContext, 2)
|
ms, err := issues_model.GetIssueByID(db.DefaultContext, 2)
|
||||||
|
|
|
@ -5,6 +5,7 @@ package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/indexer/issues/bleve"
|
"code.gitea.io/gitea/modules/indexer/issues/bleve"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/models"
|
_ "code.gitea.io/gitea/models"
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
|
@ -89,7 +91,7 @@ func TestBleveSearchIssues(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDBSearchIssues(t *testing.T) {
|
func TestDBSearchIssuesWithKeyword(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
setting.Indexer.IssueType = "db"
|
setting.Indexer.IssueType = "db"
|
||||||
|
@ -131,3 +133,82 @@ func TestDBSearchIssues(t *testing.T) {
|
||||||
assert.EqualValues(t, []int64{1}, ids)
|
assert.EqualValues(t, []int64{1}, ids)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add more tests
|
||||||
|
func TestDBSearchIssueWithoutKeyword(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
setting.Indexer.IssueType = "db"
|
||||||
|
InitIssueIndexer(true)
|
||||||
|
|
||||||
|
int64Pointer := func(x int64) *int64 {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
for _, test := range []struct {
|
||||||
|
opts SearchOptions
|
||||||
|
expectedIDs []int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
},
|
||||||
|
[]int64{11, 5, 3, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
AssigneeID: int64Pointer(1),
|
||||||
|
},
|
||||||
|
[]int64{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
PosterID: int64Pointer(1),
|
||||||
|
},
|
||||||
|
[]int64{11, 3, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
IsClosed: util.OptionalBoolFalse,
|
||||||
|
},
|
||||||
|
[]int64{11, 3, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
IsClosed: util.OptionalBoolTrue,
|
||||||
|
},
|
||||||
|
[]int64{5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
},
|
||||||
|
[]int64{11, 5, 3, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
AssigneeID: int64Pointer(1),
|
||||||
|
},
|
||||||
|
[]int64{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
RepoIDs: []int64{1},
|
||||||
|
PosterID: int64Pointer(1),
|
||||||
|
},
|
||||||
|
[]int64{11, 3, 2, 1},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("%#v", test.opts), func(t *testing.T) {
|
||||||
|
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue