feat(issue search): query string for boolean operators and phrase search (#6952)

closes #6909

related to forgejo/design#14

# Description

Adds the following boolean operators for issues when using an indexer (with minor caveats)

- `+term`: `term` MUST be present for any result
- `-term`: negation; exclude results that contain `term`
- `"this is a term"`: matches the exact phrase `this is a term`

In all cases the special characters may be escaped by the prefix `\`

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6952
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
Co-committed-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
This commit is contained in:
Shiny Nematoda 2025-02-23 08:35:35 +00:00 committed by Earl Warren
parent eaa641c21e
commit cddf608cb9
19 changed files with 451 additions and 192 deletions

View file

@ -1120,7 +1120,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1145,32 +1144,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
t.Run("Fuzzy", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/issues?fuzzy=false")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
called := false
htmlDoc.Find("#issue-filters a[href^='?']").Each(func(_ int, s *goquery.Selection) {
called = true
href, _ := s.Attr("href")
assert.Contains(t, href, "?q=&")
assert.Contains(t, href, "&type=")
assert.Contains(t, href, "&sort=")
assert.Contains(t, href, "&state=")
assert.Contains(t, href, "&labels=")
assert.Contains(t, href, "&milestone=")
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=false")
})
assert.True(t, called)
})
@ -1195,7 +1168,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1220,7 +1192,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1245,7 +1216,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1270,7 +1240,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1295,7 +1264,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1320,7 +1288,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=1")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1345,7 +1312,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=1")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1370,7 +1336,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=1")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1395,7 +1360,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
@ -1420,7 +1384,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
assert.Contains(t, href, "&archived=true")
})
assert.True(t, called)