From ba0e5a5e7c7be3863356fe16737a87126cd12153 Mon Sep 17 00:00:00 2001 From: James C Scott III Date: Wed, 14 Feb 2024 10:22:59 -0500 Subject: [PATCH] Add a generic entity client for datastore (#19) * Add a generic entity client for datastore Previously this client was made specifically for WebFeatures. By moving to generics, we can easily expand support for future entity types. In order to support the generic client, two new interfaces are introduced: - Filterable - Mergeable Filterable allows us to pass custom filters that apply to that entity type Mergeable allows us to define logic for that entity type to describe how to handle merges during updates Also add some tests for this new entity client This is part of splitting up https://github.com/GoogleChrome/webstatus.dev/pull/10 Change-Id: I40ff90582cbb73c11897777e1e72a995669dc12f * fix typo Change-Id: Ifc55e09388d8a9d554b565402f719bc77d8f0a2a --- backend/cmd/server/main.go | 2 +- backend/pkg/httpserver/server.go | 9 +- lib/gds/client.go | 138 ++++++----- lib/gds/client_test.go | 222 +++++++++++++++--- lib/gds/web_feature.go | 115 +++++++++ lib/gds/web_feature_test.go | 66 ++++++ .../web_feature_consumer/cmd/server/main.go | 2 +- .../pkg/httpserver/server.go | 7 +- 8 files changed, 465 insertions(+), 96 deletions(-) create mode 100644 lib/gds/web_feature.go create mode 100644 lib/gds/web_feature_test.go diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 5eb6d92e..7699e80f 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -27,7 +27,7 @@ func main() { if value, found := os.LookupEnv("DATASTORE_DATABASE"); found { datastoreDB = &value } - fs, err := gds.NewWebFeatureClient(os.Getenv("PROJECT_ID"), datastoreDB) + fs, err := gds.NewDatastoreClient(os.Getenv("PROJECT_ID"), datastoreDB) if err != nil { slog.Error("failed to create datastore client", "error", err.Error()) os.Exit(1) diff --git a/backend/pkg/httpserver/server.go b/backend/pkg/httpserver/server.go index b54b9cb9..6e128b17 100644 --- a/backend/pkg/httpserver/server.go +++ b/backend/pkg/httpserver/server.go @@ -28,8 +28,8 @@ import ( ) type WebFeatureMetadataStorer interface { - List(ctx context.Context) ([]backend.Feature, error) - Get(ctx context.Context, featureID string) (*backend.Feature, error) + ListWebFeatureData(ctx context.Context, nextPageToken *string) ([]backend.Feature, *string, error) + GetWebFeatureData(ctx context.Context, featureID string) (*backend.Feature, error) } type Server struct { @@ -42,7 +42,7 @@ func (s *Server) GetV1FeaturesFeatureId( ctx context.Context, request backend.GetV1FeaturesFeatureIdRequestObject, ) (backend.GetV1FeaturesFeatureIdResponseObject, error) { - feature, err := s.metadataStorer.Get(ctx, request.FeatureId) + feature, err := s.metadataStorer.GetWebFeatureData(ctx, request.FeatureId) if err != nil { slog.Error("unable to get feature", "error", err) @@ -61,7 +61,8 @@ func (s *Server) GetV1Features( ctx context.Context, _ backend.GetV1FeaturesRequestObject, ) (backend.GetV1FeaturesResponseObject, error) { - featureData, err := s.metadataStorer.List(ctx) + // TODO. Pass next page token. + featureData, _, err := s.metadataStorer.ListWebFeatureData(ctx, nil) if err != nil { // TODO check error type slog.Error("unable to get list of features", "error", err) diff --git a/lib/gds/client.go b/lib/gds/client.go index 8534fd78..b3d42302 100644 --- a/lib/gds/client.go +++ b/lib/gds/client.go @@ -20,17 +20,15 @@ import ( "log/slog" "cloud.google.com/go/datastore" - "github.com/GoogleChrome/webstatus.dev/lib/gen/jsonschema/web_platform_dx__web_features" - "github.com/GoogleChrome/webstatus.dev/lib/gen/openapi/backend" + "google.golang.org/api/iterator" ) -const featureDataKey = "FeatureDataTest" - type Client struct { *datastore.Client } -func NewWebFeatureClient(projectID string, database *string) (*Client, error) { +// NewDatastoreClient returns a Client for the Google Datastore service. +func NewDatastoreClient(projectID string, database *string) (*Client, error) { if projectID == "" { return nil, errors.New("projectID is empty") } @@ -53,28 +51,38 @@ func NewWebFeatureClient(projectID string, database *string) (*Client, error) { return &Client{client}, nil } -type FeatureData struct { - WebFeatureID string `datastore:"web_feature_id"` - Name string `datastore:"name"` - id int64 // The integer ID used in the datastore. +// Filterable modifies a query with a given filter. +type Filterable interface { + FilterQuery(*datastore.Query) *datastore.Query +} + +// entityClient is generic client that contains generic methods that can apply +// to any entity stored in datastore. +type entityClient[T any] struct { + *Client } -func (f FeatureData) ID() int64 { - return f.id +type Mergeable[T any] interface { + Merge(existing *T, new *T) *T } -func (c *Client) Upsert( +func (c *entityClient[T]) upsert( ctx context.Context, - webFeatureID string, - data web_platform_dx__web_features.FeatureData, -) error { + kind string, + data *T, + mergeable Mergeable[T], + filterables ...Filterable) error { // Begin a transaction. _, err := c.RunInTransaction(ctx, func(tx *datastore.Transaction) error { // Get the entity, if it exists. - var entity []FeatureData - query := datastore.NewQuery(featureDataKey).FilterField("web_feature_id", "=", webFeatureID).Transaction(tx) + var existingEntity []T + query := datastore.NewQuery(kind) + for _, filterable := range filterables { + query = filterable.FilterQuery(query) + } + query = query.Limit(1).Transaction(tx) - keys, err := c.GetAll(ctx, query, &entity) + keys, err := c.GetAll(ctx, query, &existingEntity) if err != nil && !errors.Is(err, datastore.ErrNoSuchEntity) { slog.Error("unable to check for existing entities", "error", err) @@ -82,24 +90,19 @@ func (c *Client) Upsert( } var key *datastore.Key - // If the entity exists, update it. + // If the entity exists, merge the two entities. if len(keys) > 0 { key = keys[0] - + data = mergeable.Merge(&existingEntity[0], data) } else { // If the entity does not exist, insert it. - key = datastore.IncompleteKey(featureDataKey, nil) + key = datastore.IncompleteKey(kind, nil) } - // nolint: exhaustruct // id does not exist yet - feature := &FeatureData{ - WebFeatureID: webFeatureID, - Name: data.Name, - } - _, err = tx.Put(key, feature) + _, err = tx.Put(key, data) if err != nil { // Handle any errors in an appropriate way, such as returning them. - slog.Error("unable to upsert metadata", "error", err) + slog.Error("unable to upsert entity", "error", err) return err } @@ -116,42 +119,63 @@ func (c *Client) Upsert( return nil } -func (c *Client) List(ctx context.Context) ([]backend.Feature, error) { - var featureData []*FeatureData - _, err := c.GetAll(ctx, datastore.NewQuery(featureDataKey), &featureData) - if err != nil { - return nil, err +func (c entityClient[T]) list( + ctx context.Context, + kind string, + pageToken *string, + filterables ...Filterable) ([]*T, *string, error) { + var data []*T + query := datastore.NewQuery(kind) + if pageToken != nil { + cursor, err := datastore.DecodeCursor(*pageToken) + if err != nil { + return nil, nil, err + } + query = query.Start(cursor) + } + for _, filterable := range filterables { + query = filterable.FilterQuery(query) } - ret := make([]backend.Feature, len(featureData)) - - // nolint: exhaustruct - // TODO. Will fix this lint error once the data is coming in. - for idx, val := range featureData { - ret[idx] = backend.Feature{ - FeatureId: val.WebFeatureID, - Name: val.Name, - Spec: nil, + it := c.Run(ctx, query) + for { + var entity T + _, err := it.Next(&entity) + if errors.Is(err, iterator.Done) { + cursor, err := it.Cursor() + if err != nil { + // TODO: Handle error. + return nil, nil, err + } + nextToken := cursor.String() + + return data, &nextToken, nil } + if err != nil { + return nil, nil, err + } + data = append(data, &entity) } - - return ret, nil } -func (c *Client) Get(ctx context.Context, webFeatureID string) (*backend.Feature, error) { - var featureData []*FeatureData - _, err := c.GetAll( - ctx, datastore.NewQuery(featureDataKey). - FilterField("web_feature_id", "=", webFeatureID).Limit(1), - &featureData) +var ErrEntityNotFound = errors.New("queried entity not found") + +func (c entityClient[T]) get(ctx context.Context, kind string, filterables ...Filterable) (*T, error) { + var data []*T + query := datastore.NewQuery(kind) + for _, filterable := range filterables { + query = filterable.FilterQuery(query) + } + query = query.Limit(1) + _, err := c.GetAll(ctx, query, &data) if err != nil { + slog.Error("failed to list data", "error", err, "kind", kind) + return nil, err } - // nolint: exhaustruct - // TODO. Will fix this lint error once the data is coming in. - return &backend.Feature{ - Name: featureData[0].WebFeatureID, - FeatureId: featureData[0].WebFeatureID, - Spec: nil, - }, nil + if len(data) < 1 { + return nil, ErrEntityNotFound + } + + return data[0], nil } diff --git a/lib/gds/client_test.go b/lib/gds/client_test.go index fb47dc0d..2b15412f 100644 --- a/lib/gds/client_test.go +++ b/lib/gds/client_test.go @@ -15,15 +15,17 @@ package gds import ( + "cmp" "context" + "errors" "fmt" "os" "path/filepath" - "slices" + "reflect" "testing" + "time" - "github.com/GoogleChrome/webstatus.dev/lib/gen/jsonschema/web_platform_dx__web_features" - "github.com/GoogleChrome/webstatus.dev/lib/gen/openapi/backend" + "cloud.google.com/go/datastore" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) @@ -60,7 +62,7 @@ func getTestDatabase(ctx context.Context, t *testing.T) (*Client, func()) { db := "" dbPtr := &db os.Setenv("DATASTORE_EMULATOR_HOST", fmt.Sprintf("localhost:%s", mappedPort.Port())) - dsClient, err := NewWebFeatureClient(testDatastoreProject, dbPtr) + dsClient, err := NewDatastoreClient(testDatastoreProject, dbPtr) if err != nil { if unsetErr := os.Unsetenv("DATASTORE_EMULATOR_HOST"); unsetErr != nil { t.Errorf("failed to unset env. %s", unsetErr.Error()) @@ -87,44 +89,202 @@ func getTestDatabase(ctx context.Context, t *testing.T) (*Client, func()) { } } -// nolint: exhaustruct // No need to use every option of 3rd party struct. -func TestUpsert(t *testing.T) { +const sampleKey = "SampleData" + +type TestSample struct { + Name string `datastore:"name"` + Value *int `datastore:"value"` + CreatedAt time.Time `datastore:"created_at"` +} + +type nameFilter struct { + name string +} + +func (f nameFilter) FilterQuery(query *datastore.Query) *datastore.Query { + return query.FilterField("name", "=", f.name) +} + +type sortSampleFilter struct { +} + +func (f sortSampleFilter) FilterQuery(query *datastore.Query) *datastore.Query { + return query.Order("-created_at") +} + +type limitSampleFilter struct { + size int +} + +func (f limitSampleFilter) FilterQuery(query *datastore.Query) *datastore.Query { + return query.Limit(f.size) +} + +// testSampleMerge implements Mergeable for TestSample. +type testSampleMerge struct{} + +func (m testSampleMerge) Merge(existing *TestSample, new *TestSample) *TestSample { + return &TestSample{ + Value: cmp.Or[*int](new.Value, existing.Value), + // The below fields cannot be overridden during a merge. + Name: existing.Name, + CreatedAt: existing.CreatedAt, + } +} + +func intPtr(in int) *int { + return &in +} + +// nolint: gochecknoglobals +var testSamples = []TestSample{ + { + Name: "a", + Value: intPtr(0), + CreatedAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + { + Name: "b", + Value: intPtr(1), + CreatedAt: time.Date(1999, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + { + Name: "c", + Value: intPtr(2), + CreatedAt: time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + { + Name: "d", + Value: intPtr(3), + CreatedAt: time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC), + }, +} + +func insertEntities( + ctx context.Context, + t *testing.T, + c entityClient[TestSample]) { + for i := range testSamples { + err := c.upsert(ctx, sampleKey, &testSamples[i], testSampleMerge{}, nameFilter{name: testSamples[i].Name}) + if err != nil { + t.Fatalf("failed to insert entities. %s", err.Error()) + } + } +} + +func TestEntityClientOperations(t *testing.T) { ctx := context.Background() client, cleanup := getTestDatabase(ctx, t) defer cleanup() - - // Part 1. Try to insert the first version - err := client.Upsert(ctx, "id-1", web_platform_dx__web_features.FeatureData{ - Name: "version-1-name", - }) + c := entityClient[TestSample]{client} + // Step 1. Make sure the entity is not there yet. + // Step 1a. Do Get + entity, err := c.get(ctx, sampleKey, nameFilter{name: "a"}) + if entity != nil { + t.Error("expected no entity") + } + if !errors.Is(err, ErrEntityNotFound) { + t.Error("expected ErrEntityNotFound") + } + // Step 1b. Do List + pageEmpty, nextPageToken, err := c.list(ctx, sampleKey, nil) if err != nil { - t.Errorf("failed to upsert %s", err.Error()) + t.Errorf("list query failed. %s", err.Error()) } - features, err := client.List(ctx) + if nextPageToken == nil { + t.Error("expected next page token") + } + if pageEmpty != nil { + t.Error("expected empty page") + } + // Step 2. Insert the entities + insertEntities(ctx, t, c) + // Step 3. Get the entity + entity, err = c.get(ctx, sampleKey, nameFilter{name: "a"}) if err != nil { - t.Errorf("failed to list %s", err.Error()) + t.Errorf("expected error %s", err.Error()) } - - expectedFeatures := []backend.Feature{{FeatureId: "id-1", Spec: nil, Name: "version-1-name"}} - if !slices.Equal[[]backend.Feature](features, expectedFeatures) { - t.Errorf("slices not equal actual [%v] expected [%v]", features, expectedFeatures) + if entity == nil { + t.Error("expected entity") + t.FailNow() } - - // Part 2. Upsert the second version - err = client.Upsert(ctx, "id-1", web_platform_dx__web_features.FeatureData{ - Name: "version-2-name", - }) + if !reflect.DeepEqual(*entity, TestSample{ + Name: "a", + Value: intPtr(0), + CreatedAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), + }) { + t.Errorf("values not equal. received %+v", *entity) + } + // Step 4. Upsert the entity + entity.Value = intPtr(200) + // CreatedAt should not update due to the Mergeable policy + entity.CreatedAt = time.Date(3000, time.March, 1, 0, 0, 0, 0, time.UTC) + err = c.upsert(ctx, sampleKey, entity, testSampleMerge{}, nameFilter{name: "a"}) if err != nil { - t.Errorf("failed to upsert again %s", err.Error()) + t.Errorf("upsert failed %s", err.Error()) } - - features, err = client.List(ctx) + // Step 5. Get the updated entity + entity, err = c.get(ctx, sampleKey, nameFilter{name: "a"}) if err != nil { - t.Errorf("failed to list %s", err.Error()) + t.Errorf("expected error %s", err.Error()) } - - expectedFeatures = []backend.Feature{{FeatureId: "id-1", Spec: nil, Name: "version-2-name"}} - if !slices.Equal[[]backend.Feature](features, expectedFeatures) { - t.Errorf("slices not equal actual [%v] expected [%v]", features, expectedFeatures) + if entity == nil { + t.Error("expected entity") + t.FailNow() + } + if !reflect.DeepEqual(*entity, TestSample{ + Name: "a", + Value: intPtr(200), + CreatedAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), + }) { + t.Errorf("values not equal. received %+v", *entity) + } + // Step 6. List the entities + // Step 6a. Get first page + filters := []Filterable{sortSampleFilter{}, limitSampleFilter{size: 2}} + pageOne, nextPageToken, err := c.list(ctx, sampleKey, nil, filters...) + if err != nil { + t.Errorf("page one query failed. %s", err.Error()) + } + if nextPageToken == nil { + t.Error("expected next page token") + } + expectedPageOne := []*TestSample{ + { + Name: "d", + Value: intPtr(3), + CreatedAt: time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + { + Name: "c", + Value: intPtr(2), + CreatedAt: time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + } + if !reflect.DeepEqual(pageOne, expectedPageOne) { + t.Error("values not equal") + } + // Step 6b. Get second page + pageTwo, nextPageToken, err := c.list(ctx, sampleKey, nextPageToken, filters...) + if err != nil { + t.Errorf("page two query failed. %s", err.Error()) + } + if nextPageToken == nil { + t.Error("expected next page token") + } + expectedPageTwo := []*TestSample{ + { + Name: "a", + Value: intPtr(200), + CreatedAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + { + Name: "b", + Value: intPtr(1), + CreatedAt: time.Date(1999, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + } + if !reflect.DeepEqual(pageTwo, expectedPageTwo) { + t.Error("values not equal") } } diff --git a/lib/gds/web_feature.go b/lib/gds/web_feature.go new file mode 100644 index 00000000..d5db517d --- /dev/null +++ b/lib/gds/web_feature.go @@ -0,0 +1,115 @@ +// 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 gds + +import ( + "cmp" + "context" + + "cloud.google.com/go/datastore" + "github.com/GoogleChrome/webstatus.dev/lib/gen/jsonschema/web_platform_dx__web_features" + "github.com/GoogleChrome/webstatus.dev/lib/gen/openapi/backend" +) + +const featureDataKey = "FeatureData" + +// FeatureData contains: +// - basic metadata about the web feature. +// - snapshot of latest metrics. +type FeatureData struct { + WebFeatureID *string `datastore:"web_feature_id"` + Name *string `datastore:"name"` +} + +// webFeaturesFilter implements Filterable to filter by web_feature_id. +// Compatible kinds: +// - featureDataKey. +type webFeaturesFilter struct { + webFeatureID string +} + +func (f webFeaturesFilter) FilterQuery(query *datastore.Query) *datastore.Query { + return query.FilterField("web_feature_id", "=", f.webFeatureID) +} + +// webFeatureMerge implements Mergeable for FeatureData. +type webFeatureMerge struct{} + +func (m webFeatureMerge) Merge(existing *FeatureData, new *FeatureData) *FeatureData { + return &FeatureData{ + Name: cmp.Or[*string](new.Name, existing.Name), + // The below fields cannot be overridden during a merge. + WebFeatureID: existing.WebFeatureID, + } +} + +// UpsertFeatureData inserts/updates data for the given web feature. +func (c *Client) UpsertFeatureData( + ctx context.Context, + webFeatureID string, + data web_platform_dx__web_features.FeatureData, +) error { + entityClient := entityClient[FeatureData]{c} + + return entityClient.upsert(ctx, + featureDataKey, + &FeatureData{ + WebFeatureID: &webFeatureID, + Name: &data.Name, + }, + webFeatureMerge{}, + webFeaturesFilter{ + webFeatureID: webFeatureID, + }, + ) +} + +// ListWebFeatureData lists web features data. +func (c *Client) ListWebFeatureData(ctx context.Context, pageToken *string) ([]backend.Feature, *string, error) { + entityClient := entityClient[FeatureData]{c} + featureData, nextPageToken, err := entityClient.list(ctx, featureDataKey, pageToken) + if err != nil { + return nil, nil, err + } + ret := make([]backend.Feature, len(featureData)) + for idx, val := range featureData { + // nolint: exhaustruct // TODO revisit once we adjust the ingestion data to incorporate the new fields. + ret[idx] = backend.Feature{ + FeatureId: *val.WebFeatureID, + Name: *val.Name, + Spec: nil, + } + } + + return ret, nextPageToken, nil +} + +// GetWebFeatureData atttempts to get data for a given web feature. +func (c *Client) GetWebFeatureData(ctx context.Context, webFeatureID string) (*backend.Feature, error) { + entityClient := entityClient[FeatureData]{c} + featureData, err := entityClient.get(ctx, featureDataKey, webFeaturesFilter{ + webFeatureID: webFeatureID, + }) + if err != nil { + return nil, err + } + + // nolint: exhaustruct // TODO revisit once we adjust the ingestion data to incorporate the new fields. + return &backend.Feature{ + Name: *featureData.Name, + FeatureId: *featureData.WebFeatureID, + Spec: nil, + }, nil +} diff --git a/lib/gds/web_feature_test.go b/lib/gds/web_feature_test.go new file mode 100644 index 00000000..b8d8e865 --- /dev/null +++ b/lib/gds/web_feature_test.go @@ -0,0 +1,66 @@ +// 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 gds + +import ( + "context" + "slices" + "testing" + + "github.com/GoogleChrome/webstatus.dev/lib/gen/jsonschema/web_platform_dx__web_features" + "github.com/GoogleChrome/webstatus.dev/lib/gen/openapi/backend" +) + +// nolint: exhaustruct // No need to use every option of 3rd party struct. +func TestFeatureDataOperations(t *testing.T) { + ctx := context.Background() + client, cleanup := getTestDatabase(ctx, t) + defer cleanup() + + // Part 1. Try to insert the first version + err := client.UpsertFeatureData(ctx, "id-1", web_platform_dx__web_features.FeatureData{ + Name: "version-1-name", + }) + if err != nil { + t.Errorf("failed to upsert %s", err.Error()) + } + features, _, err := client.ListWebFeatureData(ctx, nil) + if err != nil { + t.Errorf("failed to list %s", err.Error()) + } + + expectedFeatures := []backend.Feature{{FeatureId: "id-1", Spec: nil, Name: "version-1-name"}} + if !slices.Equal[[]backend.Feature](features, expectedFeatures) { + t.Errorf("slices not equal actual [%v] expected [%v]", features, expectedFeatures) + } + + // Part 2. Upsert the second version + err = client.UpsertFeatureData(ctx, "id-1", web_platform_dx__web_features.FeatureData{ + Name: "version-2-name", + }) + if err != nil { + t.Errorf("failed to upsert again %s", err.Error()) + } + + features, _, err = client.ListWebFeatureData(ctx, nil) + if err != nil { + t.Errorf("failed to list %s", err.Error()) + } + + expectedFeatures = []backend.Feature{{FeatureId: "id-1", Spec: nil, Name: "version-2-name"}} + if !slices.Equal[[]backend.Feature](features, expectedFeatures) { + t.Errorf("slices not equal actual [%v] expected [%v]", features, expectedFeatures) + } +} diff --git a/workflows/steps/services/web_feature_consumer/cmd/server/main.go b/workflows/steps/services/web_feature_consumer/cmd/server/main.go index 44a6b26b..8a8be6aa 100644 --- a/workflows/steps/services/web_feature_consumer/cmd/server/main.go +++ b/workflows/steps/services/web_feature_consumer/cmd/server/main.go @@ -49,7 +49,7 @@ func main() { if value, found := os.LookupEnv("DATASTORE_DATABASE"); found { datastoreDB = &value } - fs, err := gds.NewWebFeatureClient(os.Getenv("PROJECT_ID"), datastoreDB) + fs, err := gds.NewDatastoreClient(os.Getenv("PROJECT_ID"), datastoreDB) if err != nil { slog.Error("failed to create datastore client", "error", err.Error()) os.Exit(1) diff --git a/workflows/steps/services/web_feature_consumer/pkg/httpserver/server.go b/workflows/steps/services/web_feature_consumer/pkg/httpserver/server.go index 3e35b35e..0e058139 100644 --- a/workflows/steps/services/web_feature_consumer/pkg/httpserver/server.go +++ b/workflows/steps/services/web_feature_consumer/pkg/httpserver/server.go @@ -35,7 +35,10 @@ type ObjectGetter interface { } type WebFeatureMetadataStorer interface { - Upsert(ctx context.Context, webFeatureID string, featureData web_platform_dx__web_features.FeatureData) error + UpsertFeatureData( + ctx context.Context, + webFeatureID string, + featureData web_platform_dx__web_features.FeatureData) error } type Server struct { @@ -87,7 +90,7 @@ func (s *Server) PostV1WebFeatures( }, nil } - err = s.metadataStorer.Upsert(ctx, webFeatureKey, featureData) + err = s.metadataStorer.UpsertFeatureData(ctx, webFeatureKey, featureData) if err != nil { slog.Error("unable to store data", "error", err)