This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / pulls / list.go
6.7 kB 246 lines
1package pulls 2 3import ( 4 "context" 5 "net/http" 6 7 "tangled.org/core/api/tangled" 8 "tangled.org/core/appview/db" 9 "tangled.org/core/appview/models" 10 "tangled.org/core/appview/pages" 11 "tangled.org/core/appview/pagination" 12 "tangled.org/core/appview/searchquery" 13 "tangled.org/core/orm" 14 15 "github.com/bluesky-social/indigo/atproto/syntax" 16) 17 18func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) { 19 l := s.logger.With("handler", "RepoPulls") 20 21 user := s.oauth.GetMultiAccountUser(r) 22 if user != nil { 23 l = l.With("user", user.Did) 24 } 25 26 params := r.URL.Query() 27 page := pagination.FromContext(r.Context()) 28 29 f, err := s.repoResolver.Resolve(r) 30 if err != nil { 31 l.Error("failed to get repo and knot", "err", err) 32 return 33 } 34 l = l.With("repo_at", f.RepoAt().String()) 35 36 query := searchquery.Parse(params.Get("q")) 37 38 var state *models.PullState 39 if urlState := params.Get("state"); urlState != "" { 40 switch urlState { 41 case "open": 42 state = ptrPullState(models.PullOpen) 43 case "closed": 44 state = ptrPullState(models.PullClosed) 45 case "merged": 46 state = ptrPullState(models.PullMerged) 47 } 48 query.Set("state", urlState) 49 } else if queryState := query.Get("state"); queryState != nil { 50 switch *queryState { 51 case "open": 52 state = ptrPullState(models.PullOpen) 53 case "closed": 54 state = ptrPullState(models.PullClosed) 55 case "merged": 56 state = ptrPullState(models.PullMerged) 57 } 58 } else if _, hasQ := params["q"]; !hasQ { 59 state = ptrPullState(models.PullOpen) 60 query.Set("state", "open") 61 } 62 63 resolve := func(ctx context.Context, ident string) (string, error) { 64 id, err := s.idResolver.ResolveIdent(ctx, ident) 65 if err != nil { 66 return "", err 67 } 68 return id.DID.String(), nil 69 } 70 71 authorDid, negatedAuthorDids := searchquery.ResolveAuthor(r.Context(), query, resolve, l) 72 73 labels := query.GetAll("label") 74 negatedLabels := query.GetAllNegated("label") 75 labelValues := query.GetDynamicTags() 76 negatedLabelValues := query.GetNegatedDynamicTags() 77 78 // resolve DID-format label values: if a dynamic tag's label 79 // definition has format "did", resolve the handle to a DID 80 if len(labelValues) > 0 || len(negatedLabelValues) > 0 { 81 labelDefs, err := db.GetLabelDefinitions( 82 s.db, 83 orm.FilterIn("at_uri", f.Labels), 84 orm.FilterContains("scope", tangled.RepoPullNSID), 85 ) 86 if err == nil { 87 didLabels := make(map[string]bool) 88 for _, def := range labelDefs { 89 if def.ValueType.Format == models.ValueTypeFormatDid { 90 didLabels[def.Name] = true 91 } 92 } 93 labelValues = searchquery.ResolveDIDLabelValues(r.Context(), labelValues, didLabels, resolve, l) 94 negatedLabelValues = searchquery.ResolveDIDLabelValues(r.Context(), negatedLabelValues, didLabels, resolve, l) 95 } else { 96 l.Debug("failed to fetch label definitions for DID resolution", "err", err) 97 } 98 } 99 100 tf := searchquery.ExtractTextFilters(query) 101 102 searchOpts := models.PullSearchOptions{ 103 Keywords: tf.Keywords, 104 Phrases: tf.Phrases, 105 RepoDid: f.RepoDid, 106 State: state, 107 AuthorDid: authorDid, 108 Labels: labels, 109 LabelValues: labelValues, 110 NegatedKeywords: tf.NegatedKeywords, 111 NegatedPhrases: tf.NegatedPhrases, 112 NegatedLabels: negatedLabels, 113 NegatedLabelValues: negatedLabelValues, 114 NegatedAuthorDids: negatedAuthorDids, 115 Page: page, 116 } 117 118 var totalPulls int 119 if state == nil { 120 totalPulls = f.RepoStats.PullCount.Open + f.RepoStats.PullCount.Merged + f.RepoStats.PullCount.Closed 121 } else { 122 switch *state { 123 case models.PullOpen: 124 totalPulls = f.RepoStats.PullCount.Open 125 case models.PullMerged: 126 totalPulls = f.RepoStats.PullCount.Merged 127 case models.PullClosed: 128 totalPulls = f.RepoStats.PullCount.Closed 129 } 130 } 131 132 repoInfo := s.repoResolver.GetRepoInfo(r, user) 133 134 ctx := r.Context() 135 136 var pulls []*models.Pull 137 138 if searchOpts.HasSearchFilters() { 139 res, err := s.indexer.Search(ctx, searchOpts) 140 if err != nil { 141 l.Error("failed to search for pulls", "err", err) 142 return 143 } 144 totalPulls = int(res.Total) 145 l.Debug("searched pulls with indexer", "count", len(res.Hits)) 146 147 // update tab counts to reflect filtered results 148 countOpts := searchOpts 149 countOpts.Page = pagination.Page{Limit: 1} 150 for _, ps := range []models.PullState{models.PullOpen, models.PullMerged, models.PullClosed} { 151 countOpts.State = &ps 152 countRes, err := s.indexer.Search(ctx, countOpts) 153 if err != nil { 154 continue 155 } 156 switch ps { 157 case models.PullOpen: 158 repoInfo.Stats.PullCount.Open = int(countRes.Total) 159 case models.PullMerged: 160 repoInfo.Stats.PullCount.Merged = int(countRes.Total) 161 case models.PullClosed: 162 repoInfo.Stats.PullCount.Closed = int(countRes.Total) 163 } 164 } 165 166 if len(res.Hits) > 0 { 167 pulls, err = db.GetPullsPaginated( 168 ctx, 169 s.db, 170 pagination.Page{Limit: searchOpts.Page.Limit}, 171 orm.FilterIn("id", res.Hits), 172 ) 173 if err != nil { 174 l.Error("failed to get pulls", "err", err) 175 s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.") 176 return 177 } 178 } 179 } else { 180 filters := []orm.Filter{ 181 orm.FilterEq("repo_did", f.RepoDid), 182 } 183 if state != nil { 184 filters = append(filters, orm.FilterEq("state", *state)) 185 } 186 pulls, err = db.GetPullsPaginated( 187 ctx, 188 s.db, 189 page, 190 filters..., 191 ) 192 if err != nil { 193 l.Error("failed to get pulls", "err", err) 194 s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.") 195 return 196 } 197 } 198 199 labelDefs, err := db.GetLabelDefinitions( 200 s.db, 201 orm.FilterIn("at_uri", f.Labels), 202 orm.FilterContains("scope", tangled.RepoPullNSID), 203 ) 204 if err != nil { 205 l.Error("failed to fetch labels", "err", err) 206 s.pages.Error503(w) 207 return 208 } 209 210 defs := make(map[string]*models.LabelDefinition) 211 for _, l := range labelDefs { 212 defs[l.AtUri().String()] = &l 213 } 214 215 filterState := "" 216 if state != nil { 217 filterState = state.String() 218 } 219 220 vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 221 if user != nil { 222 dids := make([]syntax.DID, len(pulls)) 223 for i, p := range pulls { 224 dids[i] = syntax.DID(p.OwnerDid) 225 } 226 vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), dids) 227 if err != nil { 228 l.Error("failed to fetch vouch relationships", "err", err) 229 } 230 } 231 232 err = s.pages.RepoPulls(w, pages.RepoPullsParams{ 233 BaseParams: pages.BaseParamsFromContext(r.Context()), 234 RepoInfo: repoInfo, 235 Pulls: pulls, 236 LabelDefs: defs, 237 FilterState: filterState, 238 FilterQuery: query.String(), 239 Page: page, 240 PullCount: totalPulls, 241 VouchRelationships: vouchRelationships, 242 }) 243 if err != nil { 244 l.Error("failed to render page", "err", err) 245 } 246}