github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/versions.go (about)

     1  // Copyright 2018 The WPT Dashboard Project. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net/http"
    12  	"sort"
    13  	"time"
    14  
    15  	"github.com/web-platform-tests/wpt.fyi/shared"
    16  )
    17  
    18  // VersionsHandler is an http.Handler for the /api/versions endpoint.
    19  type VersionsHandler struct {
    20  	ctx context.Context // nolint:containedctx // TODO: Fix containedctx lint error
    21  }
    22  
    23  // apiVersionsHandler is responsible for emitting just the browser versions for the test runs.
    24  func apiVersionsHandler(w http.ResponseWriter, r *http.Request) {
    25  	// Serve cached with 5 minute expiry. Delegate to VersionsHandler on cache
    26  	// miss.
    27  	ctx := r.Context()
    28  	shared.NewCachingHandler(
    29  		ctx,
    30  		VersionsHandler{ctx},
    31  		shared.NewGZReadWritable(shared.NewRedisReadWritable(ctx, 5*time.Minute)),
    32  		shared.AlwaysCachable,
    33  		shared.URLAsCacheKey,
    34  		shared.CacheStatusOK,
    35  	).ServeHTTP(w, r)
    36  }
    37  
    38  func (h VersionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    39  	product, err := shared.ParseProductParam(r.URL.Query())
    40  	if err != nil {
    41  		http.Error(w, err.Error(), http.StatusBadRequest)
    42  
    43  		return
    44  	} else if product == nil {
    45  		http.Error(w, fmt.Sprintf("Invalid product param: %s", r.URL.Query().Get("product")), http.StatusBadRequest)
    46  
    47  		return
    48  	}
    49  
    50  	ctx := h.ctx
    51  	store := shared.NewAppEngineDatastore(ctx, false)
    52  	query := store.NewQuery("TestRun").Filter("BrowserName =", product.BrowserName)
    53  	if product.Labels != nil {
    54  		for label := range product.Labels.Iter() {
    55  			query = query.Filter("Labels =", label)
    56  		}
    57  	}
    58  	distinctQuery := query.Project("BrowserVersion").Distinct()
    59  	var queries []shared.Query
    60  	if product.BrowserVersion == "" {
    61  		queries = []shared.Query{distinctQuery}
    62  	} else {
    63  		queries = []shared.Query{
    64  			query.Filter("BrowserVersion =", product.BrowserVersion).Limit(1),
    65  			shared.VersionPrefix(distinctQuery, "BrowserVersion", product.BrowserVersion, false /*desc*/),
    66  		}
    67  	}
    68  
    69  	var runs shared.TestRuns
    70  	for _, query := range queries {
    71  		var someRuns shared.TestRuns
    72  		if _, err := store.GetAll(query, &someRuns); err != nil {
    73  			http.Error(w, err.Error(), http.StatusInternalServerError)
    74  
    75  			return
    76  		}
    77  		runs = append(runs, someRuns...)
    78  	}
    79  
    80  	if len(runs) < 1 {
    81  		w.WriteHeader(http.StatusNotFound)
    82  		_, err = w.Write([]byte("[]"))
    83  		if err != nil {
    84  			logger := shared.GetLogger(ctx)
    85  			logger.Warningf("Failed to write data in api/versions handler: %s", err.Error())
    86  		}
    87  
    88  		return
    89  	}
    90  
    91  	versions := make([]string, len(runs))
    92  	for i := range runs {
    93  		versions[i] = runs[i].BrowserVersion
    94  	}
    95  	// nolint:godox // TODO(lukebjerring): Fix this, it will put 100 before 11..., etc.
    96  	sort.Sort(sort.Reverse(sort.StringSlice(versions)))
    97  
    98  	versionsBytes, err := json.Marshal(versions)
    99  	if err != nil {
   100  		http.Error(w, err.Error(), http.StatusInternalServerError)
   101  
   102  		return
   103  	}
   104  
   105  	_, err = w.Write(versionsBytes)
   106  	if err != nil {
   107  		logger := shared.GetLogger(ctx)
   108  		logger.Warningf("Failed to write data in api/versions handler: %s", err.Error())
   109  	}
   110  }