Commit 0f51fba689
Verified · cmc ci/build: success ci/sonar: success ci/test: success ci/vuln: success
Layout: unified · split
.gitbay/wiki/Parity.org +4 −2
| @@ -269,7 +269,8 @@ missing UI rather than a rule. | ||
| 269 | 269 | | teams create, delete | yes | yes | yes | |
| 270 | 270 | | team members | yes | yes | yes | |
| 271 | 271 | | team repo grants | yes | yes | yes | |
| 272 | | create, rename, delete | yes | no | no | | |
| 272 | | create, rename | yes | yes | no | | |
| 273 | | delete | yes | n/a | n/a | | |
| 273 | 274 | |
| 274 | 275 | * Pagination |
| 275 | 276 | |
| @@ -283,7 +284,8 @@ with the same cursors; iOS pages with them too. | ||
| 283 | 284 | |
| 284 | 285 | Build secrets, mirror configuration and tokens, custom domain claims, |
| 285 | 286 | API token minting, deploy keys, account and instance administration. Deleting or transferring a repository is also |
| 286 | CLI-only: both want a typed confirmation, not a button. | |
| 287 | CLI-only, as is deleting an organization: each wants a typed | |
| 288 | confirmation, not a button. | |
| 287 | 289 | |
| 288 | 290 | These are the only rows where a =no= is intended. Everywhere else a |
| 289 | 291 | =no= is work outstanding, and =n/a= means a surface cannot usefully |
e2e/orgweb_test.go +55
| @@ -130,3 +130,58 @@ func orgMembers(t *testing.T, inst *instance, key string) []string { | ||
| 130 | 130 | } |
| 131 | 131 | return names |
| 132 | 132 | } |
| 133 | ||
| 134 | // The organization lifecycle from a browser: create from your own page, | |
| 135 | // rename from the org's. Delete stays on the CLI, where a typed | |
| 136 | // confirmation is the norm (#167). | |
| 137 | func TestOrgLifecycleWeb(t *testing.T) { | |
| 138 | inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n") | |
| 139 | aliceKey := inst.newKey(t, "alice") | |
| 140 | bobKey := inst.newKey(t, "bob") | |
| 141 | inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub") | |
| 142 | inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub") | |
| 143 | alice := loginBrowser(t, inst, aliceKey) | |
| 144 | bob := loginBrowser(t, inst, bobKey) | |
| 145 | ||
| 146 | // The create form is on your own page and nobody else's. | |
| 147 | if _, body := browserGet(t, alice, inst.base()+"/alice"); !strings.Contains(body, `value="org-create"`) { | |
| 148 | t.Fatalf("no create form on your own page:\n%s", body) | |
| 149 | } | |
| 150 | if _, body := browserGet(t, bob, inst.base()+"/alice"); strings.Contains(body, `value="org-create"`) { | |
| 151 | t.Fatal("create form on someone else's page") | |
| 152 | } | |
| 153 | ||
| 154 | if status, _ := browserPost(t, alice, inst.base()+"/alice", url.Values{ | |
| 155 | "field": {"org-create"}, "name": {"acmeco"}}); status != 200 { | |
| 156 | t.Fatal("org create failed") | |
| 157 | } | |
| 158 | if out, _, _ := inst.ssh(t, aliceKey, "", "org", "list", "--json"); !strings.Contains(out, "acmeco") { | |
| 159 | t.Fatalf("org not created:\n%s", out) | |
| 160 | } | |
| 161 | ||
| 162 | // Rename is offered to its admin, and the org moves. | |
| 163 | _, body := browserGet(t, alice, inst.base()+"/acmeco") | |
| 164 | if !strings.Contains(body, `value="org-rename"`) { | |
| 165 | t.Fatalf("no rename form for the org admin:\n%s", body) | |
| 166 | } | |
| 167 | if !strings.Contains(body, "gitbay org delete") || strings.Contains(body, `value="org-delete"`) { | |
| 168 | t.Error("delete is not recorded as a CLI operation") | |
| 169 | } | |
| 170 | if status, _ := browserPost(t, alice, inst.base()+"/acmeco", url.Values{ | |
| 171 | "field": {"org-rename"}, "name": {"acmeltd"}}); status != 200 { | |
| 172 | t.Fatal("org rename failed") | |
| 173 | } | |
| 174 | if _, _, code := inst.ssh(t, aliceKey, "", "org", "show", "acmeltd"); code != 0 { | |
| 175 | t.Fatal("renamed org not found under its new name") | |
| 176 | } | |
| 177 | if status, _ := browserGet(t, alice, inst.base()+"/acmeco"); status != http.StatusNotFound { | |
| 178 | t.Errorf("old org name still resolves: %d", status) | |
| 179 | } | |
| 180 | ||
| 181 | // A non-admin cannot rename it, form or no form. | |
| 182 | browserPost(t, bob, inst.base()+"/acmeltd", url.Values{ | |
| 183 | "field": {"org-rename"}, "name": {"bobsltd"}}) | |
| 184 | if _, _, code := inst.ssh(t, aliceKey, "", "org", "show", "acmeltd"); code != 0 { | |
| 185 | t.Fatal("a non-admin renamed the organization") | |
| 186 | } | |
| 187 | } | |
internal/httpd/orgweb.go +17
| @@ -51,6 +51,23 @@ func (s *Server) orgSubmit(w http.ResponseWriter, r *http.Request, u store.User) | ||
| 51 | 51 | team := strings.TrimSpace(r.FormValue("team")) |
| 52 | 52 | user := strings.TrimSpace(r.FormValue("user")) |
| 53 | 53 | |
| 54 | // Create and rename land on a different page than the one they were | |
| 55 | // posted from: a new org has no page yet, and a renamed one has moved. | |
| 56 | switch field { | |
| 57 | case "org-create", "org-rename": | |
| 58 | name := strings.TrimSpace(r.FormValue("name")) | |
| 59 | argv := []string{"org", "create", name} | |
| 60 | if field == "org-rename" { | |
| 61 | argv = []string{"org", "rename", owner, name} | |
| 62 | } | |
| 63 | if _, msg, ok := s.runControl(u, argv); !ok { | |
| 64 | back(msg) | |
| 65 | return | |
| 66 | } | |
| 67 | http.Redirect(w, r, "/"+name, http.StatusSeeOther) | |
| 68 | return | |
| 69 | } | |
| 70 | ||
| 54 | 71 | var argv []string |
| 55 | 72 | switch field { |
| 56 | 73 | case "member-add": |
internal/httpd/web.go +4 −1
| @@ -440,10 +440,13 @@ func (s *Server) ownerPage(w http.ResponseWriter, r *http.Request) { | ||
| 440 | 440 | ActivityTotal int |
| 441 | 441 | Teams []teamView |
| 442 | 442 | CanAdmin bool |
| 443 | Self bool | |
| 443 | 444 | Notice string |
| 444 | 445 | }{s.baseFor(viewer), name, d.Kind, profile, aboutHTML(profile), |
| 445 | 446 | d.Repos, d.Members, d.Orgs, |
| 446 | weeks, activityTotal, teams, canAdmin, s.takeFlash(w, r)}) | |
| 447 | weeks, activityTotal, teams, canAdmin, | |
| 448 | d.Kind == "user" && viewer.ID != 0 && strings.EqualFold(viewer.Username, name), | |
| 449 | s.takeFlash(w, r)}) | |
| 447 | 450 | } |
| 448 | 451 | |
| 449 | 452 | func (s *Server) repoHome(w http.ResponseWriter, r *http.Request) { |
internal/web/templates/owner.html +29
| @@ -23,6 +23,20 @@ | ||
| 23 | 23 | {{else}}<li class="empty">no visible repositories</li>{{end}} |
| 24 | 24 | </ul> |
| 25 | 25 | |
| 26 | {{if .Self}} | |
| 27 | <h2>organizations</h2> | |
| 28 | {{if .Notice}}<p class="error" role="alert">{{.Notice}}</p>{{end}} | |
| 29 | <details class="editbox"> | |
| 30 | <summary>New organization</summary> | |
| 31 | <form method="post" action="/{{.Owner}}" class="setform"> | |
| 32 | <input type="hidden" name="field" value="org-create"> | |
| 33 | <label for="orgname">Name</label> | |
| 34 | <input type="text" id="orgname" name="name" required> | |
| 35 | <button type="submit">Create</button> | |
| 36 | </form> | |
| 37 | </details> | |
| 38 | {{end}} | |
| 39 | ||
| 26 | 40 | {{if .CanAdmin}}{{$org := .Owner}} |
| 27 | 41 | <h2>people <span class="count">{{len .Members}}</span></h2> |
| 28 | 42 | {{if .Notice}}<p class="error" role="alert">{{.Notice}}</p>{{end}} |
| @@ -96,5 +110,20 @@ of a team get its role on every repository it is granted.</p> | ||
| 96 | 110 | <button type="submit">Create</button> |
| 97 | 111 | </form> |
| 98 | 112 | </details> |
| 113 | ||
| 114 | <h2>organization</h2> | |
| 115 | <details class="editbox"> | |
| 116 | <summary>Rename</summary> | |
| 117 | <form method="post" action="/{{$org}}" class="setform"> | |
| 118 | <input type="hidden" name="field" value="org-rename"> | |
| 119 | <label for="orgrename">New name</label> | |
| 120 | <input type="text" id="orgrename" name="name" value="{{$org}}" required> | |
| 121 | <button type="submit">Rename</button> | |
| 122 | </form> | |
| 123 | <p class="meta">Every clone URL under this organization changes with the name.</p> | |
| 124 | </details> | |
| 125 | <p class="meta">Deleting an organization is a CLI operation, like deleting a | |
| 126 | repository: it wants a typed confirmation rather than a button.</p> | |
| 127 | <pre class="message">gitbay org delete {{$org}} --yes</pre> | |
| 99 | 128 | {{end}} |
| 100 | 129 | {{end}} |