Commit f99b620c29
Verified · cmc
internal/httpd/mractions.go +10
| @@ -66,6 +66,16 @@ func (s *Server) mrReviewRequestSubmit(w http.ResponseWriter, r *http.Request, u | ||
| 66 | 66 | s.done(w, r, code, msg, s.mrRedirect) |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | func (s *Server) mrLabelSubmit(w http.ResponseWriter, r *http.Request, u store.User) { | |
| 70 | args := append(fieldArgs("--add", r.FormValue("add")), fieldArgs("--remove", r.FormValue("remove"))...) | |
| 71 | if len(args) == 0 { | |
| 72 | s.mrRedirect(w, r, "name at least one label") | |
| 73 | return | |
| 74 | } | |
| 75 | _, msg, code := s.runControlCode(u, mrArgs(r, "label", args...)) | |
| 76 | s.done(w, r, code, msg, s.mrRedirect) | |
| 77 | } | |
| 78 | ||
| 69 | 79 | func (s *Server) mrMergeSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| 70 | 80 | args := []string{} |
| 71 | 81 | if st := strings.TrimSpace(r.FormValue("strategy")); st != "" && st != "auto" { |
internal/httpd/routes.go +2
| @@ -202,6 +202,8 @@ func (s *Server) Routes() []Route { | ||
| 202 | 202 | Handler: s.checkOrigin(s.requireUser(s.mrReviewSubmit))}, |
| 203 | 203 | Route{Method: "POST", Pattern: "/{owner}/{repo}/mrs/{n}/review-request", Mutating: true, |
| 204 | 204 | Handler: s.checkOrigin(s.requireUser(s.mrReviewRequestSubmit))}, |
| 205 | Route{Method: "POST", Pattern: "/{owner}/{repo}/mrs/{n}/label", Mutating: true, | |
| 206 | Handler: s.checkOrigin(s.requireUser(s.mrLabelSubmit))}, | |
| 205 | 207 | Route{Method: "POST", Pattern: "/{owner}/{repo}/mrs/{n}/merge", Mutating: true, |
| 206 | 208 | Handler: s.checkOrigin(s.requireUser(s.mrMergeSubmit))}, |
| 207 | 209 | Route{Method: "POST", Pattern: "/{owner}/{repo}/mrs/{n}/close", Mutating: true, |
internal/httpd/web.go +17 −9
| @@ -1814,8 +1814,8 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) { | ||
| 1814 | 1814 | state = "open" |
| 1815 | 1815 | } |
| 1816 | 1816 | qv := r.URL.Query() |
| 1817 | mf := store.MRFilter{State: state, Author: qv.Get("author"), Milestone: qv.Get("milestone"), | |
| 1818 | Search: strings.TrimSpace(qv.Get("q")), Limit: listPage + 1} | |
| 1817 | mf := store.MRFilter{State: state, Label: qv.Get("label"), Author: qv.Get("author"), | |
| 1818 | Milestone: qv.Get("milestone"), Search: strings.TrimSpace(qv.Get("q")), Limit: listPage + 1} | |
| 1819 | 1819 | mf.Before, _ = strconv.ParseInt(qv.Get("before"), 10, 64) |
| 1820 | 1820 | mrs, err := s.st.QueryMRs(p.Repo.ID, mf) |
| 1821 | 1821 | if err != nil { |
| @@ -1841,19 +1841,26 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) { | ||
| 1841 | 1841 | if err != nil { |
| 1842 | 1842 | comments = map[int64]int{} |
| 1843 | 1843 | } |
| 1844 | labels, err := s.st.ListMRLabels(p.Repo) | |
| 1845 | if err != nil { | |
| 1846 | labels = map[int64][]string{} | |
| 1847 | } | |
| 1844 | 1848 | rows := make([]mrRow, len(mrs)) |
| 1845 | 1849 | for i, m := range mrs { |
| 1850 | m.Labels = labels[m.ID] | |
| 1846 | 1851 | rows[i] = mrRow{MR: m, Check: checks[m.HeadSHA], Comments: comments[m.ID]} |
| 1847 | 1852 | } |
| 1848 | 1853 | s.render(w, "mrs.html", struct { |
| 1849 | 1854 | repoPage |
| 1850 | State string | |
| 1851 | Query string | |
| 1852 | Filters []listFilter | |
| 1853 | MRs []mrRow | |
| 1854 | Older string | |
| 1855 | State string | |
| 1856 | Query string | |
| 1857 | Filters []listFilter | |
| 1858 | MRs []mrRow | |
| 1859 | LabelColors map[string]template.CSS | |
| 1860 | Older string | |
| 1855 | 1861 | }{p, state, mf.Search, |
| 1856 | activeFilters(state, [][2]string{{"author", mf.Author}, {"milestone", mf.Milestone}}), rows, older}) | |
| 1862 | activeFilters(state, [][2]string{{"label", mf.Label}, {"author", mf.Author}, {"milestone", mf.Milestone}}), | |
| 1863 | rows, s.labelColors(p.Repo), older}) | |
| 1857 | 1864 | } |
| 1858 | 1865 | |
| 1859 | 1866 | func (s *Server) mr(w http.ResponseWriter, r *http.Request) { |
| @@ -2016,10 +2023,11 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { | ||
| 2016 | 2023 | HeadMerged bool |
| 2017 | 2024 | HeadPruned bool |
| 2018 | 2025 | Base string |
| 2026 | LabelColors map[string]template.CSS | |
| 2019 | 2027 | }{p, m, view, md(m.Body, m.BodyFormat), checks, combined, renderComments(comments, md), |
| 2020 | 2028 | reviewRows, files, diffTruncated, stat, commits, commitsTotal, branches, s.canEditItem(r, p.Repo, m.Author), |
| 2021 | 2029 | canWrite, unresolved, revisions, s.takeFlash(w, r), detachedThreads, stackedOn, stacked, supersedes, gates, |
| 2022 | sourceGone(p, m), headMerged, headPruned, base}) | |
| 2030 | sourceGone(p, m), headMerged, headPruned, base, s.labelColors(p.Repo)}) | |
| 2023 | 2031 | } |
| 2024 | 2032 | |
| 2025 | 2033 | // sourceGone reports whether an MR's source branch no longer exists: the |
internal/web/templates/labels.html +3 −2
| @@ -3,7 +3,7 @@ | ||
| 3 | 3 | <h1>Labels</h1> |
| 4 | 4 | {{if .Notice}}<p class="error" role="alert">{{.Notice}}</p>{{end}} |
| 5 | 5 | {{if .Labels}}<div class="tablewrap"><table class="keys"> |
| 6 | <tr class="cols"><th scope="col">label</th>{{if or $.CanWrite $.AnyColor}}<th scope="col">colour</th>{{end}}<th scope="col">issues</th><th scope="col"></th></tr> | |
| 6 | <tr class="cols"><th scope="col">label</th>{{if or $.CanWrite $.AnyColor}}<th scope="col">colour</th>{{end}}<th scope="col">issues</th><th scope="col">merge requests</th><th scope="col"></th></tr> | |
| 7 | 7 | {{range .Labels}}<tr> |
| 8 | 8 | <td><a class="chip label" style="{{index $.LabelColors .Name}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/issues?label={{.Name}}">{{.Name}}</a>{{if .Org}} <span class="chip chip-neutral">org</span>{{end}}</td> |
| 9 | 9 | {{if or $.CanWrite $.AnyColor}}<td>{{if and $.CanWrite (not .Org)}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> |
| @@ -12,6 +12,7 @@ | ||
| 12 | 12 | <button type="submit" class="btn">Save</button> |
| 13 | 13 | </form>{{else}}<span class="mono">{{if .Color}}{{.Color}}{{else}}—{{end}}</span>{{end}}</td>{{end}} |
| 14 | 14 | <td>{{.Issues}}</td> |
| 15 | <td>{{.MRs}}</td> | |
| 15 | 16 | <td class="act">{{if and $.CanWrite (not .Org)}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> |
| 16 | 17 | <input type="hidden" name="action" value="remove"> |
| 17 | 18 | <input type="hidden" name="name" value="{{.Name}}"> |
| @@ -32,6 +33,6 @@ | ||
| 32 | 33 | <button type="submit" class="btn">Create label</button> |
| 33 | 34 | </form> |
| 34 | 35 | </details> |
| 35 | <p class="meta">Removing a label takes it off every issue that carries it.</p> | |
| 36 | <p class="meta">Removing a label takes it off every issue and merge request that carries it.</p> | |
| 36 | 37 | {{end}} |
| 37 | 38 | {{end}} |
internal/web/templates/mr.html +12
| @@ -166,6 +166,18 @@ | ||
| 166 | 166 | <h2>Milestone</h2> |
| 167 | 167 | <p class="row"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/milestones">{{.MR.Milestone}}</a></p> |
| 168 | 168 | </div>{{end}} |
| 169 | <div class="grp"> | |
| 170 | <h2>Labels</h2> | |
| 171 | {{if .MR.Labels}}<p class="row">{{range .MR.Labels}}<a class="chip label" style="{{index $.LabelColors .}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs?label={{.}}">{{.}}</a> {{end}}</p> | |
| 172 | {{else}}<p class="none">none yet</p>{{end}} | |
| 173 | {{if .CanWrite}} | |
| 174 | <form method="post" action="{{$base}}/label" class="actions"> | |
| 175 | <input type="text" name="add" aria-label="Add labels" placeholder="add, space-separated"> | |
| 176 | <input type="text" name="remove" aria-label="Remove labels" placeholder="remove"> | |
| 177 | <button type="submit" class="btn">Apply</button> | |
| 178 | </form> | |
| 179 | {{end}} | |
| 180 | </div> | |
| 169 | 181 | </aside> |
| 170 | 182 | </div> |
| 171 | 183 | {{end}} |
internal/web/templates/mrs.html +3 −2
| @@ -13,13 +13,14 @@ | ||
| 13 | 13 | <button type="submit" class="btn">Search</button> |
| 14 | 14 | <input type="hidden" name="state" value="{{.State}}"> |
| 15 | 15 | </form> |
| 16 | {{range .Filters}}<p class="meta">{{.Key}}: <b>{{.Value}}</b> <a href="{{.Clear}}">clear</a></p>{{end}} | |
| 16 | {{range .Filters}}<p class="meta">{{.Key}}: {{if eq .Key "label"}}<span class="chip label" style="{{index $.LabelColors .Value}}">{{.Value}}</span>{{else}}<b>{{.Value}}</b>{{end}} <a href="{{.Clear}}">clear</a></p>{{end}} | |
| 17 | 17 | </div> |
| 18 | 18 | {{if .Viewer}}<p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs/new">New merge request</a></p>{{end}} |
| 19 | 19 | <ul class="issuelist"> |
| 20 | 20 | {{range .MRs}}{{$n := .Number}}<li> |
| 21 | 21 | <div class="issuemain"> |
| 22 | <p class="title"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{.Number}}">{{.Title}}</a></p> | |
| 22 | <p class="title"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{.Number}}">{{.Title}}</a> | |
| 23 | {{range .Labels}}<a class="chip label" style="{{index $.LabelColors .}}" href="?label={{.}}">{{.}}</a> {{end}}</p> | |
| 23 | 24 | <p class="meta">!{{.Number}} by <a href="/{{.Author}}">{{.Author}}</a> · {{if .SourcePath}}{{.SourcePath}}:{{end}}{{.SourceRef}} → {{.TargetRef}}</p> |
| 24 | 25 | </div> |
| 25 | 26 | <div class="issueside"> |