diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index df8b807eda..fda155644a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -163,8 +163,8 @@ search = Search... type_tooltip = Search type fuzzy = Fuzzy fuzzy_tooltip = Include results that also match the search term closely -match = Match -match_tooltip = Include only results that match the exact search term +exact = Exact +exact_tooltip = Include only results that match the exact search term repo_kind = Search repos... user_kind = Search users... org_kind = Search orgs... @@ -178,6 +178,8 @@ branch_kind = Search branches... commit_kind = Search commits... runner_kind = Search runners... no_results = No matching results found. +issue_kind = Search issues... +pull_kind = Search pulls... keyword_search_unavailable = Searching by keyword is currently not available. Please contact the site administrator. [aria] diff --git a/release-notes/8.0.0/feat/4160.md b/release-notes/8.0.0/feat/4160.md new file mode 100644 index 0000000000..c8b7bdf47e --- /dev/null +++ b/release-notes/8.0.0/feat/4160.md @@ -0,0 +1,3 @@ +Added support for fuzzy searching issues and pulls +- support for `/issues` and `/pulls` were ported from [`gitea#be5be0ac81`](https://github.com/go-gitea/gitea/commit/be5be0ac81ce50ad5adb079af6ca4e8c396aaece) +- support for `/user/repo/issues` and `/user/repo/pulls` were added diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 9806dfe966..e34f90c73b 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -201,6 +201,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt keyword = "" } + isFuzzy := ctx.FormBool("fuzzy") + var mileIDs []int64 if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned mileIDs = []int64{milestoneID} @@ -221,7 +223,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt IssueIDs: nil, } if keyword != "" { - allIssueIDs, err := issueIDsFromSearch(ctx, keyword, statsOpts) + allIssueIDs, err := issueIDsFromSearch(ctx, keyword, isFuzzy, statsOpts) if err != nil { if issue_indexer.IsAvailable(ctx) { ctx.ServerError("issueIDsFromSearch", err) @@ -289,7 +291,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt var issues issues_model.IssueList { - ids, err := issueIDsFromSearch(ctx, keyword, &issues_model.IssuesOptions{ + ids, err := issueIDsFromSearch(ctx, keyword, isFuzzy, &issues_model.IssuesOptions{ Paginator: &db.ListOptions{ Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, @@ -465,6 +467,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["ProjectID"] = projectID ctx.Data["AssigneeID"] = assigneeID ctx.Data["PosterID"] = posterID + ctx.Data["IsFuzzy"] = isFuzzy ctx.Data["Keyword"] = keyword switch { case isShowClosed.Value(): @@ -486,12 +489,17 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt pager.AddParam(ctx, "assignee", "AssigneeID") pager.AddParam(ctx, "poster", "PosterID") pager.AddParam(ctx, "archived", "ShowArchivedLabels") + pager.AddParam(ctx, "fuzzy", "IsFuzzy") ctx.Data["Page"] = pager } -func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) +func issueIDsFromSearch(ctx *context.Context, keyword string, fuzzy bool, opts *issues_model.IssuesOptions) ([]int64, error) { + ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( + func(o *issue_indexer.SearchOptions) { + o.IsFuzzyKeyword = fuzzy + }, + )) if err != nil { return nil, fmt.Errorf("SearchIssues: %w", err) } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 52aca1825a..1a8a9a4126 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -447,6 +447,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { User: ctx.Doer, } + isFuzzy := ctx.FormBool("fuzzy") + // Search all repositories which // // As user: @@ -576,7 +578,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // USING FINAL STATE OF opts FOR A QUERY. var issues issues_model.IssueList { - issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) + issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( + func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy }, + )) if err != nil { ctx.ServerError("issueIDsFromSearch", err) return @@ -597,7 +601,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // ------------------------------- // Fill stats to post to ctx.Data. // ------------------------------- - issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts)) + issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy( + func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy }, + )) if err != nil { ctx.ServerError("getUserIssueStats", err) return @@ -652,6 +658,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["SelectLabels"] = selectedLabels ctx.Data["PageIsOrgIssues"] = org != nil + ctx.Data["IsFuzzy"] = isFuzzy if isShowClosed { ctx.Data["State"] = "closed" @@ -667,6 +674,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { pager.AddParam(ctx, "labels", "SelectLabels") pager.AddParam(ctx, "milestone", "MilestoneID") pager.AddParam(ctx, "assignee", "AssigneeID") + pager.AddParam(ctx, "fuzzy", "IsFuzzy") ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplIssues) diff --git a/templates/repo/issue/search.tmpl b/templates/repo/issue/search.tmpl index 769387b51c..007fe41920 100644 --- a/templates/repo/issue/search.tmpl +++ b/templates/repo/issue/search.tmpl @@ -9,10 +9,10 @@ {{end}} - {{template "shared/search/input" dict "Value" .Keyword}} - {{if .PageIsIssueList}} - + {{if .PageIsPullList}} + {{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} + {{else}} + {{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{end}} - {{template "shared/search/button"}} diff --git a/templates/shared/search/fuzzy.tmpl b/templates/shared/search/fuzzy.tmpl index 6ddb03c004..5c09d3c150 100644 --- a/templates/shared/search/fuzzy.tmpl +++ b/templates/shared/search/fuzzy.tmpl @@ -2,9 +2,9 @@ {{/* IsFuzzy - state of the fuzzy search toggle */}} diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 0f147d4a5f..052536296e 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -5,11 +5,11 @@ {{template "base/alert" .}}
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index d5bdea2a19..cfea110521 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -107,6 +107,7 @@ func TestViewIssuesKeyword(t *testing.T) { }) issues.UpdateIssueIndexer(context.Background(), issue.ID) time.Sleep(time.Second * 1) + const keyword = "first" req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) resp := MakeRequest(t, req, http.StatusOK) @@ -120,6 +121,27 @@ func TestViewIssuesKeyword(t *testing.T) { assert.False(t, issue.IsPull) assertMatch(t, issue, keyword) }) + + // keyword: 'firstt' + // should not match when fuzzy searching is disabled + req = NewRequestf(t, "GET", "%s/issues?q=%st&fuzzy=false", repo.Link(), keyword) + resp = MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + issuesSelection = getIssuesSelection(t, htmlDoc) + assert.EqualValues(t, 0, issuesSelection.Length()) + + // should match as 'first' when fuzzy seaeching is enabled + req = NewRequestf(t, "GET", "%s/issues?q=%st&fuzzy=true", repo.Link(), keyword) + resp = MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + issuesSelection = getIssuesSelection(t, htmlDoc) + assert.EqualValues(t, 1, issuesSelection.Length()) + issuesSelection.Each(func(_ int, selection *goquery.Selection) { + issue := getIssue(t, repo.ID, selection) + assert.False(t, issue.IsClosed) + assert.False(t, issue.IsPull) + assertMatch(t, issue, keyword) + }) } func TestNoLoginViewIssue(t *testing.T) { diff --git a/web_src/css/form.css b/web_src/css/form.css index c757234e3b..dd2788f542 100644 --- a/web_src/css/form.css +++ b/web_src/css/form.css @@ -40,7 +40,7 @@ textarea, /* fix fomantic small dropdown having inconsistent padding with input */ .ui.small.selection.dropdown { - padding: .67857143em 3.2em .67857143em 1em; + padding: .67857143em 1.6em .67857143em 1em; } input:hover,