internal/reviews: add filter box to display of open changes This adds a new form input field to the top of the display of open changes. The new field may be used as a filter expression that selects the changes to display. For example, entering "Ian Lance Taylor" will show all CLs with a field (typically a DisplayName field) matching "Ian Lance Taylor". Or "NOT Predicates.Name:hasUnresolvedComments" will show all CLs that do not have the hasUnresolvedComments predicate (that is, will show all CLs for which all comments are resolved). Change-Id: Id29ee69a5228bca9d5730e71929be65a3fba9127 Reviewed-on: https://go-review.googlesource.com/c/oscar/+/650018 Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> Commit-Queue: Ian Lance Taylor <iant@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/gaby/reviews.go b/internal/gaby/reviews.go index 9629715..ef5958e 100644 --- a/internal/gaby/reviews.go +++ b/internal/gaby/reviews.go
@@ -16,5 +16,5 @@ http.Error(w, err.Error(), http.StatusInternalServerError) return } - c.Display(w, r) + c.Display(reviewsID.Endpoint(), w, r) }
diff --git a/internal/goreviews/changes.go b/internal/goreviews/changes.go index 2863c98..fb6bb03 100644 --- a/internal/goreviews/changes.go +++ b/internal/goreviews/changes.go
@@ -54,10 +54,10 @@ const displayDoc template.HTML = `Excluding those marked WIP, having hashtags "wait-author", "wait-release", "wait-issue", or description containing "DO NOT REVIEW".` // Display is an HTTP handler that displays the dashboard. -func (ch *Changes) Display(w http.ResponseWriter, r *http.Request) { +func (ch *Changes) Display(endpoint string, w http.ResponseWriter, r *http.Request) { ch.mu.Lock() cps := ch.cps ch.mu.Unlock() - reviews.Display(ch.slog, displayDoc, cps, w, r) + reviews.Display(ch.slog, displayDoc, endpoint, cps, w, r) }
diff --git a/internal/reviews/display.go b/internal/reviews/display.go index 08f2e47..5974ab1 100644 --- a/internal/reviews/display.go +++ b/internal/reviews/display.go
@@ -5,34 +5,72 @@ package reviews import ( + "errors" + "fmt" "html/template" "io" "log/slog" "net/http" + "strings" + + "golang.org/x/oscar/internal/filter" ) // Display is an HTTP handler function that displays the // changes to review. -func Display(lg *slog.Logger, doc template.HTML, cps []ChangePreds, w http.ResponseWriter, r *http.Request) { +func Display(lg *slog.Logger, doc template.HTML, endpoint string, cps []ChangePreds, w http.ResponseWriter, r *http.Request) { if len(cps) == 0 { io.WriteString(w, reportNoData) return } - sc := make([]CP, len(cps)) - for i, v := range cps { - sc[i] = CP{v} + userFilter := r.FormValue("filter") + filterFn, err := makeFilter(userFilter) + if err != nil { + http.Error(w, fmt.Sprintf("invalid filter: %v", err), http.StatusBadRequest) + return + } + + sc := make([]CP, 0, len(cps)) + for _, v := range cps { + if filterFn != nil && !filterFn(v) { + continue + } + + sc = append(sc, CP{v}) } data := &displayType{ - Doc: doc, - Changes: sc, + Endpoint: endpoint, + Doc: doc, + Filter: userFilter, + Changes: sc, } if err := displayTemplate.Execute(w, data); err != nil { lg.Error("template execution failed", "err", err) } } +// makeFilter turns the user-specific filter string into a filter function. +// This returns nil if there is nothing to filter. +func makeFilter(s string) (func(ChangePreds) bool, error) { + if s == "" { + return nil, nil + } + expr, err := filter.ParseFilter(s) + if err != nil { + return nil, err + } + ev, problems := filter.Evaluator[ChangePreds](expr, nil) + if len(problems) > 0 { + return nil, errors.New(strings.Join(problems, "\n")) + } + fn := func(cp ChangePreds) bool { + return ev(cp) + } + return fn, nil +} + // reportNoData is the HTML that we report if we have not finished // applying predicates to changes. const reportNoData = ` @@ -56,8 +94,10 @@ // displayType is the type expected by displayHTML type displayType struct { - Doc template.HTML - Changes []CP + Endpoint string // Relative URL being served. + Doc template.HTML // Documentation string. + Filter string // Filter value. + Changes []CP // List of changes with predicates. } // CP is the type we pass to the HTML template displayTemplate. @@ -202,6 +242,14 @@ <a href="https://go.googlesource.com/oscar/+/master/internal/goreviews/display.go">Source code</a> </div> </header> + <div class="filter"> + <form id="form" action="{{.Endpoint}}" method="GET"> + <span> + <label for="filter">Filter</label> + <input id="filter" type="text" size=75 name="filter" value="{{.Filter}}"/> + </span> + </form> + </div> {{range $change := .Changes}} <div class="row"> <span class="date">{{.FormattedLastUpdate}}</span>