Monorepo for Tangled
0
fork

Configure Feed

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

at fix/knot-version-string 193 lines 4.3 kB view raw
1package state 2 3import ( 4 "net/http" 5 "strings" 6 "time" 7 8 "github.com/posthog/posthog-go" 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/models" 11 "tangled.org/core/appview/pages" 12 "tangled.org/core/appview/pagination" 13 "tangled.org/core/appview/searchquery" 14 "tangled.org/core/orm" 15) 16 17func (s *State) Search(w http.ResponseWriter, r *http.Request) { 18 l := s.logger.With("handler", "Search") 19 20 params := r.URL.Query() 21 page := pagination.FromContext(r.Context()) 22 23 query := searchquery.Parse(params.Get("q")) 24 25 sortParam := params.Get("sort") 26 sortField, sortDesc := parseSortParam(sortParam) 27 28 var language string 29 if lang := query.Get("language"); lang != nil { 30 language = *lang 31 } 32 33 tf := searchquery.ExtractTextFilters(query) 34 35 searchOpts := models.RepoSearchOptions{ 36 Keywords: tf.Keywords, 37 Phrases: tf.Phrases, 38 NegatedKeywords: tf.NegatedKeywords, 39 NegatedPhrases: tf.NegatedPhrases, 40 Language: language, 41 SortField: sortField, 42 SortDesc: sortDesc, 43 Page: page, 44 } 45 46 var repos []models.Repo 47 var err error 48 var resultCount int 49 var searchDuration time.Duration 50 var docCount int64 51 method := "bleve" 52 53 if searchOpts.HasSearchFilters() || sortParam != "" { 54 res, err := s.indexer.Repos.Search(r.Context(), searchOpts) 55 if err != nil { 56 l.Error("failed to search repos", "err", err) 57 s.pages.Error500(w) 58 return 59 } 60 61 searchDuration = res.Duration 62 63 if len(res.Hits) > 0 { 64 repos, err = db.GetRepos(s.db, orm.FilterIn("id", res.Hits)) 65 if err != nil { 66 l.Error("failed to get repos by IDs", "err", err) 67 s.pages.Error500(w) 68 return 69 } 70 71 // sort repos to match search result order (by relevance) 72 repoMap := make(map[int64]models.Repo, len(repos)) 73 for _, repo := range repos { 74 repoMap[repo.Id] = repo 75 } 76 repos = make([]models.Repo, 0, len(res.Hits)) 77 for _, id := range res.Hits { 78 if repo, ok := repoMap[id]; ok { 79 repos = append(repos, repo) 80 } 81 } 82 } 83 resultCount = int(res.Total) 84 85 dc, err := (s.indexer.Repos.TotalDocCount()) 86 if err != nil { 87 l.Error("failed to get total doc count", "err", err) 88 } 89 docCount = int64(dc) 90 91 } else { 92 method = "db" 93 repos, err = db.GetReposPaginated( 94 s.db, 95 page, 96 ) 97 if err != nil { 98 l.Error("failed to get repos", "err", err) 99 s.pages.Error500(w) 100 return 101 } 102 103 rc, err := db.CountRepos( 104 s.db, 105 ) 106 if err != nil { 107 l.Error("failed to count repos", "err", err) 108 s.pages.Error500(w) 109 return 110 } 111 112 resultCount = int(rc) 113 docCount = int64(rc) 114 } 115 116 l.Info( 117 "RepoSearch", 118 "method", method, 119 "resultCount", resultCount, 120 "docCount", docCount, 121 "time", searchDuration, 122 "filterQuery", query.String(), 123 "sortParam", sortParam, 124 ) 125 126 if !s.config.Core.Dev && query.String() != "" { 127 distinctId := s.oauth.GetDid(r) 128 if distinctId == "" { 129 distinctId = "anonymous" 130 } 131 go func() { 132 if err := s.posthog.Enqueue(posthog.Capture{ 133 DistinctId: distinctId, 134 Event: "search", 135 Properties: posthog.Properties{ 136 "query": query.String(), 137 "result_count": resultCount, 138 "method": method, 139 }, 140 }); err != nil { 141 l.Error("failed to enqueue posthog event", "err", err) 142 } 143 }() 144 } 145 146 err = s.pages.SearchRepos(w, pages.SearchReposParams{ 147 LoggedInUser: s.oauth.GetMultiAccountUser(r), 148 Repos: repos, 149 Page: page, 150 FilterQuery: query.String(), 151 SortParam: sortParam, 152 TimeTaken: searchDuration, 153 ResultCount: resultCount, 154 DocCount: docCount, 155 }) 156 if err != nil { 157 l.Error("failed to render page", "err", err) 158 } 159} 160 161// parseSortParam parses sort parameter like "stars-desc" or "created-asc" 162func parseSortParam(sortParam string) (string, bool) { 163 defaultSort := func() (string, bool) { return "relevance", true } 164 165 // no sort param supplied, just go default 166 if sortParam == "" { 167 return defaultSort() 168 } 169 170 parts := strings.Split(sortParam, "-") 171 if len(parts) != 2 { 172 return defaultSort() 173 } 174 175 field := parts[0] 176 desc := parts[1] == "desc" 177 178 // validate field 179 validFields := map[string]bool{ 180 "relevance": true, 181 "created": true, 182 "stars": true, 183 "issues": true, 184 "pulls": true, 185 } 186 187 // invalid fields, just go default 188 if !validFields[field] { 189 return defaultSort() 190 } 191 192 return field, desc 193}