forked from tangled.org/core
Monorepo for Tangled

appview/pulls: add search endpoint

Signed-off-by: Seongmin Lee <git@boltless.me>

authored by boltless.me and committed by Tangled 78198773 e3b54c9f

Changed files
+105 -2
appview
db
pages
templates
repo
pulls
pulls
state
+61
appview/db/pulls.go
··· 281 return GetPullsWithLimit(e, 0, filters...) 282 } 283 284 func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 285 pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId)) 286 if err != nil {
··· 281 return GetPullsWithLimit(e, 0, filters...) 282 } 283 284 + func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) { 285 + var ids []int64 286 + 287 + var filters []filter 288 + filters = append(filters, FilterEq("state", opts.State)) 289 + if opts.RepoAt != "" { 290 + filters = append(filters, FilterEq("repo_at", opts.RepoAt)) 291 + } 292 + 293 + var conditions []string 294 + var args []any 295 + 296 + for _, filter := range filters { 297 + conditions = append(conditions, filter.Condition()) 298 + args = append(args, filter.Arg()...) 299 + } 300 + 301 + whereClause := "" 302 + if conditions != nil { 303 + whereClause = " where " + strings.Join(conditions, " and ") 304 + } 305 + pageClause := "" 306 + if opts.Page.Limit != 0 { 307 + pageClause = fmt.Sprintf( 308 + " limit %d offset %d ", 309 + opts.Page.Limit, 310 + opts.Page.Offset, 311 + ) 312 + } 313 + 314 + query := fmt.Sprintf( 315 + ` 316 + select 317 + id 318 + from 319 + pulls 320 + %s 321 + %s`, 322 + whereClause, 323 + pageClause, 324 + ) 325 + args = append(args, opts.Page.Limit, opts.Page.Offset) 326 + rows, err := e.Query(query, args...) 327 + if err != nil { 328 + return nil, err 329 + } 330 + defer rows.Close() 331 + 332 + for rows.Next() { 333 + var id int64 334 + err := rows.Scan(&id) 335 + if err != nil { 336 + return nil, err 337 + } 338 + 339 + ids = append(ids, id) 340 + } 341 + 342 + return ids, nil 343 + } 344 + 345 func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 346 pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId)) 347 if err != nil {
+1
appview/pages/pages.go
··· 1102 Pulls []*models.Pull 1103 Active string 1104 FilteringBy models.PullState 1105 Stacks map[string]models.Stack 1106 Pipelines map[string]models.Pipeline 1107 LabelDefs map[string]*models.LabelDefinition
··· 1102 Pulls []*models.Pull 1103 Active string 1104 FilteringBy models.PullState 1105 + FilterQuery string 1106 Stacks map[string]models.Stack 1107 Pipelines map[string]models.Pipeline 1108 LabelDefs map[string]*models.LabelDefinition
+7
appview/pages/templates/repo/pulls/pulls.html
··· 31 {{ i "ban" "w-4 h-4" }} 32 <span>{{ .RepoInfo.Stats.PullCount.Closed }} closed</span> 33 </a> 34 </div> 35 <a 36 href="/{{ .RepoInfo.FullName }}/pulls/new"
··· 31 {{ i "ban" "w-4 h-4" }} 32 <span>{{ .RepoInfo.Stats.PullCount.Closed }} closed</span> 33 </a> 34 + <form class="flex gap-4" method="GET"> 35 + <input type="hidden" name="state" value="{{ .FilteringBy.String }}"> 36 + <input class="" type="text" name="q" value="{{ .FilterQuery }}"> 37 + <button class="btn" type="submit"> 38 + search 39 + </button> 40 + </form> 41 </div> 42 <a 43 href="/{{ .RepoInfo.FullName }}/pulls/new"
+35 -2
appview/pulls/pulls.go
··· 17 "tangled.org/core/api/tangled" 18 "tangled.org/core/appview/config" 19 "tangled.org/core/appview/db" 20 "tangled.org/core/appview/models" 21 "tangled.org/core/appview/notify" 22 "tangled.org/core/appview/oauth" ··· 49 enforcer *rbac.Enforcer 50 logger *slog.Logger 51 validator *validator.Validator 52 } 53 54 func New( ··· 61 notifier notify.Notifier, 62 enforcer *rbac.Enforcer, 63 validator *validator.Validator, 64 logger *slog.Logger, 65 ) *Pulls { 66 return &Pulls{ ··· 74 enforcer: enforcer, 75 logger: logger, 76 validator: validator, 77 } 78 } 79 ··· 544 } 545 546 func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) { 547 user := s.oauth.GetUser(r) 548 params := r.URL.Query() 549 ··· 561 return 562 } 563 564 pulls, err := db.GetPulls( 565 s.db, 566 - db.FilterEq("repo_at", f.RepoAt()), 567 - db.FilterEq("state", state), 568 ) 569 if err != nil { 570 log.Println("failed to get pulls", err) ··· 651 Pulls: pulls, 652 LabelDefs: defs, 653 FilteringBy: state, 654 Stacks: stacks, 655 Pipelines: m, 656 })
··· 17 "tangled.org/core/api/tangled" 18 "tangled.org/core/appview/config" 19 "tangled.org/core/appview/db" 20 + pulls_indexer "tangled.org/core/appview/indexer/pulls" 21 "tangled.org/core/appview/models" 22 "tangled.org/core/appview/notify" 23 "tangled.org/core/appview/oauth" ··· 50 enforcer *rbac.Enforcer 51 logger *slog.Logger 52 validator *validator.Validator 53 + indexer *pulls_indexer.Indexer 54 } 55 56 func New( ··· 63 notifier notify.Notifier, 64 enforcer *rbac.Enforcer, 65 validator *validator.Validator, 66 + indexer *pulls_indexer.Indexer, 67 logger *slog.Logger, 68 ) *Pulls { 69 return &Pulls{ ··· 77 enforcer: enforcer, 78 logger: logger, 79 validator: validator, 80 + indexer: indexer, 81 } 82 } 83 ··· 548 } 549 550 func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) { 551 + l := s.logger.With("handler", "RepoPulls") 552 + 553 user := s.oauth.GetUser(r) 554 params := r.URL.Query() 555 ··· 567 return 568 } 569 570 + keyword := params.Get("q") 571 + 572 + var ids []int64 573 + searchOpts := models.PullSearchOptions{ 574 + Keyword: keyword, 575 + RepoAt: f.RepoAt().String(), 576 + State: state, 577 + // Page: page, 578 + } 579 + l.Debug("searching with", "searchOpts", searchOpts) 580 + if keyword != "" { 581 + res, err := s.indexer.Search(r.Context(), searchOpts) 582 + if err != nil { 583 + l.Error("failed to search for pulls", "err", err) 584 + return 585 + } 586 + ids = res.Hits 587 + l.Debug("searched pulls with indexer", "count", len(ids)) 588 + } else { 589 + ids, err = db.GetPullIDs(s.db, searchOpts) 590 + if err != nil { 591 + l.Error("failed to get all pull ids", "err", err) 592 + return 593 + } 594 + l.Debug("indexed all pulls from the db", "count", len(ids)) 595 + } 596 + 597 pulls, err := db.GetPulls( 598 s.db, 599 + db.FilterIn("id", ids), 600 ) 601 if err != nil { 602 log.Println("failed to get pulls", err) ··· 683 Pulls: pulls, 684 LabelDefs: defs, 685 FilteringBy: state, 686 + FilterQuery: keyword, 687 Stacks: stacks, 688 Pipelines: m, 689 })
+1
appview/state/router.go
··· 279 s.notifier, 280 s.enforcer, 281 s.validator, 282 log.SubLogger(s.logger, "pulls"), 283 ) 284 return pulls.Router(mw)
··· 279 s.notifier, 280 s.enforcer, 281 s.validator, 282 + s.indexer.Pulls, 283 log.SubLogger(s.logger, "pulls"), 284 ) 285 return pulls.Router(mw)