forked from tangled.org/core
Monorepo for Tangled

appview/{models,state}: implement good first issues handler

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

authored by anirudh.fi and committed by oppi.li 0ef988d4 b57a7325

Changed files
+157
appview
models
pages
state
+5
appview/models/repo.go
··· 86 86 RepoAt syntax.ATURI 87 87 LabelAt syntax.ATURI 88 88 } 89 + 90 + type RepoGroup struct { 91 + Repo *Repo 92 + Issues []Issue 93 + }
+12
appview/pages/pages.go
··· 312 312 return p.execute("timeline/timeline", w, params) 313 313 } 314 314 315 + type GoodFirstIssuesParams struct { 316 + LoggedInUser *oauth.User 317 + Issues []models.Issue 318 + RepoGroups []*models.RepoGroup 319 + LabelDefs map[string]*models.LabelDefinition 320 + Page pagination.Page 321 + } 322 + 323 + func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error { 324 + return p.execute("goodfirstissues/index", w, params) 325 + } 326 + 315 327 type UserProfileSettingsParams struct { 316 328 LoggedInUser *oauth.User 317 329 Tabs []map[string]any
+138
appview/state/gfi.go
··· 1 + package state 2 + 3 + import ( 4 + "fmt" 5 + "log" 6 + "net/http" 7 + "sort" 8 + 9 + "tangled.org/core/api/tangled" 10 + "tangled.org/core/appview/db" 11 + "tangled.org/core/appview/models" 12 + "tangled.org/core/appview/pages" 13 + "tangled.org/core/appview/pagination" 14 + "tangled.org/core/consts" 15 + ) 16 + 17 + func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) { 18 + user := s.oauth.GetUser(r) 19 + 20 + page, ok := r.Context().Value("page").(pagination.Page) 21 + if !ok { 22 + page = pagination.FirstPage() 23 + } 24 + 25 + goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue") 26 + 27 + repoLabels, err := db.GetRepoLabels(s.db, db.FilterEq("label_at", goodFirstIssueLabel)) 28 + if err != nil { 29 + log.Println("failed to get repo labels", err) 30 + s.pages.Error503(w) 31 + return 32 + } 33 + 34 + if len(repoLabels) == 0 { 35 + s.pages.GoodFirstIssues(w, pages.GoodFirstIssuesParams{ 36 + LoggedInUser: user, 37 + RepoGroups: []*models.RepoGroup{}, 38 + LabelDefs: make(map[string]*models.LabelDefinition), 39 + Page: page, 40 + }) 41 + return 42 + } 43 + 44 + repoUris := make([]string, 0, len(repoLabels)) 45 + for _, rl := range repoLabels { 46 + repoUris = append(repoUris, rl.RepoAt.String()) 47 + } 48 + 49 + allIssues, err := db.GetIssues( 50 + s.db, 51 + db.FilterIn("repo_at", repoUris), 52 + db.FilterEq("open", 1), 53 + ) 54 + if err != nil { 55 + log.Println("failed to get issues", err) 56 + s.pages.Error503(w) 57 + return 58 + } 59 + 60 + var goodFirstIssues []models.Issue 61 + for _, issue := range allIssues { 62 + if issue.Labels.ContainsLabel(goodFirstIssueLabel) { 63 + goodFirstIssues = append(goodFirstIssues, issue) 64 + } 65 + } 66 + 67 + repoGroups := make(map[string]*models.RepoGroup) 68 + for _, issue := range goodFirstIssues { 69 + repoKey := fmt.Sprintf("%s/%s", issue.Repo.Did, issue.Repo.Name) 70 + if group, exists := repoGroups[repoKey]; exists { 71 + group.Issues = append(group.Issues, issue) 72 + } else { 73 + repoGroups[repoKey] = &models.RepoGroup{ 74 + Repo: issue.Repo, 75 + Issues: []models.Issue{issue}, 76 + } 77 + } 78 + } 79 + 80 + var sortedGroups []*models.RepoGroup 81 + for _, group := range repoGroups { 82 + sortedGroups = append(sortedGroups, group) 83 + } 84 + 85 + sort.Slice(sortedGroups, func(i, j int) bool { 86 + return sortedGroups[i].Repo.Name < sortedGroups[j].Repo.Name 87 + }) 88 + 89 + groupStart := page.Offset 90 + groupEnd := page.Offset + page.Limit 91 + if groupStart > len(sortedGroups) { 92 + groupStart = len(sortedGroups) 93 + } 94 + if groupEnd > len(sortedGroups) { 95 + groupEnd = len(sortedGroups) 96 + } 97 + 98 + paginatedGroups := sortedGroups[groupStart:groupEnd] 99 + 100 + var allIssuesFromGroups []models.Issue 101 + for _, group := range paginatedGroups { 102 + allIssuesFromGroups = append(allIssuesFromGroups, group.Issues...) 103 + } 104 + 105 + var allLabelDefs []models.LabelDefinition 106 + if len(allIssuesFromGroups) > 0 { 107 + labelDefUris := make(map[string]bool) 108 + for _, issue := range allIssuesFromGroups { 109 + for labelDefUri := range issue.Labels.Inner() { 110 + labelDefUris[labelDefUri] = true 111 + } 112 + } 113 + 114 + uriList := make([]string, 0, len(labelDefUris)) 115 + for uri := range labelDefUris { 116 + uriList = append(uriList, uri) 117 + } 118 + 119 + if len(uriList) > 0 { 120 + allLabelDefs, err = db.GetLabelDefinitions(s.db, db.FilterIn("at_uri", uriList)) 121 + if err != nil { 122 + log.Println("failed to fetch labels", err) 123 + } 124 + } 125 + } 126 + 127 + labelDefsMap := make(map[string]*models.LabelDefinition) 128 + for i := range allLabelDefs { 129 + labelDefsMap[allLabelDefs[i].AtUri().String()] = &allLabelDefs[i] 130 + } 131 + 132 + s.pages.GoodFirstIssues(w, pages.GoodFirstIssuesParams{ 133 + LoggedInUser: user, 134 + RepoGroups: paginatedGroups, 135 + LabelDefs: labelDefsMap, 136 + Page: page, 137 + }) 138 + }
+2
appview/state/router.go
··· 132 132 // r.Post("/import", s.ImportRepo) 133 133 }) 134 134 135 + r.Get("/goodfirstissues", s.GoodFirstIssues) 136 + 135 137 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 136 138 r.Post("/", s.Follow) 137 139 r.Delete("/", s.Follow)