Signed-off-by: Seongmin Lee boltlessengineer@proton.me
+84
-37
appview/db/issues.go
+84
-37
appview/db/issues.go
···
3
3
import (
4
4
"database/sql"
5
5
"fmt"
6
+
"strconv"
6
7
"strings"
7
8
"time"
8
9
9
10
"github.com/bluesky-social/indigo/atproto/syntax"
10
11
"tangled.sh/tangled.sh/core/api/tangled"
12
+
"tangled.sh/tangled.sh/core/appview/models"
11
13
"tangled.sh/tangled.sh/core/appview/pagination"
12
14
)
13
15
···
162
164
return issues, nil
163
165
}
164
166
165
-
func GetIssuesPaginated(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {
166
-
var issues []Issue
167
+
// GetIssueIDs gets list of all existing issue's IDs
168
+
func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) {
169
+
var ids []int64
170
+
171
+
var filters []filter
167
172
openValue := 0
168
-
if isOpen {
173
+
if opts.IsOpen {
169
174
openValue = 1
170
175
}
176
+
filters = append(filters, FilterEq("open", openValue))
177
+
if opts.RepoAt != "" {
178
+
filters = append(filters, FilterEq("repo_at", opts.RepoAt))
179
+
}
171
180
172
-
rows, err := e.Query(
181
+
var conditions []string
182
+
var args []any
183
+
184
+
for _, filter := range filters {
185
+
conditions = append(conditions, filter.Condition())
186
+
args = append(args, filter.Arg()...)
187
+
}
188
+
189
+
whereClause := ""
190
+
if conditions != nil {
191
+
whereClause = " where " + strings.Join(conditions, " and ")
192
+
}
193
+
query := fmt.Sprintf(
173
194
`
174
-
with numbered_issue as (
175
-
select
176
-
i.id,
177
-
i.owner_did,
178
-
i.rkey,
179
-
i.issue_id,
180
-
i.created,
181
-
i.title,
182
-
i.body,
183
-
i.open,
184
-
count(c.id) as comment_count,
185
-
row_number() over (order by i.created desc) as row_num
186
-
from
187
-
issues i
188
-
left join
189
-
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
190
-
where
191
-
i.repo_at = ? and i.open = ?
192
-
group by
193
-
i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open
194
-
)
195
195
select
196
-
id,
197
-
owner_did,
198
-
rkey,
199
-
issue_id,
200
-
created,
201
-
title,
202
-
body,
203
-
open,
204
-
comment_count
196
+
id
205
197
from
206
-
numbered_issue
198
+
issues
199
+
%s
200
+
limit ? offset ?`,
201
+
whereClause,
202
+
)
203
+
args = append(args, opts.Page.Limit, opts.Page.Offset)
204
+
rows, err := e.Query(query, args...)
205
+
if err != nil {
206
+
return nil, err
207
+
}
208
+
defer rows.Close()
209
+
210
+
for rows.Next() {
211
+
var id int64
212
+
err := rows.Scan(&id)
213
+
if err != nil {
214
+
return nil, err
215
+
}
216
+
217
+
ids = append(ids, id)
218
+
}
219
+
220
+
return ids, nil
221
+
}
222
+
223
+
// GetIssuesByIDs gets list of issues from given IDs
224
+
func GetIssuesByIDs(e Execer, ids []int64) ([]Issue, error) {
225
+
var issues []Issue
226
+
227
+
// HACK: would be better to create "?,?,?,..." or use something like sqlx
228
+
idStrings := make([]string, len(ids))
229
+
for i, id := range ids {
230
+
idStrings[i] = strconv.FormatInt(id, 10)
231
+
}
232
+
idList := strings.Join(idStrings, ",")
233
+
query := fmt.Sprintf(
234
+
`
235
+
select
236
+
i.id,
237
+
i.owner_did,
238
+
i.rkey,
239
+
i.issue_id,
240
+
i.created,
241
+
i.title,
242
+
i.body,
243
+
i.open,
244
+
count(c.id) as comment_count
245
+
from
246
+
issues i
247
+
left join
248
+
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
207
249
where
208
-
row_num between ? and ?`,
209
-
repoAt, openValue, page.Offset+1, page.Offset+page.Limit)
250
+
i.id in (%s)
251
+
group by
252
+
i.id
253
+
order by i.created desc`,
254
+
idList,
255
+
)
256
+
rows, err := e.Query(query)
210
257
if err != nil {
211
258
return nil, err
212
259
}
+28
-1
appview/issues/issues.go
+28
-1
appview/issues/issues.go
···
19
19
"tangled.sh/tangled.sh/core/appview/config"
20
20
"tangled.sh/tangled.sh/core/appview/db"
21
21
issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues"
22
+
"tangled.sh/tangled.sh/core/appview/models"
22
23
"tangled.sh/tangled.sh/core/appview/notify"
23
24
"tangled.sh/tangled.sh/core/appview/oauth"
24
25
"tangled.sh/tangled.sh/core/appview/pages"
···
608
609
return
609
610
}
610
611
611
-
issues, err := db.GetIssuesPaginated(rp.db, f.RepoAt(), isOpen, page)
612
+
keyword := params.Get("q")
613
+
614
+
var ids []int64
615
+
searchOpts := models.IssueSearchOptions{
616
+
Keyword: keyword,
617
+
RepoAt: f.RepoAt().String(),
618
+
IsOpen: isOpen,
619
+
Page: page,
620
+
}
621
+
if keyword != "" {
622
+
res, err := rp.indexer.Search(r.Context(), searchOpts)
623
+
if err != nil {
624
+
log.Println("failed to search for issues", err)
625
+
return
626
+
}
627
+
log.Println("searched issues:", res.Hits)
628
+
ids = res.Hits
629
+
} else {
630
+
ids, err = db.GetIssueIDs(rp.db, searchOpts)
631
+
if err != nil {
632
+
log.Println("failed to search for issues", err)
633
+
return
634
+
}
635
+
}
636
+
637
+
issues, err := db.GetIssuesByIDs(rp.db, ids)
612
638
if err != nil {
613
639
log.Println("failed to get issues", err)
614
640
rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
···
620
646
RepoInfo: f.RepoInfo(user),
621
647
Issues: issues,
622
648
FilteringByOpen: isOpen,
649
+
FilterQuery: keyword,
623
650
Page: page,
624
651
})
625
652
}
+9
-2
appview/pages/templates/repo/issues/issues.html
+9
-2
appview/pages/templates/repo/issues/issues.html
···
24
24
{{ i "ban" "w-4 h-4" }}
25
25
<span>{{ .RepoInfo.Stats.IssueCount.Closed }} closed</span>
26
26
</a>
27
+
<form class="flex gap-4" method="GET">
28
+
<input type="hidden" name="state" value="{{ if .FilteringByOpen }}open{{ else }}closed{{ end }}">
29
+
<input class="" type="text" name="q" value="{{ .FilterQuery }}">
30
+
<button class="btn" type="submit">
31
+
search
32
+
</button>
33
+
</form>
27
34
</div>
28
35
<a
29
36
href="/{{ .RepoInfo.FullName }}/issues/new"
···
100
107
<a
101
108
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
102
109
hx-boost="true"
103
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
110
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&q={{ .FilterQuery }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
104
111
>
105
112
{{ i "chevron-left" "w-4 h-4" }}
106
113
previous
···
114
121
<a
115
122
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
116
123
hx-boost="true"
117
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}"
124
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&q={{ .FilterQuery }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}"
118
125
>
119
126
next
120
127
{{ i "chevron-right" "w-4 h-4" }}