web: delete a release from the browser !275
4 files changed, +38 −5
Layout: unified · split
.gitbay/wiki/Parity.org +1 −1
| @@ -147,7 +147,7 @@ format column and are always markdown. | |||
| 147 | | dependency checks on/off | yes | yes | no | | 147 | | dependency checks on/off | yes | yes | no | |
| 148 | | dependency status | yes | no | no | | 148 | | dependency status | yes | no | no | |
| 149 | | delete, transfer | yes | no | no | | 149 | | delete, transfer | yes | no | no | |
| 150 | | release delete | yes | no | no | | 150 | | release delete | yes | yes | no | |
| 151 | 151 | ||
| 152 | No row is web-only any more. Blame, file editing, log at a ref, | 152 | No row is web-only any more. Blame, file editing, log at a ref, |
| 153 | archive, the public listing and the wiki were all in that state — a | 153 | archive, the public listing and the wiki were all in that state — a |
e2e/releaseweb_test.go +20 −2
| @@ -88,7 +88,25 @@ func TestReleaseAndBuildWeb(t *testing.T) { | |||
| 88 | if _, p := browserGet(t, bob, base+"/builds"); strings.Contains(p, "Run a job now") { | 88 | if _, p := browserGet(t, bob, base+"/builds"); strings.Contains(p, "Run a job now") { |
| 89 | t.Fatal("reader sees the trigger control") | 89 | t.Fatal("reader sees the trigger control") |
| 90 | } | 90 | } |
| 91 | if _, p := browserGet(t, bob, base+"/releases"); strings.Contains(p, "New release") { | 91 | if _, p := browserGet(t, bob, base+"/releases"); strings.Contains(p, "New release") || |
| 92 | t.Fatal("reader sees the release form") | 92 | strings.Contains(p, "Delete release") { |
| 93 | t.Fatal("reader sees a release control") | ||
| 94 | } | ||
| 95 | |||
| 96 | // Delete is offered to a repo admin and removes the release; the tag | ||
| 97 | // survives, so it is offered for creation again (#165). | ||
| 98 | if _, p := browserGet(t, alice, base+"/releases"); !strings.Contains(p, "Delete release") { | ||
| 99 | t.Fatalf("owner is not offered the delete control:\n%s", p) | ||
| 100 | } | ||
| 101 | if status, _ := browserPost(t, alice, base+"/releases", url.Values{ | ||
| 102 | "action": {"delete"}, "tag": {"v1.0"}}); status != 200 { | ||
| 103 | t.Fatal("release delete failed") | ||
| 104 | } | ||
| 105 | out, _, _ = inst.ssh(t, aliceKey, "", "release", "list", "alice/app", "--json") | ||
| 106 | if strings.Contains(out, "v1.0") { | ||
| 107 | t.Fatalf("release still listed after delete:\n%s", out) | ||
| 108 | } | ||
| 109 | if _, p := browserGet(t, alice, base+"/releases"); !strings.Contains(p, `<option value="v1.0"`) { | ||
| 110 | t.Fatalf("tag not free again after delete:\n%s", p) | ||
| 93 | } | 111 | } |
| 94 | } | 112 | } |
internal/httpd/releaseactions.go +10 −1
| @@ -27,6 +27,15 @@ func (s *Server) releaseSubmit(w http.ResponseWriter, r *http.Request, u store.U | |||
| 27 | s.backTo(w, r, "releases", "pick a tag") | 27 | s.backTo(w, r, "releases", "pick a tag") |
| 28 | return | 28 | return |
| 29 | } | 29 | } |
| 30 | back := func(w http.ResponseWriter, r *http.Request, msg string) { s.backTo(w, r, "releases", msg) } | ||
| 31 | // The CLI's --yes guards against a mistyped tag; here the tag comes from | ||
| 32 | // the page and the button sits behind a disclosure, so the click is the | ||
| 33 | // deliberate act. | ||
| 34 | if r.FormValue("action") == "delete" { | ||
| 35 | _, msg, code := s.runControlCode(u, []string{"release", "delete", repo, tag, "--yes"}) | ||
| 36 | s.done(w, r, code, msg, back) | ||
| 37 | return | ||
| 38 | } | ||
| 30 | verb := "create" | 39 | verb := "create" |
| 31 | if r.FormValue("action") == "edit" { | 40 | if r.FormValue("action") == "edit" { |
| 32 | verb = "edit" | 41 | verb = "edit" |
| @@ -40,7 +49,7 @@ func (s *Server) releaseSubmit(w http.ResponseWriter, r *http.Request, u store.U | |||
| 40 | argv = append(argv, "--notes", notes) | 49 | argv = append(argv, "--notes", notes) |
| 41 | } | 50 | } |
| 42 | _, msg, code := s.runControlCode(u, argv) | 51 | _, msg, code := s.runControlCode(u, argv) |
| 43 | s.done(w, r, code, msg, func(w http.ResponseWriter, r *http.Request, msg string) { s.backTo(w, r, "releases", msg) }) | 52 | s.done(w, r, code, msg, back) |
| 44 | } | 53 | } |
| 45 | 54 | ||
| 46 | func (s *Server) buildTriggerSubmit(w http.ResponseWriter, r *http.Request, u store.User) { | 55 | func (s *Server) buildTriggerSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
internal/web/templates/releases.html +7 −1
| @@ -31,7 +31,13 @@ | |||
| 31 | <p><input type="text" name="title" aria-label="Title" value="{{$rel.Title}}"></p> | 31 | <p><input type="text" name="title" aria-label="Title" value="{{$rel.Title}}"></p> |
| 32 | <p><textarea name="notes" aria-label="Notes" rows="6">{{$rel.Notes}}</textarea></p> | 32 | <p><textarea name="notes" aria-label="Notes" rows="6">{{$rel.Notes}}</textarea></p> |
| 33 | <p><button type="submit">Save</button></p> | 33 | <p><button type="submit">Save</button></p> |
| 34 | </form></details>{{end}} | 34 | </form> |
| 35 | {{if $.CanAdmin}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/releases" class="commentform"> | ||
| 36 | <input type="hidden" name="action" value="delete"> | ||
| 37 | <input type="hidden" name="tag" value="{{$rel.Tag}}"> | ||
| 38 | <p class="meta">Deleting is permanent and takes the assets with it. The tag stays.</p> | ||
| 39 | <p><button type="submit" class="linklike">Delete release</button></p> | ||
| 40 | </form>{{end}}</details>{{end}} | ||
| 35 | {{if $rel.Assets}}<table class="assets"> | 41 | {{if $rel.Assets}}<table class="assets"> |
| 36 | <thead><tr><th scope="col">File</th><th scope="col">Bytes</th><th scope="col">SHA-256</th></tr></thead> | 42 | <thead><tr><th scope="col">File</th><th scope="col">Bytes</th><th scope="col">SHA-256</th></tr></thead> |
| 37 | {{range $rel.Assets}}<tr> | 43 | {{range $rel.Assets}}<tr> |