From cecc54bc8c79c220c342b2a5804bd6f45763e1f0 Mon Sep 17 00:00:00 2001 From: James Scott Date: Tue, 5 Nov 2024 20:31:44 +0000 Subject: [PATCH] Add ListMissingOneImplCounts spanner method This is the spanner method that will power the missing one implementation graph. It uses the pre calculated results from the BrowserFeatureSupportEvents table. Features: - It allows the caller to pass in arbitrary set of browsers to compare against. - It has a string pagination token that is decoded and used to continue paging for results - It returns the results in DESC order of the Release Date This method is similar to: 1. the [ListMetricsForFeatureIDBrowserAndChannel](https://github.com/GoogleChrome/webstatus.dev/blob/dd754efe5e2fa27cc80542fbacd41886e765b7ba/lib/gcpspanner/wpt_run_feature_metric.go#L418-L494) method used to get the results for the WPT metrics that power the chart on the feature detail, and 2. the [ListBrowserFeatureCountMetric](https://github.com/GoogleChrome/webstatus.dev/blob/dd754efe5e2fa27cc80542fbacd41886e765b7ba/lib/gcpspanner/browser_feature_count.go#L38C18-L105) method used to get the results for the feature count that power the chart on the stats page This PR is a split up of https://github.com/GoogleChrome/webstatus.dev/compare/missing-one-impl-count-db?expand=1 Fixes #864 Depends on #872 --- lib/gcpspanner/missing_one_implementation.go | 224 ++++++ .../missing_one_implementation_test.go | 668 ++++++++++++++++++ 2 files changed, 892 insertions(+) create mode 100644 lib/gcpspanner/missing_one_implementation.go create mode 100644 lib/gcpspanner/missing_one_implementation_test.go diff --git a/lib/gcpspanner/missing_one_implementation.go b/lib/gcpspanner/missing_one_implementation.go new file mode 100644 index 00000000..c637b2e4 --- /dev/null +++ b/lib/gcpspanner/missing_one_implementation.go @@ -0,0 +1,224 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcpspanner + +import ( + "context" + "errors" + "fmt" + "time" + + "cloud.google.com/go/spanner" + "google.golang.org/api/iterator" +) + +func init() { + missingOneImplTemplate = NewQueryTemplate(missingOneImplCountRawTemplate) +} + +// nolint: gochecknoglobals // WONTFIX. Compile the template once at startup. Startup fails if invalid. +var ( + // missingOneImplTemplate is the compiled version of missingOneImplCountRawTemplate. + missingOneImplTemplate BaseQueryTemplate +) + +// MissingOneImplCountPage contains the details for the missing one implementation count request. +type MissingOneImplCountPage struct { + NextPageToken *string + Metrics []MissingOneImplCount +} + +// spannerMissingOneImplCount is a wrapper for the missing one implementation count. +type spannerMissingOneImplCount struct { + MissingOneImplCount +} + +// MissingOneImplCount contains information regarding the count of features implemented in all other browsers but not +// in the target browser. +type MissingOneImplCount struct { + EventReleaseDate time.Time `spanner:"EventReleaseDate"` + Count int64 `spanner:"Count"` +} + +// missingOneImplCursor: Represents a point for resuming queries based on the last +// browser release date. Useful for pagination. +type missingOneImplCursor struct { + ReleaseDate time.Time `json:"release_date"` +} + +// decodeMissingOneImplCursor provides a wrapper around the generic decodeCursor. +func decodeMissingOneImplCursor(cursor string) (*missingOneImplCursor, error) { + return decodeCursor[missingOneImplCursor](cursor) +} + +// encodeMissingOneImplCursor provides a wrapper around the generic encodeCursor. +func encodeMissingOneImplCursor(releaseDate time.Time) string { + return encodeCursor(missingOneImplCursor{ + ReleaseDate: releaseDate, + }) +} + +const missingOneImplCountRawTemplate = ` +SELECT releases.EventReleaseDate, + ( + SELECT COUNT(DISTINCT wf.ID) + FROM WebFeatures wf + LEFT JOIN BrowserFeatureSupportEvents bfse + ON wf.ID = bfse.WebFeatureID + AND bfse.TargetBrowserName = @targetBrowserParam + AND bfse.EventReleaseDate = releases.EventReleaseDate + AND bfse.SupportStatus = 'unsupported' -- Added condition + WHERE bfse.WebFeatureID IS NOT NULL -- Feature is unsupported by the target browser + AND {{range $browser := .OtherBrowserParamNames}} + EXISTS ( + SELECT 1 + FROM BrowserFeatureSupportEvents bfse_other + WHERE bfse_other.WebFeatureID = wf.ID + AND bfse_other.SupportStatus = 'supported' + AND bfse_other.TargetBrowserName = @{{ $browser }} + AND bfse_other.EventReleaseDate = releases.EventReleaseDate + ) + AND + {{end}} + 1=1 + ) AS Count +FROM ( + SELECT DISTINCT EventReleaseDate + FROM BrowserFeatureSupportEvents + WHERE TargetBrowserName = @targetBrowserParam + AND EventBrowserName IN UNNEST(@allBrowsersParam) +) releases +WHERE releases.EventReleaseDate >= @startAt + AND releases.EventReleaseDate < @endAt + {{if .ReleaseDateParam }} + AND releases.EventReleaseDate < @{{ .ReleaseDateParam }} + {{end}} +ORDER BY releases.EventReleaseDate DESC +LIMIT @limit; +` + +type missingOneImplTemplateData struct { + OtherBrowserParamNames []string + ReleaseDateParam string +} + +func buildMissingOneImplTemplate( + cursor *missingOneImplCursor, + targetBrowser string, + otherBrowsers []string, + startAt time.Time, + endAt time.Time, + pageSize int, +) spanner.Statement { + params := map[string]interface{}{} + allBrowsers := make([]string, len(otherBrowsers)+1) + copy(allBrowsers, otherBrowsers) + allBrowsers[len(allBrowsers)-1] = targetBrowser + params["targetBrowserParam"] = targetBrowser + params["allBrowsersParam"] = allBrowsers + otherBrowsersParamNames := make([]string, 0, len(otherBrowsers)) + for i := range otherBrowsers { + paramName := fmt.Sprintf("otherBrowser%d", i) + params[paramName] = otherBrowsers[i] + otherBrowsersParamNames = append(otherBrowsersParamNames, paramName) + } + params["limit"] = pageSize + + releaseDateParamName := "" + if cursor != nil { + releaseDateParamName = "releaseDateCursor" + params[releaseDateParamName] = cursor.ReleaseDate + } + + params["startAt"] = startAt + params["endAt"] = endAt + + tmplData := missingOneImplTemplateData{ + OtherBrowserParamNames: otherBrowsersParamNames, + ReleaseDateParam: releaseDateParamName, + } + sql := missingOneImplTemplate.Execute(tmplData) + stmt := spanner.NewStatement(sql) + stmt.Params = params + + return stmt +} + +func (c *Client) ListMissingOneImplCounts( + ctx context.Context, + targetBrowser string, + otherBrowsers []string, + startAt time.Time, + endAt time.Time, + pageSize int, + pageToken *string, +) (*MissingOneImplCountPage, error) { + + var cursor *missingOneImplCursor + var err error + if pageToken != nil { + cursor, err = decodeMissingOneImplCursor(*pageToken) + if err != nil { + return nil, errors.Join(ErrInternalQueryFailure, err) + } + } + + txn := c.ReadOnlyTransaction() + defer txn.Close() + + stmt := buildMissingOneImplTemplate( + cursor, + targetBrowser, + otherBrowsers, + startAt, + endAt, + pageSize, + ) + + it := txn.Query(ctx, stmt) + defer it.Stop() + + var results []MissingOneImplCount + for { + row, err := it.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return nil, err + } + var result spannerMissingOneImplCount + if err := row.ToStruct(&result); err != nil { + return nil, err + } + actualResult := MissingOneImplCount{ + EventReleaseDate: result.EventReleaseDate, + Count: result.Count, + } + results = append(results, actualResult) + } + + page := MissingOneImplCountPage{ + Metrics: results, + NextPageToken: nil, + } + + if len(results) == pageSize { + token := encodeMissingOneImplCursor(results[len(results)-1].EventReleaseDate) + page.NextPageToken = &token + } + + return &page, nil +} diff --git a/lib/gcpspanner/missing_one_implementation_test.go b/lib/gcpspanner/missing_one_implementation_test.go new file mode 100644 index 00000000..939a9855 --- /dev/null +++ b/lib/gcpspanner/missing_one_implementation_test.go @@ -0,0 +1,668 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcpspanner + +import ( + "context" + "reflect" + "slices" + "testing" + "time" +) + +func loadDataForListMissingOneImplCounts(ctx context.Context, t *testing.T, client *Client) { + webFeatures := []WebFeature{ + {FeatureKey: "FeatureX", Name: "Cool API"}, + {FeatureKey: "FeatureY", Name: "Super API"}, + {FeatureKey: "FeatureZ", Name: "Neat API"}, + {FeatureKey: "FeatureW", Name: "Amazing API"}, + } + for _, feature := range webFeatures { + _, err := client.UpsertWebFeature(ctx, feature) + if err != nil { + t.Errorf("unexpected error during insert of features. %s", err.Error()) + } + } + + browserReleases := []BrowserRelease{ + // fooBrowser Releases + {BrowserName: "fooBrowser", BrowserVersion: "110", ReleaseDate: time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "fooBrowser", BrowserVersion: "111", ReleaseDate: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "fooBrowser", BrowserVersion: "112", ReleaseDate: time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "fooBrowser", BrowserVersion: "113", ReleaseDate: time.Date(2024, 4, 15, 0, 0, 0, 0, time.UTC)}, + + // barBrowser Releases + {BrowserName: "barBrowser", BrowserVersion: "113", ReleaseDate: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "barBrowser", BrowserVersion: "114", ReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "barBrowser", BrowserVersion: "115", ReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC)}, + + // bazBrowser Releases + {BrowserName: "bazBrowser", BrowserVersion: "16.4", ReleaseDate: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "bazBrowser", BrowserVersion: "16.5", ReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC)}, + {BrowserName: "bazBrowser", BrowserVersion: "17", ReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC)}, + } + for _, release := range browserReleases { + err := client.InsertBrowserRelease(ctx, release) + if err != nil { + t.Errorf("unexpected error during insert of releases. %s", err.Error()) + } + } + + browserFeatureAvailabilities := []struct { + FeatureKey string + BrowserFeatureAvailability + }{ + // fooBrowser Availabilities + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "fooBrowser", BrowserVersion: "111"}, + FeatureKey: "FeatureX", + }, // Available from fooBrowser 111 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "fooBrowser", BrowserVersion: "112"}, + FeatureKey: "FeatureY", + }, // Available from fooBrowser 112 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "fooBrowser", BrowserVersion: "112"}, + FeatureKey: "FeatureZ", + }, // Available from fooBrowser 112 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "fooBrowser", BrowserVersion: "113"}, + FeatureKey: "FeatureW", + }, // Available from fooBrowser 113 + + // barBrowser Availabilities + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "barBrowser", BrowserVersion: "113"}, + FeatureKey: "FeatureX", + }, // Available from barBrowser 113 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "barBrowser", BrowserVersion: "113"}, + FeatureKey: "FeatureZ", + }, // Available from barBrowser 113 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "barBrowser", BrowserVersion: "114"}, + FeatureKey: "FeatureY", + }, // Available from barBrowser 114 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "barBrowser", BrowserVersion: "115"}, + FeatureKey: "FeatureW", + }, // Available from barBrowser 115 + + // bazBrowser Availabilities + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "bazBrowser", BrowserVersion: "16.4"}, + FeatureKey: "FeatureX", + }, // Available from bazBrowser 16.4 + { + BrowserFeatureAvailability: BrowserFeatureAvailability{BrowserName: "bazBrowser", BrowserVersion: "16.5"}, + FeatureKey: "FeatureY", + }, // Available from bazBrowser 16.5 + } + for _, availability := range browserFeatureAvailabilities { + err := client.InsertBrowserFeatureAvailability(ctx, + availability.FeatureKey, availability.BrowserFeatureAvailability) + if err != nil { + t.Errorf("unexpected error during insert. %s", err.Error()) + } + } + err := spannerClient.PrecalculateBrowserFeatureSupportEvents(ctx) + if err != nil { + t.Errorf("unexpected error during pre-calculate. %s", err.Error()) + } +} + +func assertListMissingOneImplCounts(ctx context.Context, t *testing.T, startAt, endAt time.Time, pageToken *string, + targetBrowser string, otherBrowsers []string, pageSize int, expectedPage *MissingOneImplCountPage) { + result, err := spannerClient.ListMissingOneImplCounts( + ctx, + targetBrowser, + otherBrowsers, + startAt, + endAt, + pageSize, + pageToken, + ) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(expectedPage, result) { + t.Errorf("unexpected result.\nExpected %+v\nReceived %+v", expectedPage, result) + } +} + +func TestListMissingOneImplCounts(t *testing.T) { + restartDatabaseContainer(t) + ctx := context.Background() + + loadDataForListMissingOneImplCounts(ctx, t, spannerClient) + actualEvents := spannerClient.readAllBrowserFeatureSupportEvents(ctx, t) + slices.SortFunc(actualEvents, sortBrowserFeatureSupportEvents) + defaultStartAt := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + defaultEndAt := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + defaultPageSize := 100 + + t.Run("bazBrowser ", func(t *testing.T) { + targetBrowser := "bazBrowser" + otherBrowsers := []string{ + "fooBrowser", + "barBrowser", + } + + // nolint:dupl // WONTFIX - false positive + t.Run("all data in one page", func(t *testing.T) { + expectedResult := &MissingOneImplCountPage{ + NextPageToken: nil, + Metrics: []MissingOneImplCount{ + // fooBrowser 113 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureW, FeatureZ + { + EventReleaseDate: time.Date(2024, 4, 15, 0, 0, 0, 0, time.UTC), + Count: 2, + }, + // barBrowser 115 AND bazBrowser 17 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // barBrowser 114 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY + // barBrowser: FeatureX, FeatureZ, FeatureY + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // fooBrowser 112 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // bazBrowser 16.5 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 111 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // bazBrowser 16.4 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // barBrowser 113 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: None + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 110 release + // Currently supported features: + // fooBrowser: None + // barBrowser: None + // bazBrowser: None + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + }, + } + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + nil, + targetBrowser, + otherBrowsers, + defaultPageSize, + expectedResult, + ) + }) + + t.Run("pagination", func(t *testing.T) { + // Page One + pageOneToken := encodeMissingOneImplCursor(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)) + expectedPageOne := &MissingOneImplCountPage{ + NextPageToken: &pageOneToken, + Metrics: []MissingOneImplCount{ + // fooBrowser 113 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureW, FeatureZ + { + EventReleaseDate: time.Date(2024, 4, 15, 0, 0, 0, 0, time.UTC), + Count: 2, + }, + // barBrowser 115 AND bazBrowser 17 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // barBrowser 114 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY + // barBrowser: FeatureX, FeatureZ, FeatureY + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // fooBrowser 112 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + }, + } + + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + nil, + targetBrowser, + otherBrowsers, + 4, + expectedPageOne, + ) + + // Page Two + pageTwoToken := encodeMissingOneImplCursor(time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC)) + expectedPageTwo := &MissingOneImplCountPage{ + NextPageToken: &pageTwoToken, + Metrics: []MissingOneImplCount{ + // bazBrowser 16.5 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 111 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // bazBrowser 16.4 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // barBrowser 113 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: None + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + }, + } + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + &pageOneToken, + targetBrowser, + otherBrowsers, + 4, + expectedPageTwo, + ) + + // Page Three + expectedPageThree := &MissingOneImplCountPage{ + NextPageToken: nil, + Metrics: []MissingOneImplCount{ + // fooBrowser 110 release + // Currently supported features: + // fooBrowser: None + // barBrowser: None + // bazBrowser: None + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + }, + } + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + &pageTwoToken, + targetBrowser, + otherBrowsers, + 4, + expectedPageThree, + ) + }) + + t.Run("should reduce the number of results by constraining startAt and endAt", func(t *testing.T) { + expectedResult := &MissingOneImplCountPage{ + NextPageToken: nil, + Metrics: []MissingOneImplCount{ + // barBrowser 114 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureZ, FeatureY + // barBrowser: FeatureX, FeatureZ, FeatureY + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // fooBrowser 112 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // bazBrowser 16.5 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 111 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: None + { + EventReleaseDate: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + }, + } + assertListMissingOneImplCounts( + ctx, + t, + time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), + time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC), + nil, + targetBrowser, + otherBrowsers, + defaultPageSize, + expectedResult, + ) + }) + + t.Run("should show less data points when looking at a smaller subset of browsers", func(t *testing.T) { + otherBrowsers := []string{ + "barBrowser", + } + + expectedResult := &MissingOneImplCountPage{ + NextPageToken: nil, + Metrics: []MissingOneImplCount{ + // barBrowser 115 AND bazBrowser 17 release + // Currently supported features: + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ, FeatureW + { + EventReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC), + Count: 2, + }, + // barBrowser 114 release + // Currently supported features: + // barBrowser: FeatureX, FeatureZ, FeatureY + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // bazBrowser 16.5 release + // Currently supported features: + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // bazBrowser 16.4 release + // Currently supported features: + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for bazBrowser: FeatureZ + { + EventReleaseDate: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // barBrowser 113 release + // Currently supported features: + // barBrowser: FeatureX, FeatureZ + // bazBrowser: None + // Missing in on for bazBrowser: FeatureX, FeatureZ + { + EventReleaseDate: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC), + Count: 2, + }, + }, + } + + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + nil, + targetBrowser, + otherBrowsers, + defaultPageSize, + expectedResult, + ) + }) + }) + + // Misc tests just to make sure we can get other browser info. + // nolint:dupl // WONTFIX - false positive + t.Run("all fooBrowser data", func(t *testing.T) { + targetBrowser := "fooBrowser" + otherBrowsers := []string{ + "barBrowser", + "bazBrowser", + } + + expectedResult := &MissingOneImplCountPage{ + NextPageToken: nil, + Metrics: []MissingOneImplCount{ + // fooBrowser 113 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 4, 15, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // barBrowser 115 AND bazBrowser 17 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ, FeatureY, FeatureW + // bazBrowser: FeatureX, FeatureY + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // barBrowser 114 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ, FeatureY + // bazBrowser: FeatureX, FeatureY + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 28, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 112 release + // Currently supported features: + // fooBrowser: FeatureX, FeatureY, FeatureZ + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // bazBrowser 16.5 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX, FeatureY + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 3, 5, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 111 release + // Currently supported features: + // fooBrowser: FeatureX + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // bazBrowser 16.4 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: FeatureX + // Missing in on for fooBrowser: FeatureX + { + EventReleaseDate: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC), + Count: 1, + }, + // barBrowser 113 release + // Currently supported features: + // fooBrowser: None + // barBrowser: FeatureX, FeatureZ + // bazBrowser: None + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + // fooBrowser 110 release + // Currently supported features: + // fooBrowser: None + // barBrowser: None + // bazBrowser: None + // Missing in on for fooBrowser: None + { + EventReleaseDate: time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC), + Count: 0, + }, + }, + } + + assertListMissingOneImplCounts( + ctx, + t, + defaultStartAt, + defaultEndAt, + nil, + targetBrowser, + otherBrowsers, + defaultPageSize, + expectedResult, + ) + }) +}