Monorepo for Tangled tangled.org

appview: add issue search endpoint

Signed-off-by: Seongmin Lee <boltlessengineer@proton.me>

authored by boltless.me and committed by Tangled 552fa4b9 fc9517ce

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