Skip to content

Commit 7470044

Browse files
authored
Merge branch 'main' into bugfix/variable_router
2 parents 1514441 + 085f273 commit 7470044

File tree

9 files changed

+228
-56
lines changed

9 files changed

+228
-56
lines changed

models/issues/issue.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
user_model "code.gitea.io/gitea/models/user"
1818
"code.gitea.io/gitea/modules/container"
1919
"code.gitea.io/gitea/modules/log"
20+
"code.gitea.io/gitea/modules/optional"
2021
"code.gitea.io/gitea/modules/setting"
2122
api "code.gitea.io/gitea/modules/structs"
2223
"code.gitea.io/gitea/modules/timeutil"
@@ -501,6 +502,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
501502
return issue, nil
502503
}
503504

505+
func isPullToCond(isPull optional.Option[bool]) builder.Cond {
506+
if isPull.Has() {
507+
return builder.Eq{"is_pull": isPull.Value()}
508+
}
509+
return builder.NewCond()
510+
}
511+
512+
func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) {
513+
issues := make([]*Issue, 0, pageSize)
514+
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
515+
And(isPullToCond(isPull)).
516+
OrderBy("updated_unix DESC").
517+
Limit(pageSize).
518+
Find(&issues)
519+
return issues, err
520+
}
521+
522+
func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) {
523+
cond := builder.NewCond()
524+
if excludedID > 0 {
525+
cond = cond.And(builder.Neq{"`id`": excludedID})
526+
}
527+
528+
// It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?)
529+
// The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) + content"
530+
// But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results.
531+
// So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future.
532+
cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword))
533+
534+
issues := make([]*Issue, 0, pageSize)
535+
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
536+
And(isPullToCond(isPull)).
537+
And(cond).
538+
OrderBy("updated_unix DESC, `index` DESC").
539+
Limit(pageSize).
540+
Find(&issues)
541+
return issues, err
542+
}
543+
504544
// GetIssueWithAttrsByIndex returns issue by index in a repository.
505545
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
506546
issue, err := GetIssueByIndex(ctx, repoID, index)

options/locale/locale_en-US.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,13 @@ show_only_public = Showing only public
385385

386386
issues.in_your_repos = In your repositories
387387

388+
guide_title = No Activity
389+
guide_desc = You are currently not following any repositories or users, so there is no content to display. You can explore repositories or users of interest from the links below.
390+
explore_repos = Explore repositories
391+
explore_users = Explore users
392+
empty_org = There are no organizations yet.
393+
empty_repo = There are no repositories yet.
394+
388395
[explore]
389396
repos = Repositories
390397
users = Users

routers/web/repo/issue_suggestions.go

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ package repo
66
import (
77
"net/http"
88

9-
"code.gitea.io/gitea/models/db"
10-
issues_model "code.gitea.io/gitea/models/issues"
119
"code.gitea.io/gitea/models/unit"
12-
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
1310
"code.gitea.io/gitea/modules/optional"
14-
"code.gitea.io/gitea/modules/structs"
1511
"code.gitea.io/gitea/services/context"
12+
issue_service "code.gitea.io/gitea/services/issue"
1613
)
1714

1815
// IssueSuggestions returns a list of issue suggestions
@@ -29,54 +26,11 @@ func IssueSuggestions(ctx *context.Context) {
2926
isPull = optional.Some(false)
3027
}
3128

32-
searchOpt := &issue_indexer.SearchOptions{
33-
Paginator: &db.ListOptions{
34-
Page: 0,
35-
PageSize: 5,
36-
},
37-
Keyword: keyword,
38-
RepoIDs: []int64{ctx.Repo.Repository.ID},
39-
IsPull: isPull,
40-
IsClosed: nil,
41-
SortBy: issue_indexer.SortByUpdatedDesc,
42-
}
43-
44-
ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt)
45-
if err != nil {
46-
ctx.ServerError("SearchIssues", err)
47-
return
48-
}
49-
issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
29+
suggestions, err := issue_service.GetSuggestion(ctx, ctx.Repo.Repository, isPull, keyword)
5030
if err != nil {
51-
ctx.ServerError("FindIssuesByIDs", err)
31+
ctx.ServerError("GetSuggestion", err)
5232
return
5333
}
5434

55-
suggestions := make([]*structs.Issue, 0, len(issues))
56-
57-
for _, issue := range issues {
58-
suggestion := &structs.Issue{
59-
ID: issue.ID,
60-
Index: issue.Index,
61-
Title: issue.Title,
62-
State: issue.State(),
63-
}
64-
65-
if issue.IsPull {
66-
if err := issue.LoadPullRequest(ctx); err != nil {
67-
ctx.ServerError("LoadPullRequest", err)
68-
return
69-
}
70-
if issue.PullRequest != nil {
71-
suggestion.PullRequest = &structs.PullRequestMeta{
72-
HasMerged: issue.PullRequest.HasMerged,
73-
IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx),
74-
}
75-
}
76-
}
77-
78-
suggestions = append(suggestions, suggestion)
79-
}
80-
8135
ctx.JSON(http.StatusOK, suggestions)
8236
}

services/issue/suggestion.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package issue
5+
6+
import (
7+
"context"
8+
"strconv"
9+
10+
issues_model "code.gitea.io/gitea/models/issues"
11+
repo_model "code.gitea.io/gitea/models/repo"
12+
"code.gitea.io/gitea/modules/optional"
13+
"code.gitea.io/gitea/modules/structs"
14+
)
15+
16+
func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull optional.Option[bool], keyword string) ([]*structs.Issue, error) {
17+
var issues issues_model.IssueList
18+
var err error
19+
pageSize := 5
20+
if keyword == "" {
21+
issues, err = issues_model.FindLatestUpdatedIssues(ctx, repo.ID, isPull, pageSize)
22+
if err != nil {
23+
return nil, err
24+
}
25+
} else {
26+
indexKeyword, _ := strconv.ParseInt(keyword, 10, 64)
27+
var issueByIndex *issues_model.Issue
28+
var excludedID int64
29+
if indexKeyword > 0 {
30+
issueByIndex, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword)
31+
if err != nil && !issues_model.IsErrIssueNotExist(err) {
32+
return nil, err
33+
}
34+
if issueByIndex != nil {
35+
excludedID = issueByIndex.ID
36+
pageSize--
37+
}
38+
}
39+
40+
issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, excludedID, pageSize)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
if issueByIndex != nil {
46+
issues = append([]*issues_model.Issue{issueByIndex}, issues...)
47+
}
48+
}
49+
50+
if err := issues.LoadPullRequests(ctx); err != nil {
51+
return nil, err
52+
}
53+
54+
suggestions := make([]*structs.Issue, 0, len(issues))
55+
for _, issue := range issues {
56+
suggestion := &structs.Issue{
57+
ID: issue.ID,
58+
Index: issue.Index,
59+
Title: issue.Title,
60+
State: issue.State(),
61+
}
62+
63+
if issue.IsPull && issue.PullRequest != nil {
64+
suggestion.PullRequest = &structs.PullRequestMeta{
65+
HasMerged: issue.PullRequest.HasMerged,
66+
IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx),
67+
}
68+
}
69+
suggestions = append(suggestions, suggestion)
70+
}
71+
72+
return suggestions, nil
73+
}

services/issue/suggestion_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package issue
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
repo_model "code.gitea.io/gitea/models/repo"
11+
"code.gitea.io/gitea/models/unittest"
12+
"code.gitea.io/gitea/modules/optional"
13+
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func Test_Suggestion(t *testing.T) {
18+
assert.NoError(t, unittest.PrepareTestDatabase())
19+
20+
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
21+
22+
testCases := []struct {
23+
keyword string
24+
isPull optional.Option[bool]
25+
expectedIndexes []int64
26+
}{
27+
{
28+
keyword: "",
29+
expectedIndexes: []int64{5, 1, 4, 2, 3},
30+
},
31+
{
32+
keyword: "1",
33+
expectedIndexes: []int64{1},
34+
},
35+
{
36+
keyword: "issue",
37+
expectedIndexes: []int64{4, 1, 2, 3},
38+
},
39+
{
40+
keyword: "pull",
41+
expectedIndexes: []int64{5},
42+
},
43+
}
44+
45+
for _, testCase := range testCases {
46+
t.Run(testCase.keyword, func(t *testing.T) {
47+
issues, err := GetSuggestion(db.DefaultContext, repo1, testCase.isPull, testCase.keyword)
48+
assert.NoError(t, err)
49+
50+
issueIndexes := make([]int64, 0, len(issues))
51+
for _, issue := range issues {
52+
issueIndexes = append(issueIndexes, issue.Index)
53+
}
54+
assert.EqualValues(t, testCase.expectedIndexes, issueIndexes)
55+
})
56+
}
57+
}

templates/user/dashboard/dashboard.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
<div class="flex-container-main">
66
{{template "base/alert" .}}
77
{{template "user/heatmap" .}}
8-
{{template "user/dashboard/feeds" .}}
8+
{{if .Feeds}}
9+
{{template "user/dashboard/feeds" .}}
10+
{{else}}
11+
{{template "user/dashboard/guide" .}}
12+
{{end}}
913
</div>
1014
{{template "user/dashboard/repolist" .}}
1115
</div>

templates/user/dashboard/guide.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div class="tw-text-center tw-p-8">
2+
{{svg "octicon-package" 24 "tw-text-placeholder-text"}}
3+
<h3 class="tw-my-4">{{ctx.Locale.Tr "home.guide_title"}}</h3>
4+
<p class="tw-text-placeholder-text">{{ctx.Locale.Tr "home.guide_desc"}}</p>
5+
<div>
6+
<a href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "home.explore_repos"}}</a>
7+
<span>·</span>
8+
<a href="{{AppSubUrl}}/explore/users">{{ctx.Locale.Tr "home.explore_users"}}</a>
9+
</div>
10+
</div>

templates/user/dashboard/repolist.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const data = {
55
isMirrorsEnabled: {{.MirrorsEnabled}},
66
isStarsEnabled: {{not .IsDisableStars}},
77

8+
canCreateMigrations: {{not .DisableMigrations}},
9+
10+
textNoOrg: {{ctx.Locale.Tr "home.empty_org"}},
11+
textNoRepo: {{ctx.Locale.Tr "home.empty_repo"}},
812
textRepository: {{ctx.Locale.Tr "repository"}},
913
textOrganization: {{ctx.Locale.Tr "organization"}},
1014
textMyRepos: {{ctx.Locale.Tr "home.my_repos"}},

web_src/js/components/DashboardRepoList.vue

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export default defineComponent({
113113
this.changeReposFilter(this.reposFilter);
114114
fomanticQuery(el.querySelector('.ui.dropdown')).dropdown();
115115
nextTick(() => {
116-
this.$refs.search.focus();
116+
this.$refs.search?.focus();
117117
});
118118
119119
this.textArchivedFilterTitles = {
@@ -243,7 +243,7 @@ export default defineComponent({
243243
if (!this.reposTotalCount) {
244244
const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
245245
response = await GET(totalCountSearchURL);
246-
this.reposTotalCount = response.headers.get('X-Total-Count') ?? '?';
246+
this.reposTotalCount = parseInt(response.headers.get('X-Total-Count') ?? '0');
247247
}
248248
249249
response = await GET(searchedURL);
@@ -336,7 +336,6 @@ export default defineComponent({
336336
},
337337
},
338338
});
339-
340339
</script>
341340
<template>
342341
<div>
@@ -354,7 +353,15 @@ export default defineComponent({
354353
<svg-icon name="octicon-plus"/>
355354
</a>
356355
</h4>
357-
<div class="ui attached segment repos-search">
356+
<div v-if="!reposTotalCount" class="ui attached segment">
357+
<div v-if="!isLoading" class="empty-repo-or-org">
358+
<svg-icon name="octicon-git-branch" :size="24"/>
359+
<p>{{ textNoRepo }}</p>
360+
</div>
361+
<!-- using the loading indicator here will cause more (unnecessary) page flickers, so at the moment, not use the loading indicator -->
362+
<!-- <div v-else class="is-loading loading-icon-2px tw-min-h-16"/> -->
363+
</div>
364+
<div v-else class="ui attached segment repos-search">
358365
<div class="ui small fluid action left icon input">
359366
<input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">
360367
<i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i>
@@ -438,7 +445,7 @@ export default defineComponent({
438445
class="item navigation tw-py-1" :class="{'disabled': page === 1}"
439446
@click="changePage(page - 1)" :title="textPreviousPage"
440447
>
441-
<svg-icon name="octicon-chevron-left" :size="16" clsas-name="tw-mr-1"/>
448+
<svg-icon name="octicon-chevron-left" :size="16" clsas="tw-mr-1"/>
442449
</a>
443450
<a class="active item tw-py-1">{{ page }}</a>
444451
<a
@@ -467,7 +474,13 @@ export default defineComponent({
467474
<svg-icon name="octicon-plus"/>
468475
</a>
469476
</h4>
470-
<div v-if="organizations.length" class="ui attached table segment tw-rounded-b">
477+
<div v-if="!organizations.length" class="ui attached segment">
478+
<div class="empty-repo-or-org">
479+
<svg-icon name="octicon-organization" :size="24"/>
480+
<p>{{ textNoOrg }}</p>
481+
</div>
482+
</div>
483+
<div v-else class="ui attached table segment tw-rounded-b">
471484
<ul class="repo-owner-name-list">
472485
<li class="tw-flex tw-items-center tw-py-2" v-for="org in organizations" :key="org.name">
473486
<a class="repo-list-link muted" :href="subUrl + '/' + encodeURIComponent(org.name)">
@@ -546,4 +559,14 @@ ul li:not(:last-child) {
546559
.repo-owner-name-list li.active {
547560
background: var(--color-hover);
548561
}
562+
563+
.empty-repo-or-org {
564+
margin-top: 1em;
565+
text-align: center;
566+
color: var(--color-placeholder-text);
567+
}
568+
569+
.empty-repo-or-org p {
570+
margin: 1em auto;
571+
}
549572
</style>

0 commit comments

Comments
 (0)