Skip to content

Commit 9e6bdc9

Browse files
committed
internal/middleware: move stats to its own package
Make a package internal/middleware/stats for middleware.Stats and middleware.ElapsedStat. This is part of removing the dependency from internal/frontend on internal/middleware. For golang/go#61399 Change-Id: I44afbfc9b9e28e1caabab8fe700376ec026c863d Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/514521 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Michael Matloob <[email protected]> Reviewed-by: Jamal Carvalho <[email protected]> kokoro-CI: kokoro <[email protected]>
1 parent 051a825 commit 9e6bdc9

File tree

15 files changed

+64
-63
lines changed

15 files changed

+64
-63
lines changed

internal/frontend/details.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package frontend
77
import (
88
"context"
99
"errors"
10+
mstats "golang.org/x/pkgsite/internal/middleware/stats"
1011
"net/http"
1112
"strings"
1213

@@ -16,7 +17,6 @@ import (
1617
"go.opencensus.io/tag"
1718
"golang.org/x/mod/semver"
1819
"golang.org/x/pkgsite/internal"
19-
"golang.org/x/pkgsite/internal/middleware"
2020
"golang.org/x/pkgsite/internal/stdlib"
2121
)
2222

@@ -25,7 +25,7 @@ import (
2525
// stdlib module pages are handled at "/std", and requests to "/mod/std" will
2626
// be redirected to that path.
2727
func (s *Server) serveDetails(w http.ResponseWriter, r *http.Request, ds internal.DataSource) (err error) {
28-
defer middleware.ElapsedStat(r.Context(), "serveDetails")()
28+
defer mstats.Elapsed(r.Context(), "serveDetails")()
2929

3030
ctx := r.Context()
3131
if r.Method != http.MethodGet && r.Method != http.MethodHead {

internal/frontend/doc.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import (
1616
"golang.org/x/pkgsite/internal/godoc"
1717
"golang.org/x/pkgsite/internal/godoc/dochtml"
1818
"golang.org/x/pkgsite/internal/log"
19-
"golang.org/x/pkgsite/internal/middleware"
19+
"golang.org/x/pkgsite/internal/middleware/stats"
2020
"golang.org/x/pkgsite/internal/stdlib"
2121
)
2222

2323
func renderDocParts(ctx context.Context, u *internal.Unit, docPkg *godoc.Package,
2424
nameToVersion map[string]string, bc internal.BuildContext) (_ *dochtml.Parts, err error) {
2525
defer derrors.Wrap(&err, "renderDocParts")
26-
defer middleware.ElapsedStat(ctx, "renderDocParts")()
26+
defer stats.Elapsed(ctx, "renderDocParts")()
2727

2828
modInfo := &godoc.ModuleInfo{
2929
ModulePath: u.ModulePath,

internal/frontend/latest_version.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
"golang.org/x/pkgsite/internal"
1111
"golang.org/x/pkgsite/internal/log"
12-
"golang.org/x/pkgsite/internal/middleware"
12+
"golang.org/x/pkgsite/internal/middleware/stats"
1313
)
1414

1515
// GetLatestInfo returns various pieces of information about the latest
@@ -21,7 +21,7 @@ import (
2121
// It returns empty strings on error.
2222
// It is intended to be used as an argument to middleware.LatestVersions.
2323
func (s *Server) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) internal.LatestInfo {
24-
defer middleware.ElapsedStat(ctx, "GetLatestInfo")()
24+
defer stats.Elapsed(ctx, "GetLatestInfo")()
2525

2626
// It is okay to use a different DataSource (DB connection) than the rest of the
2727
// request, because this makes self-contained calls on the DB.

internal/frontend/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"golang.org/x/pkgsite/internal/godoc/dochtml"
1818
"golang.org/x/pkgsite/internal/log"
1919
"golang.org/x/pkgsite/internal/middleware"
20+
"golang.org/x/pkgsite/internal/middleware/stats"
2021
"golang.org/x/pkgsite/internal/version"
2122
"golang.org/x/text/message"
2223
)
@@ -107,7 +108,7 @@ type File struct {
107108

108109
func fetchMainDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta,
109110
requestedVersion string, expandReadme bool, bc internal.BuildContext) (_ *MainDetails, err error) {
110-
defer middleware.ElapsedStat(ctx, "fetchMainDetails")()
111+
defer stats.Elapsed(ctx, "fetchMainDetails")()
111112

112113
unit, err := ds.GetUnit(ctx, um, internal.WithMain, bc)
113114
if err != nil {
@@ -146,7 +147,7 @@ func fetchMainDetails(ctx context.Context, ds internal.DataSource, um *internal.
146147
goos = doc.GOOS
147148
goarch = doc.GOARCH
148149
buildContexts = unit.BuildContexts
149-
end := middleware.ElapsedStat(ctx, "DecodePackage")
150+
end := stats.Elapsed(ctx, "DecodePackage")
150151
docPkg, err := godoc.DecodePackage(doc.Source)
151152
end()
152153
if err != nil {
@@ -167,7 +168,7 @@ func fetchMainDetails(ctx context.Context, ds internal.DataSource, um *internal.
167168
for _, l := range docParts.Links {
168169
docLinks = append(docLinks, link{Href: l.Href, Body: l.Text})
169170
}
170-
end = middleware.ElapsedStat(ctx, "sourceFiles")
171+
end = stats.Elapsed(ctx, "sourceFiles")
171172
files = sourceFiles(unit, docPkg)
172173
end()
173174
}
@@ -253,7 +254,7 @@ func cleanDocumentation(docs []*internal.Documentation) []*internal.Documentatio
253254
// into an outline.
254255
func readmeContent(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
255256
defer derrors.Wrap(&err, "readmeContent(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
256-
defer middleware.ElapsedStat(ctx, "readmeContent")()
257+
defer stats.Elapsed(ctx, "readmeContent")()
257258
if !u.IsRedistributable {
258259
return &Readme{}, nil
259260
}

internal/frontend/server.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"golang.org/x/pkgsite/internal/licenses"
3333
"golang.org/x/pkgsite/internal/log"
3434
"golang.org/x/pkgsite/internal/memory"
35-
"golang.org/x/pkgsite/internal/middleware"
35+
"golang.org/x/pkgsite/internal/middleware/stats"
3636
"golang.org/x/pkgsite/internal/queue"
3737
"golang.org/x/pkgsite/internal/static"
3838
"golang.org/x/pkgsite/internal/version"
@@ -198,9 +198,9 @@ func (s *Server) Install(handle func(string, http.Handler), cacher Cacher, authV
198198
handle("/", detailHandler)
199199
if s.serveStats {
200200
handle("/detail-stats/",
201-
middleware.Stats()(http.StripPrefix("/detail-stats", s.errorHandler(s.serveDetails))))
201+
stats.Stats()(http.StripPrefix("/detail-stats", s.errorHandler(s.serveDetails))))
202202
handle("/search-stats/",
203-
middleware.Stats()(http.StripPrefix("/search-stats", s.errorHandler(s.serveSearch))))
203+
stats.Stats()(http.StripPrefix("/search-stats", s.errorHandler(s.serveSearch))))
204204
}
205205
handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
206206
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
@@ -672,7 +672,7 @@ func (s *Server) renderErrorPage(ctx context.Context, status int, templateName s
672672

673673
// servePage is used to execute all templates for a *Server.
674674
func (s *Server) servePage(ctx context.Context, w http.ResponseWriter, templateName string, page any) {
675-
defer middleware.ElapsedStat(ctx, "servePage")()
675+
defer stats.Elapsed(ctx, "servePage")()
676676

677677
buf, err := s.renderPage(ctx, templateName, page)
678678
if err != nil {
@@ -688,7 +688,7 @@ func (s *Server) servePage(ctx context.Context, w http.ResponseWriter, templateN
688688

689689
// renderPage executes the given templateName with page.
690690
func (s *Server) renderPage(ctx context.Context, templateName string, page any) ([]byte, error) {
691-
defer middleware.ElapsedStat(ctx, "renderPage")()
691+
defer stats.Elapsed(ctx, "renderPage")()
692692

693693
tmpl, err := s.findTemplate(templateName)
694694
if err != nil {

internal/frontend/unit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"golang.org/x/pkgsite/internal/cookie"
1919
"golang.org/x/pkgsite/internal/derrors"
2020
"golang.org/x/pkgsite/internal/log"
21-
"golang.org/x/pkgsite/internal/middleware"
21+
"golang.org/x/pkgsite/internal/middleware/stats"
2222
"golang.org/x/pkgsite/internal/stdlib"
2323
"golang.org/x/pkgsite/internal/version"
2424
"golang.org/x/pkgsite/internal/vuln"
@@ -106,7 +106,7 @@ type UnitPage struct {
106106
func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *http.Request,
107107
ds internal.DataSource, info *urlPathInfo) (err error) {
108108
defer derrors.Wrap(&err, "serveUnitPage(ctx, w, r, ds, %v)", info)
109-
defer middleware.ElapsedStat(ctx, "serveUnitPage")()
109+
defer stats.Elapsed(ctx, "serveUnitPage")()
110110

111111
tab := r.FormValue("tab")
112112
if tab == "" {

internal/middleware/stats.go renamed to internal/middleware/stats/stats.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package middleware
5+
package stats
66

77
import (
88
"context"
@@ -18,7 +18,7 @@ type statsKey struct{}
1818

1919
// Stats returns a Middleware that, instead of serving the page,
2020
// serves statistics about the page.
21-
func Stats() Middleware {
21+
func Stats() func(http.Handler) http.Handler {
2222
return func(h http.Handler) http.Handler {
2323
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2424
sw := newStatsResponseWriter()
@@ -29,9 +29,9 @@ func Stats() Middleware {
2929
}
3030
}
3131

32-
// SetStat sets a stat named key in the current context. If key already has a
32+
// set sets a stat named key in the current context. If key already has a
3333
// value, the old and new value are both stored in a slice.
34-
func SetStat(ctx context.Context, key string, value any) {
34+
func set(ctx context.Context, key string, value any) {
3535
x := ctx.Value(statsKey{})
3636
if x == nil {
3737
return
@@ -47,17 +47,17 @@ func SetStat(ctx context.Context, key string, value any) {
4747
}
4848
}
4949

50-
// ElapsedStat records as a stat the elapsed time for a
50+
// Elapsed records as a stat the elapsed time for a
5151
// function execution. Invoke like so:
5252
//
53-
// defer ElapsedStat(ctx, "FunctionName")()
53+
// defer Elapsed(ctx, "FunctionName")()
5454
//
5555
// The resulting stat will be called "FunctionName ms" and will
5656
// be the wall-clock execution time of the function in milliseconds.
57-
func ElapsedStat(ctx context.Context, name string) func() {
57+
func Elapsed(ctx context.Context, name string) func() {
5858
start := time.Now()
5959
return func() {
60-
SetStat(ctx, name+" ms", time.Since(start).Milliseconds())
60+
set(ctx, name+" ms", time.Since(start).Milliseconds())
6161
}
6262
}
6363

internal/middleware/stats_test.go renamed to internal/middleware/stats/stats_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package middleware
5+
package stats
66

77
import (
88
"encoding/json"
@@ -23,11 +23,11 @@ func TestStats(t *testing.T) {
2323
ts := httptest.NewServer(Stats()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2424
ctx := r.Context()
2525
w.WriteHeader(code)
26-
SetStat(ctx, "a", 1)
26+
set(ctx, "a", 1)
2727
w.Write(data[:10])
28-
SetStat(ctx, "b", 2)
28+
set(ctx, "b", 2)
2929
time.Sleep(500 * time.Millisecond)
30-
SetStat(ctx, "a", 3)
30+
set(ctx, "a", 3)
3131
w.Write(data[10:])
3232
})))
3333
defer ts.Close()

internal/postgres/details.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import (
1515
"golang.org/x/pkgsite/internal"
1616
"golang.org/x/pkgsite/internal/database"
1717
"golang.org/x/pkgsite/internal/derrors"
18-
"golang.org/x/pkgsite/internal/middleware"
18+
"golang.org/x/pkgsite/internal/middleware/stats"
1919
)
2020

2121
// GetNestedModules returns the latest major version of all nested modules
2222
// given a modulePath path prefix with or without major version.
2323
func (db *DB) GetNestedModules(ctx context.Context, modulePath string) (_ []*internal.ModuleInfo, err error) {
2424
defer derrors.WrapStack(&err, "GetNestedModules(ctx, %v)", modulePath)
25-
defer middleware.ElapsedStat(ctx, "GetNestedModules")()
25+
defer stats.Elapsed(ctx, "GetNestedModules")()
2626

2727
query := `
2828
SELECT DISTINCT ON (series_path)
@@ -78,7 +78,7 @@ func (db *DB) GetNestedModules(ctx context.Context, modulePath string) (_ []*int
7878
// Instead of supporting pagination, this query runs with a limit.
7979
func (db *DB) GetImportedBy(ctx context.Context, pkgPath, modulePath string, limit int) (paths []string, err error) {
8080
defer derrors.WrapStack(&err, "GetImportedBy(ctx, %q, %q)", pkgPath, modulePath)
81-
defer middleware.ElapsedStat(ctx, "GetImportedBy")()
81+
defer stats.Elapsed(ctx, "GetImportedBy")()
8282

8383
if pkgPath == "" {
8484
return nil, fmt.Errorf("pkgPath cannot be empty: %w", derrors.InvalidArgument)
@@ -102,7 +102,7 @@ func (db *DB) GetImportedBy(ctx context.Context, pkgPath, modulePath string, lim
102102
// GetImportedByCount returns the number of packages that import pkgPath.
103103
func (db *DB) GetImportedByCount(ctx context.Context, pkgPath, modulePath string) (_ int, err error) {
104104
defer derrors.WrapStack(&err, "GetImportedByCount(ctx, %q, %q)", pkgPath, modulePath)
105-
defer middleware.ElapsedStat(ctx, "GetImportedByCount")()
105+
defer stats.Elapsed(ctx, "GetImportedByCount")()
106106

107107
if pkgPath == "" {
108108
return 0, fmt.Errorf("pkgPath cannot be empty: %w", derrors.InvalidArgument)

internal/postgres/licenses.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import (
1717
"github.com/lib/pq"
1818
"golang.org/x/pkgsite/internal/derrors"
1919
"golang.org/x/pkgsite/internal/licenses"
20-
"golang.org/x/pkgsite/internal/middleware"
20+
"golang.org/x/pkgsite/internal/middleware/stats"
2121
"golang.org/x/pkgsite/internal/stdlib"
2222
)
2323

2424
func (db *DB) getLicenses(ctx context.Context, fullPath, modulePath string, unitID int) (_ []*licenses.License, err error) {
2525
defer derrors.WrapStack(&err, "getLicenses(ctx, %d)", unitID)
26-
defer middleware.ElapsedStat(ctx, "getLicenses")()
26+
defer stats.Elapsed(ctx, "getLicenses")()
2727

2828
query := `
2929
SELECT

internal/postgres/package_symbol.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import (
1313
"golang.org/x/pkgsite/internal"
1414
"golang.org/x/pkgsite/internal/database"
1515
"golang.org/x/pkgsite/internal/derrors"
16-
"golang.org/x/pkgsite/internal/middleware"
16+
"golang.org/x/pkgsite/internal/middleware/stats"
1717
)
1818

1919
// getPackageSymbols returns all of the symbols for a given package path and module path.
2020
func getPackageSymbols(ctx context.Context, ddb *database.DB, packagePath, modulePath string,
2121
) (_ *internal.SymbolHistory, err error) {
2222
defer derrors.Wrap(&err, "getPackageSymbols(ctx, ddb, %q, %q)", packagePath, modulePath)
23-
defer middleware.ElapsedStat(ctx, "getPackageSymbols")()
23+
defer stats.Elapsed(ctx, "getPackageSymbols")()
2424

2525
query := packageSymbolQueryJoin(
2626
squirrel.Select(

internal/postgres/symbol_history.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"golang.org/x/pkgsite/internal"
1414
"golang.org/x/pkgsite/internal/database"
1515
"golang.org/x/pkgsite/internal/derrors"
16-
"golang.org/x/pkgsite/internal/middleware"
16+
"golang.org/x/pkgsite/internal/middleware/stats"
1717
"golang.org/x/pkgsite/internal/symbol"
1818
)
1919

@@ -22,7 +22,7 @@ import (
2222
func (db *DB) GetSymbolHistory(ctx context.Context, packagePath, modulePath string,
2323
) (_ *internal.SymbolHistory, err error) {
2424
defer derrors.Wrap(&err, "GetSymbolHistory(ctx, %q, %q)", packagePath, modulePath)
25-
defer middleware.ElapsedStat(ctx, "GetSymbolHistory")()
25+
defer stats.Elapsed(ctx, "GetSymbolHistory")()
2626

2727
return GetSymbolHistoryFromTable(ctx, db.db, packagePath, modulePath)
2828
}
@@ -71,7 +71,7 @@ func GetSymbolHistoryFromTable(ctx context.Context, ddb *database.DB,
7171
func GetSymbolHistoryWithPackageSymbols(ctx context.Context, ddb *database.DB,
7272
packagePath, modulePath string) (_ *internal.SymbolHistory, err error) {
7373
defer derrors.WrapStack(&err, "GetSymbolHistoryWithPackageSymbols(ctx, ddb, %q, %q)", packagePath, modulePath)
74-
defer middleware.ElapsedStat(ctx, "GetSymbolHistoryWithPackageSymbols")()
74+
defer stats.Elapsed(ctx, "GetSymbolHistoryWithPackageSymbols")()
7575
sh, err := getPackageSymbols(ctx, ddb, packagePath, modulePath)
7676
if err != nil {
7777
return nil, err
@@ -86,7 +86,7 @@ func GetSymbolHistoryWithPackageSymbols(ctx context.Context, ddb *database.DB,
8686
func GetSymbolHistoryForBuildContext(ctx context.Context, ddb *database.DB, pathID int, modulePath string,
8787
bc internal.BuildContext) (_ map[string]string, err error) {
8888
defer derrors.WrapStack(&err, "GetSymbolHistoryForBuildContext(ctx, ddb, %d, %q)", pathID, modulePath)
89-
defer middleware.ElapsedStat(ctx, "GetSymbolHistoryForBuildContext")()
89+
defer stats.Elapsed(ctx, "GetSymbolHistoryForBuildContext")()
9090

9191
if bc == internal.BuildContextAll {
9292
bc = internal.BuildContextLinux

internal/postgres/symbolsearch.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ import (
1515
"github.com/lib/pq"
1616
"golang.org/x/pkgsite/internal/database"
1717
"golang.org/x/pkgsite/internal/derrors"
18-
"golang.org/x/pkgsite/internal/middleware"
18+
"golang.org/x/pkgsite/internal/middleware/stats"
1919
"golang.org/x/pkgsite/internal/postgres/search"
2020
"golang.org/x/sync/errgroup"
2121
)
2222

2323
func upsertSymbolSearchDocuments(ctx context.Context, tx *database.DB,
2424
modulePath, v string) (err error) {
2525
defer derrors.Wrap(&err, "upsertSymbolSearchDocuments(ctx, ddb, %q, %q)", modulePath, v)
26-
defer middleware.ElapsedStat(ctx, "upsertSymbolSearchDocuments")()
26+
defer stats.Elapsed(ctx, "upsertSymbolSearchDocuments")()
2727

2828
// If a user is looking for the symbol "DB.Begin", from package
2929
// database/sql, we want them to be able to find this by searching for
@@ -97,7 +97,7 @@ func upsertSymbolSearchDocuments(ctx context.Context, tx *database.DB,
9797
// TODO(https://golang.org/issue/44142): factor out common code between
9898
// symbolSearch and deepSearch.
9999
func (db *DB) symbolSearch(ctx context.Context, q string, limit int, opts SearchOptions) searchResponse {
100-
defer middleware.ElapsedStat(ctx, "symbolSearch")()
100+
defer stats.Elapsed(ctx, "symbolSearch")()
101101

102102
var (
103103
results []*SearchResult
@@ -156,7 +156,7 @@ func runSymbolSearchMultiWord(ctx context.Context, ddb *database.DB, q string, l
156156
symbolFilter string) (_ []*SearchResult, err error) {
157157
defer derrors.Wrap(&err, "runSymbolSearchMultiWord(ctx, ddb, query, %q, %d, %q)",
158158
q, limit, symbolFilter)
159-
defer middleware.ElapsedStat(ctx, "runSymbolSearchMultiWord")()
159+
defer stats.Elapsed(ctx, "runSymbolSearchMultiWord")()
160160

161161
symbolToPathTokens := multiwordSearchCombinations(q, symbolFilter)
162162
if len(symbolToPathTokens) == 0 {
@@ -259,7 +259,7 @@ func multiwordSearchCombinations(q, symbolFilter string) map[string]string {
259259
// when using an OR in the WHERE clause.
260260
func runSymbolSearchOneDot(ctx context.Context, ddb *database.DB, q string, limit int) (_ []*SearchResult, err error) {
261261
defer derrors.Wrap(&err, "runSymbolSearchOneDot(ctx, ddb, %q, %d)", q, limit)
262-
defer middleware.ElapsedStat(ctx, "runSymbolSearchOneDot")()
262+
defer stats.Elapsed(ctx, "runSymbolSearchOneDot")()
263263

264264
group, searchCtx := errgroup.WithContext(ctx)
265265
resultsArray := make([][]*SearchResult, 2)
@@ -318,7 +318,7 @@ func splitPackageAndSymbolNames(q string) (pkgName string, symbolName string, er
318318
func runSymbolSearch(ctx context.Context, ddb *database.DB,
319319
st search.SearchType, q string, limit int, args ...any) (results []*SearchResult, err error) {
320320
defer derrors.Wrap(&err, "runSymbolSearch(ctx, ddb, %q, %q, %d, %v)", st, q, limit, args)
321-
defer middleware.ElapsedStat(ctx, fmt.Sprintf("%s-runSymbolSearch", st))()
321+
defer stats.Elapsed(ctx, fmt.Sprintf("%s-runSymbolSearch", st))()
322322

323323
collect := func(rows *sql.Rows) error {
324324
var r SearchResult

0 commit comments

Comments
 (0)