appview: add issue search endpoint #496

merged
opened by boltless.me targeting master from boltless.me/core: feat/search
Changed files
+93 -9
appview
db
issues
pages
templates
repo
issues
+56
appview/db/issues.go
··· 247 247 return GetIssuesPaginated(e, pagination.FirstPage(), filters...) 248 248 } 249 249 250 + // GetIssueIDs gets list of all existing issue's IDs 251 + func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) { 252 + var ids []int64 253 + 254 + var filters []filter 255 + openValue := 0 256 + if opts.IsOpen { 257 + openValue = 1 258 + } 259 + filters = append(filters, FilterEq("open", openValue)) 260 + if opts.RepoAt != "" { 261 + filters = append(filters, FilterEq("repo_at", opts.RepoAt)) 262 + } 263 + 264 + var conditions []string 265 + var args []any 266 + 267 + for _, filter := range filters { 268 + conditions = append(conditions, filter.Condition()) 269 + args = append(args, filter.Arg()...) 270 + } 271 + 272 + whereClause := "" 273 + if conditions != nil { 274 + whereClause = " where " + strings.Join(conditions, " and ") 275 + } 276 + query := fmt.Sprintf( 277 + ` 278 + select 279 + id 280 + from 281 + issues 282 + %s 283 + limit ? offset ?`, 284 + whereClause, 285 + ) 286 + args = append(args, opts.Page.Limit, opts.Page.Offset) 287 + rows, err := e.Query(query, args...) 288 + if err != nil { 289 + return nil, err 290 + } 291 + defer rows.Close() 292 + 293 + for rows.Next() { 294 + var id int64 295 + err := rows.Scan(&id) 296 + if err != nil { 297 + return nil, err 298 + } 299 + 300 + ids = append(ids, id) 301 + } 302 + 303 + return ids, nil 304 + } 305 + 250 306 func AddIssueComment(e Execer, c models.IssueComment) (int64, error) { 251 307 result, err := e.Exec( 252 308 `insert into issue_comments (
+28 -7
appview/issues/issues.go
··· 787 787 return 788 788 } 789 789 790 - openVal := 0 791 - if isOpen { 792 - openVal = 1 790 + keyword := params.Get("q") 791 + 792 + var ids []int64 793 + searchOpts := models.IssueSearchOptions{ 794 + Keyword: keyword, 795 + RepoAt: f.RepoAt().String(), 796 + IsOpen: isOpen, 797 + Page: page, 798 + } 799 + if keyword != "" { 800 + res, err := rp.indexer.Search(r.Context(), searchOpts) 801 + if err != nil { 802 + l.Error("failed to search for issues", "err", err) 803 + return 804 + } 805 + l.Debug("searched issues with indexer", "res.Hits", res.Hits) 806 + ids = res.Hits 807 + } else { 808 + ids, err = db.GetIssueIDs(rp.db, searchOpts) 809 + if err != nil { 810 + l.Error("failed to search for issues", "err", err) 811 + return 812 + } 813 + l.Debug("indexed all issues from the db", "ids", ids) 793 814 } 794 - issues, err := db.GetIssuesPaginated( 815 + 816 + issues, err := db.GetIssues( 795 817 rp.db, 796 - page, 797 - db.FilterEq("repo_at", f.RepoAt()), 798 - db.FilterEq("open", openVal), 818 + db.FilterIn("id", ids), 799 819 ) 800 820 if err != nil { 801 821 l.Error("failed to get issues", "err", err) ··· 825 845 Issues: issues, 826 846 LabelDefs: defs, 827 847 FilteringByOpen: isOpen, 848 + FilterQuery: keyword, 828 849 Page: page, 829 850 }) 830 851 }
+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" ··· 55 62 <a 56 63 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 57 64 hx-boost="true" 58 - href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 65 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&q={{ .FilterQuery }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 59 66 > 60 67 {{ i "chevron-left" "w-4 h-4" }} 61 68 previous ··· 69 76 <a 70 77 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 71 78 hx-boost="true" 72 - href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}" 79 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&q={{ .FilterQuery }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}" 73 80 > 74 81 next 75 82 {{ i "chevron-right" "w-4 h-4" }}