internal/frontend: add package tab query

If a user searches for #DB, they will always be brought to the symbols
tab. However, if they click on the packages tab from that page, we want
them to see something useful.

As a result, the packages tab now has href set to:

/search?m=package&q=<query-stripped-of-filters.

For golang/go#44142

Change-Id: Iff7f3d8b3f8752a40855b816cc94354b0f7ea93f
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/348814
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index dab2666..081a6f3 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -44,8 +44,8 @@
 	}
 
 	ctx := r.Context()
-	query, filters := searchQueryAndFilters(r)
-	if !utf8.ValidString(query) {
+	cq, filters := searchQueryAndFilters(r)
+	if !utf8.ValidString(cq) {
 		return &serverError{status: http.StatusBadRequest}
 	}
 	if len(filters) > 1 {
@@ -57,7 +57,7 @@
 			},
 		}
 	}
-	if len(query) > maxSearchQueryLength {
+	if len(cq) > maxSearchQueryLength {
 		return &serverError{
 			status: http.StatusBadRequest,
 			epage: &errorPage{
@@ -66,7 +66,7 @@
 			},
 		}
 	}
-	if query == "" {
+	if cq == "" {
 		http.Redirect(w, r, "/", http.StatusFound)
 		return nil
 	}
@@ -89,7 +89,7 @@
 			},
 		}
 	}
-	if path := searchRequestRedirectPath(ctx, ds, query); path != "" {
+	if path := searchRequestRedirectPath(ctx, ds, cq); path != "" {
 		http.Redirect(w, r, path, http.StatusFound)
 		return nil
 	}
@@ -99,11 +99,11 @@
 		symbol = filters[0]
 	}
 	mode := searchMode(r)
-	page, err := fetchSearchPage(ctx, db, query, symbol, pageParams, mode == searchModeSymbol, s.getVulnEntries)
+	page, err := fetchSearchPage(ctx, db, cq, symbol, pageParams, mode == searchModeSymbol, s.getVulnEntries)
 	if err != nil {
-		return fmt.Errorf("fetchSearchPage(ctx, db, %q): %v", query, err)
+		return fmt.Errorf("fetchSearchPage(ctx, db, %q): %v", cq, err)
 	}
-	page.basePage = s.newBasePage(r, fmt.Sprintf("%s - Search Results", query))
+	page.basePage = s.newBasePage(r, fmt.Sprintf("%s - Search Results", cq))
 	page.SearchMode = mode
 	if s.shouldServeJSON(r) {
 		return s.serveJSONPage(w, r, page)
@@ -152,6 +152,11 @@
 // populate.
 type SearchPage struct {
 	basePage
+
+	// PackageTabQuery is the search query, stripped of any filters.
+	// This is used if the user clicks on the package tab.
+	PackageTabQuery string
+
 	Pagination pagination
 	Results    []*SearchResult
 }
@@ -187,7 +192,7 @@
 
 // fetchSearchPage fetches data matching the search query from the database and
 // returns a SearchPage.
-func fetchSearchPage(ctx context.Context, db *postgres.DB, query, symbol string,
+func fetchSearchPage(ctx context.Context, db *postgres.DB, cq, symbol string,
 	pageParams paginationParams, searchSymbols bool, getVulnEntries vulnEntriesFunc) (*SearchPage, error) {
 	maxResultCount := maxSearchOffset + pageParams.limit
 
@@ -196,7 +201,7 @@
 		// When using search grouping, do pageless search: always start from the beginning.
 		offset = 0
 	}
-	dbresults, err := db.Search(ctx, query, postgres.SearchOptions{
+	dbresults, err := db.Search(ctx, cq, postgres.SearchOptions{
 		MaxResults:     pageParams.limit,
 		Offset:         offset,
 		MaxResultCount: maxResultCount,
@@ -234,8 +239,9 @@
 
 	pgs := newPagination(pageParams, numPageResults, numResults)
 	sp := &SearchPage{
-		Results:    results,
-		Pagination: pgs,
+		PackageTabQuery: cq,
+		Results:         results,
+		Pagination:      pgs,
 	}
 	return sp, nil
 }
diff --git a/internal/frontend/search_test.go b/internal/frontend/search_test.go
index 5b24f20..de826e2 100644
--- a/internal/frontend/search_test.go
+++ b/internal/frontend/search_test.go
@@ -176,6 +176,7 @@
 			name:  "want expected search page",
 			query: "foo bar",
 			wantSearchPage: &SearchPage{
+				PackageTabQuery: "foo bar",
 				Pagination: pagination{
 					TotalCount:   1,
 					ResultCount:  1,
@@ -205,6 +206,7 @@
 			name:  "want only foo search page",
 			query: "package",
 			wantSearchPage: &SearchPage{
+				PackageTabQuery: "package",
 				Pagination: pagination{
 					TotalCount:   1,
 					ResultCount:  1,
diff --git a/static/frontend/search/search.tmpl b/static/frontend/search/search.tmpl
index b640197..88031d8 100644
--- a/static/frontend/search/search.tmpl
+++ b/static/frontend/search/search.tmpl
@@ -193,8 +193,12 @@
   <div>
     <nav class="go-TabNav">
       <ul>
-        <li {{if not (eq .SearchMode .SearchModeSymbol)}}aria-current="page"{{end}}><a href="{{.Pagination.URL .Pagination.Limit .SearchModePackage .Query}}">Packages</a></li>
-        <li {{if eq .SearchMode .SearchModeSymbol}}aria-current="page"{{end}}><a href="{{.Pagination.URL .Pagination.Limit .SearchModeSymbol .Query}}">Symbols</a></li>
+        <li {{if not (eq .SearchMode .SearchModeSymbol)}}aria-current="page"{{end}}>
+          <a href="{{.Pagination.URL .Pagination.Limit .SearchModePackage .PackageTabQuery}}">Packages</a>
+        </li>
+        <li {{if eq .SearchMode .SearchModeSymbol}}aria-current="page"{{end}}>
+          <a href="{{.Pagination.URL .Pagination.Limit .SearchModeSymbol .Query}}">Symbols</a>
+        </li>
       </ul>
       <hr />
     </nav>
diff --git a/tests/search/main.go b/tests/search/main.go
index 41cbba8..ee0a1ba 100755
--- a/tests/search/main.go
+++ b/tests/search/main.go
@@ -117,6 +117,9 @@
 		return nil, err
 	}
 	gotResults := searchPage.Results
+	if strings.ContainsAny(searchPage.PackageTabQuery, "#") {
+		output = append(output, "invalid package tab query, should not contain #: %q", searchPage.PackageTabQuery)
+	}
 	for i, want := range st.results {
 		got := &frontend.SearchResult{}
 		if len(gotResults) > i {