loading up the forgejo repo on tangled to test page performance

Merge pull request '[gitea] week 2024-42 cherry pick (gitea/main -> forgejo)' (#5543) from earl-warren/wcp/2024-42 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5543
Reviewed-by: Gusted <gusted@noreply.codeberg.org>

Changed files
+100 -64
models
issues
user
release-notes
routers
api
v1
repo
services
templates
swagger
tests
integration
web_src
js
components
features
+5
models/issues/pull_list.go
··· 26 26 SortType string 27 27 Labels []int64 28 28 MilestoneID int64 29 + PosterID int64 29 30 } 30 31 31 32 func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { ··· 44 45 45 46 if opts.MilestoneID > 0 { 46 47 sess.And("issue.milestone_id=?", opts.MilestoneID) 48 + } 49 + 50 + if opts.PosterID > 0 { 51 + sess.And("issue.poster_id=?", opts.PosterID) 47 52 } 48 53 49 54 return sess
+31 -29
models/user/user.go
··· 586 586 ".", 587 587 "..", 588 588 ".well-known", 589 - "admin", 590 - "api", 591 - "assets", 592 - "attachments", 593 - "avatar", 594 - "avatars", 589 + 590 + "api", // gitea api 591 + "metrics", // prometheus metrics api 592 + "v2", // container registry api 593 + 594 + "assets", // static asset files 595 + "attachments", // issue attachments 596 + 597 + "avatar", // avatar by email hash 598 + "avatars", // user avatars by file name 599 + "repo-avatars", 600 + 595 601 "captcha", 596 - "commits", 597 - "debug", 602 + "login", // oauth2 login 603 + "org", // org create/manage, or "/org/{org}", BUT if an org is named as "invite" then it goes wrong 604 + "repo", // repo create/migrate, etc 605 + "user", // user login/activate/settings, etc 606 + 607 + "admin", 598 608 "devtest", 599 - "error", 600 609 "explore", 601 - "favicon.ico", 602 - "ghost", 603 610 "issues", 604 - "login", 605 - "manifest.json", 606 - "metrics", 611 + "pulls", 607 612 "milestones", 608 - "new", 609 613 "notifications", 610 - "org", 611 - "pulls", 612 - "raw", 613 - "repo", 614 - "repo-avatars", 615 - "robots.txt", 616 - "search", 617 - "serviceworker.js", 618 - "ssh_info", 614 + 615 + "favicon.ico", 616 + "manifest.json", // web app manifests 617 + "robots.txt", // search engine robots 618 + "sitemap.xml", // search engine sitemap 619 + "ssh_info", // agit info 619 620 "swagger.v1.json", 620 - "user", 621 - "v2", 622 - "gitea-actions", 623 - "forgejo-actions", 621 + 622 + "ghost", // reserved name for deleted users (id: -1) 623 + "gitea-actions", // gitea builtin user (id: -2) 624 + "forgejo-actions", // forgejo builtin user (id: -2) 624 625 } 625 626 626 - // DON'T ADD ANY NEW STUFF, WE SOLVE THIS WITH `/user/{obj}` PATHS! 627 + // These names are reserved for user accounts: user's keys, user's rss feed, user's avatar, etc. 628 + // DO NOT add any new stuff! The paths with these names are processed by `/{username}` handler (UsernameSubRoute) manually. 627 629 reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"} 628 630 ) 629 631
+1
release-notes/5543.md
··· 1 + feat: [commit](https://codeberg.org/forgejo/forgejo/commit/d0af8fe4dc7b294fe5409b2271468494267d5a7d) Allow filtering pull requests by poster in the API.
+33 -9
routers/api/v1/repo/pull.go
··· 52 52 // parameters: 53 53 // - name: owner 54 54 // in: path 55 - // description: owner of the repo 55 + // description: Owner of the repo 56 56 // type: string 57 57 // required: true 58 58 // - name: repo 59 59 // in: path 60 - // description: name of the repo 60 + // description: Name of the repo 61 61 // type: string 62 62 // required: true 63 63 // - name: state 64 64 // in: query 65 - // description: "State of pull request: open or closed (optional)" 65 + // description: State of pull request 66 66 // type: string 67 - // enum: [closed, open, all] 67 + // enum: [open, closed, all] 68 + // default: open 68 69 // - name: sort 69 70 // in: query 70 - // description: "Type of sort" 71 + // description: Type of sort 71 72 // type: string 72 73 // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority] 73 74 // - name: milestone 74 75 // in: query 75 - // description: "ID of the milestone" 76 + // description: ID of the milestone 76 77 // type: integer 77 78 // format: int64 78 79 // - name: labels 79 80 // in: query 80 - // description: "Label IDs" 81 + // description: Label IDs 81 82 // type: array 82 83 // collectionFormat: multi 83 84 // items: 84 85 // type: integer 85 86 // format: int64 87 + // - name: poster 88 + // in: query 89 + // description: Filter by pull request author 90 + // type: string 86 91 // - name: page 87 92 // in: query 88 - // description: page number of results to return (1-based) 93 + // description: Page number of results to return (1-based) 89 94 // type: integer 95 + // minimum: 1 96 + // default: 1 90 97 // - name: limit 91 98 // in: query 92 - // description: page size of results 99 + // description: Page size of results 93 100 // type: integer 101 + // minimum: 0 94 102 // responses: 95 103 // "200": 96 104 // "$ref": "#/responses/PullRequestList" 97 105 // "404": 98 106 // "$ref": "#/responses/notFound" 107 + // "500": 108 + // "$ref": "#/responses/error" 99 109 100 110 labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels")) 101 111 if err != nil { 102 112 ctx.Error(http.StatusInternalServerError, "PullRequests", err) 103 113 return 104 114 } 115 + var posterID int64 116 + if posterStr := ctx.FormString("poster"); posterStr != "" { 117 + poster, err := user_model.GetUserByName(ctx, posterStr) 118 + if err != nil { 119 + if user_model.IsErrUserNotExist(err) { 120 + ctx.Error(http.StatusBadRequest, "Poster not found", err) 121 + } else { 122 + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) 123 + } 124 + return 125 + } 126 + posterID = poster.ID 127 + } 105 128 listOptions := utils.GetListOptions(ctx) 106 129 prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{ 107 130 ListOptions: listOptions, ··· 109 132 SortType: ctx.FormTrim("sort"), 110 133 Labels: labelIDs, 111 134 MilestoneID: ctx.FormInt64("milestone"), 135 + PosterID: posterID, 112 136 }) 113 137 if err != nil { 114 138 ctx.Error(http.StatusInternalServerError, "PullRequests", err)
+4 -4
services/user/user.go
··· 33 33 34 34 // RenameUser renames a user 35 35 func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { 36 + if newUserName == u.Name { 37 + return nil 38 + } 39 + 36 40 // Non-local users are not allowed to change their username. 37 41 if !u.IsOrganization() && !u.IsLocal() { 38 42 return user_model.ErrUserIsNotLocal{ 39 43 UID: u.ID, 40 44 Name: u.Name, 41 45 } 42 - } 43 - 44 - if newUserName == u.Name { 45 - return nil 46 46 } 47 47 48 48 if err := user_model.IsUsableUsername(newUserName); err != nil {
+3 -5
services/user/user_test.go
··· 114 114 }) 115 115 116 116 t.Run("Non usable username", func(t *testing.T) { 117 - usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"} 117 + usernames := []string{"--diff", ".well-known", "gitea-actions", "aaa.atom", "aa.png"} 118 118 for _, username := range usernames { 119 - t.Run(username, func(t *testing.T) { 120 - require.Error(t, user_model.IsUsableUsername(username)) 121 - require.Error(t, RenameUser(db.DefaultContext, user, username)) 122 - }) 119 + require.Error(t, user_model.IsUsableUsername(username), "non-usable username: %s", username) 120 + require.Error(t, RenameUser(db.DefaultContext, user, username), "non-usable username: %s", username) 123 121 } 124 122 }) 125 123
+19 -6
templates/swagger/v1_json.tmpl
··· 12295 12295 "parameters": [ 12296 12296 { 12297 12297 "type": "string", 12298 - "description": "owner of the repo", 12298 + "description": "Owner of the repo", 12299 12299 "name": "owner", 12300 12300 "in": "path", 12301 12301 "required": true 12302 12302 }, 12303 12303 { 12304 12304 "type": "string", 12305 - "description": "name of the repo", 12305 + "description": "Name of the repo", 12306 12306 "name": "repo", 12307 12307 "in": "path", 12308 12308 "required": true 12309 12309 }, 12310 12310 { 12311 12311 "enum": [ 12312 + "open", 12312 12313 "closed", 12313 - "open", 12314 12314 "all" 12315 12315 ], 12316 12316 "type": "string", 12317 - "description": "State of pull request: open or closed (optional)", 12317 + "default": "open", 12318 + "description": "State of pull request", 12318 12319 "name": "state", 12319 12320 "in": "query" 12320 12321 }, ··· 12351 12352 "in": "query" 12352 12353 }, 12353 12354 { 12355 + "type": "string", 12356 + "description": "Filter by pull request author", 12357 + "name": "poster", 12358 + "in": "query" 12359 + }, 12360 + { 12361 + "minimum": 1, 12354 12362 "type": "integer", 12355 - "description": "page number of results to return (1-based)", 12363 + "default": 1, 12364 + "description": "Page number of results to return (1-based)", 12356 12365 "name": "page", 12357 12366 "in": "query" 12358 12367 }, 12359 12368 { 12369 + "minimum": 0, 12360 12370 "type": "integer", 12361 - "description": "page size of results", 12371 + "description": "Page size of results", 12362 12372 "name": "limit", 12363 12373 "in": "query" 12364 12374 } ··· 12369 12379 }, 12370 12380 "404": { 12371 12381 "$ref": "#/responses/notFound" 12382 + }, 12383 + "500": { 12384 + "$ref": "#/responses/error" 12372 12385 } 12373 12386 } 12374 12387 },
-7
tests/integration/user_test.go
··· 114 114 "avatar", 115 115 "avatars", 116 116 "captcha", 117 - "commits", 118 - "debug", 119 117 "devtest", 120 - "error", 121 118 "explore", 122 119 "favicon.ico", 123 120 "ghost", ··· 126 123 "manifest.json", 127 124 "metrics", 128 125 "milestones", 129 - "new", 130 126 "notifications", 131 127 "org", 132 128 "pulls", 133 - "raw", 134 129 "repo", 135 130 "repo-avatars", 136 131 "robots.txt", 137 - "search", 138 - "serviceworker.js", 139 132 "ssh_info", 140 133 "swagger.v1.json", 141 134 "user",
+3 -3
web_src/js/components/DashboardRepoList.vue
··· 358 358 <div class="menu"> 359 359 <a class="item" @click="toggleArchivedFilter()"> 360 360 <div class="ui checkbox" ref="checkboxArchivedFilter" :title="checkboxArchivedFilterTitle"> 361 - <!--the "hidden" is necessary to make the checkbox work without Fomantic UI js, 361 + <!--the "tw-pointer-events-none" is necessary to prevent the checkbox from handling user's input, 362 362 otherwise if the "input" handles click event for intermediate status, it breaks the internal state--> 363 - <input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps"> 363 + <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxArchivedFilterProps"> 364 364 <label> 365 365 <svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/> 366 366 {{ textShowArchived }} ··· 369 369 </a> 370 370 <a class="item" @click="togglePrivateFilter()"> 371 371 <div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle"> 372 - <input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps"> 372 + <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxPrivateFilterProps"> 373 373 <label> 374 374 <svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/> 375 375 {{ textShowPrivate }}
+1 -1
web_src/js/features/repo-home.js
··· 60 60 // how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save" 61 61 const responseData = await response.json(); 62 62 lastErrorToast = showErrorToast(responseData.message, {duration: 5000}); 63 - if (responseData.invalidTopics.length > 0) { 63 + if (responseData.invalidTopics && responseData.invalidTopics.length > 0) { 64 64 const {invalidTopics} = responseData; 65 65 const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label'); 66 66 for (const [index, value] of topics.split(',').entries()) {