From f303c60f846d0b178d1162e4f70f56c463c980d9 Mon Sep 17 00:00:00 2001 From: James Scott Date: Sun, 28 Dec 2025 22:03:06 +0000 Subject: [PATCH] feat(api): update subscription triggers and frequencies Updates the OpenAPI definition, internal storage types, and worker tests to align with the V1 notification capabilities, specifically removing `DAILY` frequency (as it's not in scope for user selection yet) and standardizing on `IMMEDIATE`, `WEEKLY`, and `MONTHLY`. Changes: - **API**: - Removed `DAILY` frequency. - Added `IMMEDIATE`, `WEEKLY`, and `MONTHLY` frequencies. - Renamed and expanded subscription triggers (e.g. `feature_baseline_to_widely`). - **Storage**: - Updated `SavedSearchSubscription` struct to use typed `SubscriptionTrigger` and `JobFrequency` enums. - Updated Spanner adapters to convert between backend and storage types. - **Workers**: - Updated `EventProducer` logic to map `IMMEDIATE` jobs correctly. - Updated `Dispatcher` tests to use `IMMEDIATE` frequency instead of `DAILY`. - **Backend**: - Updated validation logic and tests to match the new enums. --- backend/pkg/httpserver/create_subscription.go | 11 +- .../httpserver/create_subscription_test.go | 52 +++---- .../httpserver/update_subscription_test.go | 28 ++-- lib/event/batchrefreshtrigger/v1/types.go | 3 - lib/event/featurediff/v1/types.go | 5 - lib/event/refreshsearchcommand/v1/types.go | 3 - .../searchconfigurationchanged/v1/types.go | 3 - .../batch_event_producer_test.go | 6 +- .../gcppubsubadapters/event_producer_test.go | 8 +- lib/gcpspanner/saved_search_state.go | 1 + lib/gcpspanner/saved_search_subscription.go | 37 +++-- .../saved_search_subscription_test.go | 52 ++++--- lib/gcpspanner/spanneradapters/backend.go | 104 +++++++++++--- .../spanneradapters/backend_test.go | 130 +++++++++--------- .../spanneradapters/event_producer.go | 7 +- .../spanneradapters/event_producer_test.go | 6 +- lib/workertypes/types.go | 1 - openapi/backend/openapi.yaml | 24 ++-- .../pkg/producer/batch_handler_test.go | 4 +- 19 files changed, 288 insertions(+), 197 deletions(-) diff --git a/backend/pkg/httpserver/create_subscription.go b/backend/pkg/httpserver/create_subscription.go index 502d229ec..4a8633896 100644 --- a/backend/pkg/httpserver/create_subscription.go +++ b/backend/pkg/httpserver/create_subscription.go @@ -29,9 +29,10 @@ import ( // The exhaustive linter is configured to check that this map is complete. func getAllSubscriptionTriggersSet() map[backend.SubscriptionTriggerWritable]any { return map[backend.SubscriptionTriggerWritable]any{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete: nil, - backend.SubscriptionTriggerFeatureBaselineLimitedToNewly: nil, - backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited: nil, + backend.SubscriptionTriggerFeatureBaselineToNewly: nil, + backend.SubscriptionTriggerFeatureBaselineToWidely: nil, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete: nil, + backend.SubscriptionTriggerFeatureBaselineRegressionToLimited: nil, } } @@ -76,7 +77,9 @@ func validateSubscriptionTrigger(trigger *[]backend.SubscriptionTriggerWritable, // The exhaustive linter is configured to check that this map is complete. func getAllSubscriptionFrequenciesSet() map[backend.SubscriptionFrequency]any { return map[backend.SubscriptionFrequency]any{ - backend.SubscriptionFrequencyDaily: nil, + backend.SubscriptionFrequencyImmediate: nil, + backend.SubscriptionFrequencyWeekly: nil, + backend.SubscriptionFrequencyMonthly: nil, } } diff --git a/backend/pkg/httpserver/create_subscription_test.go b/backend/pkg/httpserver/create_subscription_test.go index 083bd3e8c..dc38321eb 100644 --- a/backend/pkg/httpserver/create_subscription_test.go +++ b/backend/pkg/httpserver/create_subscription_test.go @@ -51,8 +51,8 @@ func TestCreateSubscription(t *testing.T) { ChannelId: "channel-id", SavedSearchId: "search-id", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: "daily", + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: "immediate", }, output: &backend.SubscriptionResponse{ Id: "sub-id", @@ -61,11 +61,11 @@ func TestCreateSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, - Frequency: "daily", + Frequency: "immediate", CreatedAt: now, UpdatedAt: now, }, @@ -79,16 +79,16 @@ func TestCreateSubscription(t *testing.T) { strings.NewReader(`{ "channel_id": "channel-id", "saved_search_id": "search-id", - "triggers": ["feature_any_browser_implementation_complete"], - "frequency": "daily" + "triggers": ["feature_browser_implementation_any_complete"], + "frequency": "immediate" }`), ), expectedResponse: testJSONResponse(http.StatusCreated, `{ "id":"sub-id", "channel_id":"channel-id", "saved_search_id":"search-id", - "triggers": [{"value":"feature_any_browser_implementation_complete"}], - "frequency":"daily", + "triggers": [{"value":"feature_browser_implementation_any_complete"}], + "frequency":"immediate", "created_at":"`+now.Format(time.RFC3339Nano)+`", "updated_at":"`+now.Format(time.RFC3339Nano)+`" }`), @@ -103,8 +103,8 @@ func TestCreateSubscription(t *testing.T) { "/v1/users/me/subscriptions", strings.NewReader(`{ "saved_search_id": "search-id", - "triggers": ["feature_any_browser_implementation_complete"], - "frequency": "daily" + "triggers": ["feature_browser_implementation_any_complete"], + "frequency": "immediate" }`), ), expectedResponse: testJSONResponse(http.StatusBadRequest, ` @@ -124,8 +124,8 @@ func TestCreateSubscription(t *testing.T) { ChannelId: "channel-id", SavedSearchId: "search-id", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: "daily", + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: "immediate", }, output: nil, err: backendtypes.ErrUserNotAuthorizedForAction, @@ -138,8 +138,8 @@ func TestCreateSubscription(t *testing.T) { strings.NewReader(`{ "channel_id": "channel-id", "saved_search_id": "search-id", - "triggers": ["feature_any_browser_implementation_complete"], - "frequency": "daily" + "triggers": ["feature_browser_implementation_any_complete"], + "frequency": "immediate" }`)), expectedResponse: testJSONResponse(http.StatusForbidden, `{ "code":403, @@ -154,8 +154,8 @@ func TestCreateSubscription(t *testing.T) { ChannelId: "channel-id", SavedSearchId: "search-id", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: "daily", + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: "immediate", }, output: nil, err: fmt.Errorf("database error"), @@ -168,8 +168,8 @@ func TestCreateSubscription(t *testing.T) { strings.NewReader(`{ "channel_id": "channel-id", "saved_search_id": "search-id", - "triggers": ["feature_any_browser_implementation_complete"], - "frequency": "daily" + "triggers": ["feature_browser_implementation_any_complete"], + "frequency": "immediate" }`)), expectedResponse: testJSONResponse(http.StatusInternalServerError, `{ "code":500, @@ -214,8 +214,8 @@ func TestValidateSubscriptionCreation(t *testing.T) { ChannelId: "channel-id", SavedSearchId: "search-id", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, want: nil, }, @@ -225,8 +225,8 @@ func TestValidateSubscriptionCreation(t *testing.T) { ChannelId: "", SavedSearchId: "searchid", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, want: &fieldValidationErrors{ fieldErrorMap: map[string]string{ @@ -240,8 +240,8 @@ func TestValidateSubscriptionCreation(t *testing.T) { ChannelId: "channelid", SavedSearchId: "", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, want: &fieldValidationErrors{ fieldErrorMap: map[string]string{ @@ -256,7 +256,7 @@ func TestValidateSubscriptionCreation(t *testing.T) { SavedSearchId: "searchid", Triggers: []backend.SubscriptionTriggerWritable{ "invalid_trigger"}, - Frequency: backend.SubscriptionFrequencyDaily, + Frequency: backend.SubscriptionFrequencyImmediate, }, want: &fieldValidationErrors{ fieldErrorMap: map[string]string{ @@ -270,7 +270,7 @@ func TestValidateSubscriptionCreation(t *testing.T) { ChannelId: "channelid", SavedSearchId: "searchid", Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, Frequency: "invalid_frequency", }, want: &fieldValidationErrors{ diff --git a/backend/pkg/httpserver/update_subscription_test.go b/backend/pkg/httpserver/update_subscription_test.go index f7d0a3b14..4c6e16c9b 100644 --- a/backend/pkg/httpserver/update_subscription_test.go +++ b/backend/pkg/httpserver/update_subscription_test.go @@ -50,7 +50,7 @@ func TestUpdateSubscription(t *testing.T) { expectedSubscriptionID: "sub-id", expectedUpdateRequest: backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ backend.UpdateSubscriptionRequestMaskTriggers}, Frequency: nil, @@ -62,7 +62,7 @@ func TestUpdateSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, @@ -79,7 +79,7 @@ func TestUpdateSubscription(t *testing.T) { strings.NewReader(` { "triggers": - ["feature_any_browser_implementation_complete"], + ["feature_browser_implementation_any_complete"], "update_mask": ["triggers"] }`)), expectedResponse: testJSONResponse(http.StatusOK, @@ -87,7 +87,7 @@ func TestUpdateSubscription(t *testing.T) { "id":"sub-id", "channel_id":"channel-id", "saved_search_id":"search-id", - "triggers": [{"value":"feature_any_browser_implementation_complete"}], + "triggers": [{"value":"feature_browser_implementation_any_complete"}], "frequency":"daily", "created_at":"`+now.Format(time.RFC3339Nano)+`", "updated_at":"`+now.Format(time.RFC3339Nano)+`" @@ -101,7 +101,7 @@ func TestUpdateSubscription(t *testing.T) { expectedSubscriptionID: "sub-id", expectedUpdateRequest: backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete, }, Frequency: nil, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ @@ -116,7 +116,7 @@ func TestUpdateSubscription(t *testing.T) { "/v1/users/me/subscriptions/sub-id", strings.NewReader(` { - "triggers": ["feature_any_browser_implementation_complete"], + "triggers": ["feature_browser_implementation_any_complete"], "update_mask": ["triggers"] }`)), expectedResponse: testJSONResponse(http.StatusNotFound, ` @@ -134,7 +134,7 @@ func TestUpdateSubscription(t *testing.T) { "/v1/users/me/subscriptions/sub-id", strings.NewReader(` { - "triggers": ["feature_any_browser_implementation_complete"], + "triggers": ["feature_browser_implementation_any_complete"], "update_mask": ["invalid_field"] }`)), expectedResponse: testJSONResponse(http.StatusBadRequest, ` @@ -155,7 +155,7 @@ func TestUpdateSubscription(t *testing.T) { expectedSubscriptionID: "sub-id", expectedUpdateRequest: backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ backend.UpdateSubscriptionRequestMaskTriggers}, Frequency: nil, @@ -169,7 +169,7 @@ func TestUpdateSubscription(t *testing.T) { "/v1/users/me/subscriptions/sub-id", strings.NewReader(` { - "triggers": ["feature_any_browser_implementation_complete"], + "triggers": ["feature_browser_implementation_any_complete"], "update_mask": ["triggers"] }`)), expectedResponse: testJSONResponse(http.StatusForbidden, ` @@ -186,7 +186,7 @@ func TestUpdateSubscription(t *testing.T) { expectedSubscriptionID: "sub-id", expectedUpdateRequest: backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ backend.UpdateSubscriptionRequestMaskTriggers}, Frequency: nil, @@ -200,7 +200,7 @@ func TestUpdateSubscription(t *testing.T) { "/v1/users/me/subscriptions/sub-id", strings.NewReader(` { - "triggers": ["feature_any_browser_implementation_complete"], + "triggers": ["feature_browser_implementation_any_complete"], "update_mask": ["triggers"] }`)), expectedResponse: testJSONResponse(http.StatusInternalServerError, ` @@ -250,7 +250,7 @@ func TestValidateSubscriptionUpdate(t *testing.T) { name: "valid update", input: &backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ backend.UpdateSubscriptionRequestMaskTriggers}, Frequency: nil, @@ -261,7 +261,7 @@ func TestValidateSubscriptionUpdate(t *testing.T) { name: "invalid update mask", input: &backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: []backend.UpdateSubscriptionRequestUpdateMask{ "invalid_field"}, Frequency: nil, @@ -290,7 +290,7 @@ func TestValidateSubscriptionUpdate(t *testing.T) { name: "nil update mask", input: &backend.UpdateSubscriptionRequest{ Triggers: &[]backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, UpdateMask: nil, Frequency: nil, }, diff --git a/lib/event/batchrefreshtrigger/v1/types.go b/lib/event/batchrefreshtrigger/v1/types.go index aba9f421d..f943a46bb 100644 --- a/lib/event/batchrefreshtrigger/v1/types.go +++ b/lib/event/batchrefreshtrigger/v1/types.go @@ -22,7 +22,6 @@ type JobFrequency string const ( FrequencyUnknown JobFrequency = "UNKNOWN" FrequencyImmediate JobFrequency = "IMMEDIATE" - FrequencyDaily JobFrequency = "DAILY" FrequencyWeekly JobFrequency = "WEEKLY" FrequencyMonthly JobFrequency = "MONTHLY" ) @@ -31,8 +30,6 @@ func (f JobFrequency) ToWorkerTypeJobFrequency() workertypes.JobFrequency { switch f { case FrequencyImmediate: return workertypes.FrequencyImmediate - case FrequencyDaily: - return workertypes.FrequencyDaily case FrequencyWeekly: return workertypes.FrequencyWeekly case FrequencyMonthly: diff --git a/lib/event/featurediff/v1/types.go b/lib/event/featurediff/v1/types.go index b84e3ecc2..4df9d7633 100644 --- a/lib/event/featurediff/v1/types.go +++ b/lib/event/featurediff/v1/types.go @@ -25,7 +25,6 @@ type JobFrequency string const ( FrequencyUnknown JobFrequency = "UNKNOWN" FrequencyImmediate JobFrequency = "IMMEDIATE" - FrequencyDaily JobFrequency = "DAILY" FrequencyWeekly JobFrequency = "WEEKLY" FrequencyMonthly JobFrequency = "MONTHLY" ) @@ -73,8 +72,6 @@ func (f JobFrequency) ToWorkertypes() workertypes.JobFrequency { switch f { case FrequencyImmediate: return workertypes.FrequencyImmediate - case FrequencyDaily: - return workertypes.FrequencyDaily case FrequencyWeekly: return workertypes.FrequencyWeekly case FrequencyMonthly: @@ -90,8 +87,6 @@ func ToJobFrequency(freq workertypes.JobFrequency) JobFrequency { switch freq { case workertypes.FrequencyImmediate: return FrequencyImmediate - case workertypes.FrequencyDaily: - return FrequencyDaily case workertypes.FrequencyWeekly: return FrequencyWeekly case workertypes.FrequencyMonthly: diff --git a/lib/event/refreshsearchcommand/v1/types.go b/lib/event/refreshsearchcommand/v1/types.go index 1a23ceeb4..5d03b3686 100644 --- a/lib/event/refreshsearchcommand/v1/types.go +++ b/lib/event/refreshsearchcommand/v1/types.go @@ -26,7 +26,6 @@ type JobFrequency string const ( FrequencyUnknown JobFrequency = "UNKNOWN" FrequencyImmediate JobFrequency = "IMMEDIATE" - FrequencyDaily JobFrequency = "DAILY" FrequencyWeekly JobFrequency = "WEEKLY" FrequencyMonthly JobFrequency = "MONTHLY" ) @@ -35,8 +34,6 @@ func (f JobFrequency) ToWorkerTypeJobFrequency() workertypes.JobFrequency { switch f { case FrequencyImmediate: return workertypes.FrequencyImmediate - case FrequencyDaily: - return workertypes.FrequencyDaily case FrequencyWeekly: return workertypes.FrequencyWeekly case FrequencyMonthly: diff --git a/lib/event/searchconfigurationchanged/v1/types.go b/lib/event/searchconfigurationchanged/v1/types.go index 24849399b..b26a55134 100644 --- a/lib/event/searchconfigurationchanged/v1/types.go +++ b/lib/event/searchconfigurationchanged/v1/types.go @@ -26,7 +26,6 @@ type JobFrequency string const ( FrequencyUnknown JobFrequency = "UNKNOWN" FrequencyImmediate JobFrequency = "IMMEDIATE" - FrequencyDaily JobFrequency = "DAILY" FrequencyWeekly JobFrequency = "WEEKLY" FrequencyMonthly JobFrequency = "MONTHLY" ) @@ -35,8 +34,6 @@ func (f JobFrequency) ToWorkerTypeJobFrequency() workertypes.JobFrequency { switch f { case FrequencyImmediate: return workertypes.FrequencyImmediate - case FrequencyDaily: - return workertypes.FrequencyDaily case FrequencyWeekly: return workertypes.FrequencyWeekly case FrequencyMonthly: diff --git a/lib/gcppubsub/gcppubsubadapters/batch_event_producer_test.go b/lib/gcppubsub/gcppubsubadapters/batch_event_producer_test.go index 5d5fb4e98..b357ec6c0 100644 --- a/lib/gcppubsub/gcppubsubadapters/batch_event_producer_test.go +++ b/lib/gcppubsub/gcppubsubadapters/batch_event_producer_test.go @@ -38,7 +38,7 @@ func TestBatchFanOutPublisherAdapter_PublishRefreshCommand(t *testing.T) { cmd: workertypes.RefreshSearchCommand{ SearchID: "search-123", Query: "query=abc", - Frequency: workertypes.FrequencyDaily, + Frequency: workertypes.FrequencyImmediate, Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), }, publishErr: nil, @@ -49,7 +49,7 @@ func TestBatchFanOutPublisherAdapter_PublishRefreshCommand(t *testing.T) { "data": { "search_id": "search-123", "query": "query=abc", - "frequency": "DAILY", + "frequency": "IMMEDIATE", "timestamp": "2025-01-01T00:00:00Z" } }`, @@ -59,7 +59,7 @@ func TestBatchFanOutPublisherAdapter_PublishRefreshCommand(t *testing.T) { cmd: workertypes.RefreshSearchCommand{ SearchID: "search-123", Query: "query=abc", - Frequency: workertypes.FrequencyDaily, + Frequency: workertypes.FrequencyImmediate, Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), }, publishErr: errors.New("pubsub error"), diff --git a/lib/gcppubsub/gcppubsubadapters/event_producer_test.go b/lib/gcppubsub/gcppubsubadapters/event_producer_test.go index f3264d6e0..72bc5e33a 100644 --- a/lib/gcppubsub/gcppubsubadapters/event_producer_test.go +++ b/lib/gcppubsub/gcppubsubadapters/event_producer_test.go @@ -181,7 +181,7 @@ func TestSubscribe_RoutesRefreshSearchCommand(t *testing.T) { refreshCmd := refreshv1.RefreshSearchCommand{ SearchID: "s1", Query: "q1", - Frequency: "DAILY", + Frequency: "IMMEDIATE", Timestamp: time.Time{}, } ceWrapper := map[string]interface{}{ @@ -202,7 +202,7 @@ func TestSubscribe_RoutesRefreshSearchCommand(t *testing.T) { expectedCall := searchCall{ SearchID: "s1", Query: "q1", - Frequency: workertypes.FrequencyDaily, + Frequency: workertypes.FrequencyImmediate, TriggerID: "msg-1", } @@ -293,7 +293,7 @@ func TestPublisher_Publish(t *testing.T) { EventID: "evt-1", SearchID: "search-1", Query: "query-1", - Frequency: "DAILY", + Frequency: workertypes.FrequencyImmediate, Reasons: []workertypes.Reason{workertypes.ReasonDataUpdated}, Summary: []byte(`{"added": 1}`), StateID: "state-id-1", @@ -332,7 +332,7 @@ func TestPublisher_Publish(t *testing.T) { "diff_blob_path": "gs://bucket/diff-blob", "reasons": []interface{}{"DATA_UPDATED"}, "generated_at": now.Format(time.RFC3339), - "frequency": "DAILY", + "frequency": "IMMEDIATE", }, } diff --git a/lib/gcpspanner/saved_search_state.go b/lib/gcpspanner/saved_search_state.go index d2ac701bf..279134abc 100644 --- a/lib/gcpspanner/saved_search_state.go +++ b/lib/gcpspanner/saved_search_state.go @@ -37,6 +37,7 @@ const ( SavedSearchSnapshotTypeImmediate SavedSearchSnapshotType = "IMMEDIATE" SavedSearchSnapshotTypeWeekly SavedSearchSnapshotType = "WEEKLY" SavedSearchSnapshotTypeMonthly SavedSearchSnapshotType = "MONTHLY" + SavedSearchSnapshotTypeUnknown SavedSearchSnapshotType = "UNKNOWN" ) type SavedSearchState struct { diff --git a/lib/gcpspanner/saved_search_subscription.go b/lib/gcpspanner/saved_search_subscription.go index 718474ee6..13ab5c2b5 100644 --- a/lib/gcpspanner/saved_search_subscription.go +++ b/lib/gcpspanner/saved_search_subscription.go @@ -29,30 +29,41 @@ const savedSearchSubscriptionTable = "SavedSearchSubscriptions" // SavedSearchSubscription represents a row in the SavedSearchSubscription table. type SavedSearchSubscription struct { - ID string `spanner:"ID"` - ChannelID string `spanner:"ChannelID"` - SavedSearchID string `spanner:"SavedSearchID"` - Triggers []string `spanner:"Triggers"` - Frequency string `spanner:"Frequency"` - CreatedAt time.Time `spanner:"CreatedAt"` - UpdatedAt time.Time `spanner:"UpdatedAt"` + ID string `spanner:"ID"` + ChannelID string `spanner:"ChannelID"` + SavedSearchID string `spanner:"SavedSearchID"` + Triggers []SubscriptionTrigger `spanner:"Triggers"` + Frequency SavedSearchSnapshotType `spanner:"Frequency"` + CreatedAt time.Time `spanner:"CreatedAt"` + UpdatedAt time.Time `spanner:"UpdatedAt"` } +type SubscriptionTrigger string + +const ( + SubscriptionTriggerBrowserImplementationAnyComplete SubscriptionTrigger = "feature.browser_implementation." + + "any_complete" + SubscriptionTriggerFeatureBaselinePromoteToNewly SubscriptionTrigger = "feature.baseline.promote_to_newly" + SubscriptionTriggerFeatureBaselinePromoteToWidely SubscriptionTrigger = "feature.baseline.promote_to_widely" + SubscriptionTriggerFeatureBaselineRegressionToLimited SubscriptionTrigger = "feature.baseline.regression_to_limited" + SubscriptionTriggerUnknown SubscriptionTrigger = "unknown" +) + // CreateSavedSearchSubscriptionRequest is the request to create a subscription. type CreateSavedSearchSubscriptionRequest struct { UserID string ChannelID string SavedSearchID string - Triggers []string - Frequency string + Triggers []SubscriptionTrigger + Frequency SavedSearchSnapshotType } // UpdateSavedSearchSubscriptionRequest is a request to update a saved search subscription. type UpdateSavedSearchSubscriptionRequest struct { ID string UserID string - Triggers OptionallySet[[]string] - Frequency OptionallySet[string] + Triggers OptionallySet[[]SubscriptionTrigger] + Frequency OptionallySet[SavedSearchSnapshotType] } // ListSavedSearchSubscriptionsRequest is a request to list saved search subscriptions. @@ -318,7 +329,7 @@ type readAllActivePushSubscriptionsMapper struct { type activePushSubscriptionKey struct { SavedSearchID string - Frequency string + Frequency SavedSearchSnapshotType } func (m readAllActivePushSubscriptionsMapper) SelectAllByKeys(key activePushSubscriptionKey) spanner.Statement { @@ -354,7 +365,7 @@ func (m readAllActivePushSubscriptionsMapper) SelectAllByKeys(key activePushSubs func (c *Client) FindAllActivePushSubscriptions( ctx context.Context, savedSearchID string, - frequency string, + frequency SavedSearchSnapshotType, ) ([]SubscriberDestination, error) { return newAllByKeysEntityReader[ readAllActivePushSubscriptionsMapper, diff --git a/lib/gcpspanner/saved_search_subscription_test.go b/lib/gcpspanner/saved_search_subscription_test.go index 74570ecd9..6d687f7ee 100644 --- a/lib/gcpspanner/saved_search_subscription_test.go +++ b/lib/gcpspanner/saved_search_subscription_test.go @@ -58,8 +58,8 @@ func TestCreateAndGetSavedSearchSubscription(t *testing.T) { UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"spec.links"}, - Frequency: "DAILY", + Triggers: []SubscriptionTrigger{SubscriptionTriggerFeatureBaselineRegressionToLimited}, + Frequency: SavedSearchSnapshotTypeImmediate, } subIDPtr, err := spannerClient.CreateSavedSearchSubscription(ctx, createReq) if err != nil { @@ -128,8 +128,8 @@ func TestGetSavedSearchSubscriptionFailsForWrongUser(t *testing.T) { UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"baseline.status"}, - Frequency: "IMMEDIATE", + Triggers: []SubscriptionTrigger{SubscriptionTriggerFeatureBaselineRegressionToLimited}, + Frequency: SavedSearchSnapshotTypeImmediate, } subToUpdateIDPtr, err := spannerClient.CreateSavedSearchSubscription(ctx, baseCreateReq) @@ -178,8 +178,8 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"baseline.status"}, - Frequency: "IMMEDIATE", + Triggers: []SubscriptionTrigger{SubscriptionTriggerFeatureBaselinePromoteToNewly}, + Frequency: SavedSearchSnapshotTypeImmediate, } subToUpdateIDPtr, err := spannerClient.CreateSavedSearchSubscription(ctx, baseCreateReq) @@ -189,10 +189,11 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { subToUpdateID := *subToUpdateIDPtr updateReq := UpdateSavedSearchSubscriptionRequest{ - ID: subToUpdateID, - UserID: userID, - Triggers: OptionallySet[[]string]{Value: []string{"developer_signals.upvotes"}, IsSet: true}, - Frequency: OptionallySet[string]{Value: "WEEKLY_DIGEST", IsSet: true}, + ID: subToUpdateID, + UserID: userID, + Triggers: OptionallySet[[]SubscriptionTrigger]{Value: []SubscriptionTrigger{ + SubscriptionTriggerBrowserImplementationAnyComplete}, IsSet: true}, + Frequency: OptionallySet[SavedSearchSnapshotType]{Value: SavedSearchSnapshotTypeWeekly, IsSet: true}, } err = spannerClient.UpdateSavedSearchSubscription(ctx, updateReq) if err != nil { @@ -203,9 +204,13 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { if err != nil { t.Fatalf("GetSavedSearchSubscription after update failed: %v", err) } - if retrieved.Frequency != "WEEKLY_DIGEST" { + if retrieved.Frequency != SavedSearchSnapshotTypeWeekly { t.Errorf("expected updated frequency, got %s", retrieved.Frequency) } + expectedTriggers := []SubscriptionTrigger{SubscriptionTriggerBrowserImplementationAnyComplete} + if diff := cmp.Diff(expectedTriggers, retrieved.Triggers); diff != "" { + t.Errorf("updated triggers mismatch (-want +got):\n%s", diff) + } } func TestDeleteSavedSearchSubscription(t *testing.T) { @@ -242,8 +247,8 @@ func TestDeleteSavedSearchSubscription(t *testing.T) { UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"baseline.status"}, - Frequency: "IMMEDIATE", + Triggers: []SubscriptionTrigger{SubscriptionTriggerFeatureBaselinePromoteToNewly}, + Frequency: SavedSearchSnapshotTypeImmediate, } subToDeleteIDPtr, err := spannerClient.CreateSavedSearchSubscription(ctx, baseCreateReq) @@ -297,8 +302,8 @@ func TestListSavedSearchSubscriptions(t *testing.T) { UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"baseline.status"}, - Frequency: "IMMEDIATE", + Triggers: []SubscriptionTrigger{SubscriptionTriggerFeatureBaselinePromoteToNewly}, + Frequency: SavedSearchSnapshotTypeImmediate, } // Create a few subscriptions to list @@ -410,7 +415,8 @@ func TestFindAllActivePushSubscriptions(t *testing.T) { // Subscription 1: Correct, on active EMAIL channel _, err = spannerClient.CreateSavedSearchSubscription(ctx, CreateSavedSearchSubscriptionRequest{ - UserID: userID, ChannelID: emailChannelID, SavedSearchID: savedSearchID, Frequency: "IMMEDIATE", Triggers: nil, + UserID: userID, ChannelID: emailChannelID, SavedSearchID: savedSearchID, + Frequency: SavedSearchSnapshotTypeImmediate, Triggers: nil, }) if err != nil { t.Fatalf("failed to create sub 1: %v", err) @@ -419,7 +425,8 @@ func TestFindAllActivePushSubscriptions(t *testing.T) { // Subscription 2: Correct, on active WEBHOOK channel // TODO: Enable webhook channel tests once webhooks are supported. // _, err = spannerClient.CreateSavedSearchSubscription(ctx, CreateSavedSearchSubscriptionRequest{ - // UserID: userID, ChannelID: webhookChannelID, SavedSearchID: savedSearchID, Frequency: "IMMEDIATE", + // UserID: userID, ChannelID: webhookChannelID, SavedSearchID: savedSearchID, + // Frequency: SavedSearchSnapshotTypeImmediate, // }) // if err != nil { // t.Fatalf("failed to create sub 2: %v", err) @@ -435,7 +442,8 @@ func TestFindAllActivePushSubscriptions(t *testing.T) { // Subscription 4: Non-push channel (RSS) _, err = spannerClient.CreateSavedSearchSubscription(ctx, CreateSavedSearchSubscriptionRequest{ - UserID: userID, ChannelID: rssChannelID, SavedSearchID: savedSearchID, Frequency: "IMMEDIATE", Triggers: nil, + UserID: userID, ChannelID: rssChannelID, SavedSearchID: savedSearchID, + Frequency: SavedSearchSnapshotTypeImmediate, Triggers: nil, }) if err != nil { t.Fatalf("failed to create sub 4: %v", err) @@ -443,15 +451,17 @@ func TestFindAllActivePushSubscriptions(t *testing.T) { // Subscription 5: Disabled channel _, err = spannerClient.CreateSavedSearchSubscription(ctx, CreateSavedSearchSubscriptionRequest{ - UserID: userID, ChannelID: disabledChannelID, SavedSearchID: savedSearchID, Frequency: "IMMEDIATE", - Triggers: nil, + UserID: userID, ChannelID: disabledChannelID, SavedSearchID: savedSearchID, + Frequency: SavedSearchSnapshotTypeImmediate, + Triggers: nil, }) if err != nil { t.Fatalf("failed to create sub 5: %v", err) } // Find subscribers - subscribers, err := spannerClient.FindAllActivePushSubscriptions(ctx, savedSearchID, "IMMEDIATE") + subscribers, err := spannerClient.FindAllActivePushSubscriptions(ctx, savedSearchID, + SavedSearchSnapshotTypeImmediate) if err != nil { t.Fatalf("FindAllActivePushSubscriptions failed: %v", err) } diff --git a/lib/gcpspanner/spanneradapters/backend.go b/lib/gcpspanner/spanneradapters/backend.go index e3c2841c3..8ca58c377 100644 --- a/lib/gcpspanner/spanneradapters/backend.go +++ b/lib/gcpspanner/spanneradapters/backend.go @@ -1332,29 +1332,49 @@ func (s *Backend) GetIDFromFeatureKey( return id, nil } -func backendTriggersToSpannerTriggers(backendTriggers []backend.SubscriptionTriggerWritable) []string { - triggers := make([]string, 0, len(backendTriggers)) +func backendTriggersToSpannerTriggers( + backendTriggers []backend.SubscriptionTriggerWritable) []gcpspanner.SubscriptionTrigger { + triggers := make([]gcpspanner.SubscriptionTrigger, 0, len(backendTriggers)) for _, trigger := range backendTriggers { - triggers = append(triggers, string(trigger)) + triggers = append(triggers, toSpannerSubscriptionTrigger(trigger)) } return triggers } -func spannerTriggersToBackendTriggers(spannerTriggers []string) []backend.SubscriptionTriggerResponseItem { +func spannerTriggersToBackendTriggers( + spannerTriggers []gcpspanner.SubscriptionTrigger) []backend.SubscriptionTriggerResponseItem { triggers := make([]backend.SubscriptionTriggerResponseItem, 0, len(spannerTriggers)) for _, trigger := range spannerTriggers { - input := backend.SubscriptionTriggerWritable(trigger) - switch input { - case backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete, - backend.SubscriptionTriggerFeatureBaselineLimitedToNewly, - backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited: + switch trigger { + case gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete: triggers = append(triggers, backend.SubscriptionTriggerResponseItem{ - Value: backendtypes.AttemptToStoreSubscriptionTrigger(input), + Value: backendtypes.AttemptToStoreSubscriptionTrigger( + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }) + case gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly: + triggers = append(triggers, backend.SubscriptionTriggerResponseItem{ + Value: backendtypes.AttemptToStoreSubscriptionTrigger( + backend.SubscriptionTriggerFeatureBaselineToNewly), + RawValue: nil, + }) + case gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToWidely: + triggers = append(triggers, backend.SubscriptionTriggerResponseItem{ + Value: backendtypes.AttemptToStoreSubscriptionTrigger( + backend.SubscriptionTriggerFeatureBaselineToWidely), + RawValue: nil, + }) + case gcpspanner.SubscriptionTriggerFeatureBaselineRegressionToLimited: + triggers = append(triggers, backend.SubscriptionTriggerResponseItem{ + Value: backendtypes.AttemptToStoreSubscriptionTrigger( + backend.SubscriptionTriggerFeatureBaselineRegressionToLimited), + RawValue: nil, + }) + case gcpspanner.SubscriptionTriggerUnknown: + fallthrough default: - value := trigger + value := string(trigger) triggers = append(triggers, backend.SubscriptionTriggerResponseItem{ Value: backendtypes.AttemptToStoreSubscriptionTriggerUnknown(), RawValue: &value, @@ -1365,14 +1385,63 @@ func spannerTriggersToBackendTriggers(spannerTriggers []string) []backend.Subscr return triggers } +func toBackendSubscriptionFrequency(freq gcpspanner.SavedSearchSnapshotType) backend.SubscriptionFrequency { + switch freq { + case gcpspanner.SavedSearchSnapshotTypeImmediate: + return backend.SubscriptionFrequencyImmediate + case gcpspanner.SavedSearchSnapshotTypeWeekly: + return backend.SubscriptionFrequencyWeekly + case gcpspanner.SavedSearchSnapshotTypeMonthly: + return backend.SubscriptionFrequencyMonthly + case gcpspanner.SavedSearchSnapshotTypeUnknown: + break + } + + slog.WarnContext(context.TODO(), "unknown subscription frequency from spanner", "frequency", freq) + + // Should not reach here normally. The database should not have unknown frequencies. + return backend.SubscriptionFrequencyImmediate +} + +func toSpannerSubscriptionFrequency(freq backend.SubscriptionFrequency) gcpspanner.SavedSearchSnapshotType { + switch freq { + case backend.SubscriptionFrequencyImmediate: + return gcpspanner.SavedSearchSnapshotTypeImmediate + case backend.SubscriptionFrequencyWeekly: + return gcpspanner.SavedSearchSnapshotTypeWeekly + case backend.SubscriptionFrequencyMonthly: + return gcpspanner.SavedSearchSnapshotTypeMonthly + } + + // Should not reach here normally. The http layer already checks for valid frequencies, default to unknown. + return gcpspanner.SavedSearchSnapshotTypeUnknown +} + +func toSpannerSubscriptionTrigger(trigger backend.SubscriptionTriggerWritable) gcpspanner.SubscriptionTrigger { + switch trigger { + case backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete: + return gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete + case backend.SubscriptionTriggerFeatureBaselineToNewly: + return gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly + case backend.SubscriptionTriggerFeatureBaselineToWidely: + return gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToWidely + case backend.SubscriptionTriggerFeatureBaselineRegressionToLimited: + return gcpspanner.SubscriptionTriggerFeatureBaselineRegressionToLimited + } + + // Should not reach here normally. The http layer already checks for valid triggers, default to unknown. + return gcpspanner.SubscriptionTriggerUnknown +} + func (s *Backend) CreateSavedSearchSubscription(ctx context.Context, userID string, req backend.Subscription) (*backend.SubscriptionResponse, error) { + spannerFreq := toSpannerSubscriptionFrequency(req.Frequency) createReq := gcpspanner.CreateSavedSearchSubscriptionRequest{ UserID: userID, ChannelID: req.ChannelId, SavedSearchID: req.SavedSearchId, Triggers: backendTriggersToSpannerTriggers(req.Triggers), - Frequency: string(req.Frequency), + Frequency: spannerFreq, } id, err := s.client.CreateSavedSearchSubscription(ctx, createReq) @@ -1439,11 +1508,11 @@ func (s *Backend) UpdateSavedSearchSubscription(ctx context.Context, updateReq := gcpspanner.UpdateSavedSearchSubscriptionRequest{ ID: subscriptionID, UserID: userID, - Triggers: gcpspanner.OptionallySet[[]string]{ + Triggers: gcpspanner.OptionallySet[[]gcpspanner.SubscriptionTrigger]{ IsSet: false, Value: nil, }, - Frequency: gcpspanner.OptionallySet[string]{ + Frequency: gcpspanner.OptionallySet[gcpspanner.SavedSearchSnapshotType]{ IsSet: false, Value: "", }, @@ -1452,11 +1521,12 @@ func (s *Backend) UpdateSavedSearchSubscription(ctx context.Context, for _, field := range req.UpdateMask { switch field { case backend.UpdateSubscriptionRequestMaskTriggers: - updateReq.Triggers = gcpspanner.OptionallySet[[]string]{ + updateReq.Triggers = gcpspanner.OptionallySet[[]gcpspanner.SubscriptionTrigger]{ Value: backendTriggersToSpannerTriggers(*req.Triggers), IsSet: true} case backend.UpdateSubscriptionRequestMaskFrequency: - updateReq.Frequency = gcpspanner.OptionallySet[string]{Value: string(*req.Frequency), IsSet: true} + updateReq.Frequency = gcpspanner.OptionallySet[gcpspanner.SavedSearchSnapshotType]{ + Value: toSpannerSubscriptionFrequency(*req.Frequency), IsSet: true} } } @@ -1505,7 +1575,7 @@ func toBackendSubscription(sub *gcpspanner.SavedSearchSubscription) *backend.Sub ChannelId: sub.ChannelID, SavedSearchId: sub.SavedSearchID, Triggers: spannerTriggersToBackendTriggers(sub.Triggers), - Frequency: backend.SubscriptionFrequency(sub.Frequency), + Frequency: toBackendSubscriptionFrequency(sub.Frequency), CreatedAt: sub.CreatedAt, UpdatedAt: sub.UpdatedAt, } diff --git a/lib/gcpspanner/spanneradapters/backend_test.go b/lib/gcpspanner/spanneradapters/backend_test.go index dfbce5af4..c47e9f074 100644 --- a/lib/gcpspanner/spanneradapters/backend_test.go +++ b/lib/gcpspanner/spanneradapters/backend_test.go @@ -3653,16 +3653,17 @@ func TestCreateSavedSearchSubscription(t *testing.T) { ChannelId: channelID, SavedSearchId: savedSearchID, Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, createCfg: &mockCreateSavedSearchSubscriptionConfig{ expectedRequest: gcpspanner.CreateSavedSearchSubscriptionRequest{ UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: string(backend.SubscriptionFrequencyDaily), + Triggers: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, }, result: valuePtr(subID), returnedError: nil, @@ -3674,8 +3675,8 @@ func TestCreateSavedSearchSubscription(t *testing.T) { ID: subID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: string(backend.SubscriptionFrequencyDaily), + Triggers: []gcpspanner.SubscriptionTrigger{gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3688,11 +3689,11 @@ func TestCreateSavedSearchSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, - Frequency: backend.SubscriptionFrequencyDaily, + Frequency: backend.SubscriptionFrequencyImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3704,16 +3705,16 @@ func TestCreateSavedSearchSubscription(t *testing.T) { ChannelId: channelID, SavedSearchId: savedSearchID, Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, createCfg: &mockCreateSavedSearchSubscriptionConfig{ expectedRequest: gcpspanner.CreateSavedSearchSubscriptionRequest{ UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: string(backend.SubscriptionFrequencyDaily), + Triggers: []gcpspanner.SubscriptionTrigger{gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, }, result: nil, returnedError: gcpspanner.ErrMissingRequiredRole, @@ -3728,16 +3729,16 @@ func TestCreateSavedSearchSubscription(t *testing.T) { ChannelId: channelID, SavedSearchId: savedSearchID, Triggers: []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete}, - Frequency: backend.SubscriptionFrequencyDaily, + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete}, + Frequency: backend.SubscriptionFrequencyImmediate, }, createCfg: &mockCreateSavedSearchSubscriptionConfig{ expectedRequest: gcpspanner.CreateSavedSearchSubscriptionRequest{ UserID: userID, ChannelID: channelID, SavedSearchID: savedSearchID, - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: string(backend.SubscriptionFrequencyDaily), + Triggers: []gcpspanner.SubscriptionTrigger{gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, }, result: nil, returnedError: errTest, @@ -3797,8 +3798,8 @@ func TestListSavedSearchSubscriptions(t *testing.T) { ID: "sub1", ChannelID: "chan1", SavedSearchID: "search1", - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: "daily", + Triggers: []gcpspanner.SubscriptionTrigger{gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3815,11 +3816,11 @@ func TestListSavedSearchSubscriptions(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, - Frequency: "daily", + Frequency: backend.SubscriptionFrequencyImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3890,8 +3891,8 @@ func TestGetSavedSearchSubscription(t *testing.T) { ID: subID, ChannelID: "chan1", SavedSearchID: "search1", - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: "daily", + Triggers: []gcpspanner.SubscriptionTrigger{gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3904,11 +3905,11 @@ func TestGetSavedSearchSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, - Frequency: "daily", + Frequency: backend.SubscriptionFrequencyImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -3964,10 +3965,11 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { ) now := time.Now() updatedTriggers := []backend.SubscriptionTriggerWritable{ - backend.SubscriptionTriggerFeatureBaselineLimitedToNewly, - backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited, + backend.SubscriptionTriggerFeatureBaselineToNewly, + backend.SubscriptionTriggerFeatureBaselineRegressionToLimited, } - updatedFrequency := backend.SubscriptionFrequencyDaily + updatedFrequency := backend.SubscriptionFrequencyImmediate + updatedSpannerFrequency := gcpspanner.SavedSearchSnapshotTypeImmediate testCases := []struct { name string @@ -3989,13 +3991,13 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { expectedRequest: gcpspanner.UpdateSavedSearchSubscriptionRequest{ ID: subID, UserID: userID, - Triggers: gcpspanner.OptionallySet[[]string]{ - Value: []string{ - "feature_baseline_limited_to_newly", - "feature_baseline_regression_newly_to_limited", + Triggers: gcpspanner.OptionallySet[[]gcpspanner.SubscriptionTrigger]{ + Value: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly, + gcpspanner.SubscriptionTriggerFeatureBaselineRegressionToLimited, }, IsSet: true, }, - Frequency: gcpspanner.OptionallySet[string]{IsSet: false, Value: ""}, + Frequency: gcpspanner.OptionallySet[gcpspanner.SavedSearchSnapshotType]{IsSet: false, Value: ""}, }, returnedError: nil, }, @@ -4003,11 +4005,12 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { expectedSubscriptionID: subID, expectedUserID: userID, result: &gcpspanner.SavedSearchSubscription{ - ID: subID, - Triggers: []string{"feature_baseline_limited_to_newly"}, + ID: subID, + Triggers: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly}, ChannelID: "channel", SavedSearchID: "savedsearch", - Frequency: "daily", + Frequency: gcpspanner.SavedSearchSnapshotTypeImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -4018,13 +4021,13 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureBaselineLimitedToNewly), + backend.SubscriptionTriggerFeatureBaselineToNewly), RawValue: nil, }, }, ChannelId: "channel", SavedSearchId: "savedsearch", - Frequency: "daily", + Frequency: backend.SubscriptionFrequencyImmediate, CreatedAt: now, UpdatedAt: now, }, @@ -4042,9 +4045,9 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { expectedRequest: gcpspanner.UpdateSavedSearchSubscriptionRequest{ ID: subID, UserID: userID, - Triggers: gcpspanner.OptionallySet[[]string]{IsSet: false, Value: nil}, - Frequency: gcpspanner.OptionallySet[string]{ - Value: "daily", IsSet: true, + Triggers: gcpspanner.OptionallySet[[]gcpspanner.SubscriptionTrigger]{IsSet: false, Value: nil}, + Frequency: gcpspanner.OptionallySet[gcpspanner.SavedSearchSnapshotType]{ + Value: gcpspanner.SavedSearchSnapshotTypeImmediate, IsSet: true, }, }, returnedError: nil, @@ -4056,10 +4059,11 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { ID: subID, ChannelID: "channel", SavedSearchID: "savedsearchid", - Triggers: []string{"feature_any_browser_implementation_complete"}, - Frequency: string(updatedFrequency), - CreatedAt: now, - UpdatedAt: now, + Triggers: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete}, + Frequency: updatedSpannerFrequency, + CreatedAt: now, + UpdatedAt: now, }, returnedError: nil, }, @@ -4070,7 +4074,7 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { Triggers: []backend.SubscriptionTriggerResponseItem{ { Value: backendtypes.AttemptToStoreSubscriptionTrigger( - backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil, }, }, @@ -4092,13 +4096,13 @@ func TestUpdateSavedSearchSubscription(t *testing.T) { expectedRequest: gcpspanner.UpdateSavedSearchSubscriptionRequest{ ID: subID, UserID: userID, - Triggers: gcpspanner.OptionallySet[[]string]{ - Value: []string{ - "feature_baseline_limited_to_newly", - "feature_baseline_regression_newly_to_limited", + Triggers: gcpspanner.OptionallySet[[]gcpspanner.SubscriptionTrigger]{ + Value: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly, + gcpspanner.SubscriptionTriggerFeatureBaselineRegressionToLimited, }, IsSet: true, }, - Frequency: gcpspanner.OptionallySet[string]{ + Frequency: gcpspanner.OptionallySet[gcpspanner.SavedSearchSnapshotType]{ Value: "", IsSet: false, }, @@ -4223,7 +4227,7 @@ func assertUnknownTrigger(t *testing.T, itemIndex int, func TestSpannerTriggersToBackendTriggers(t *testing.T) { testCases := []struct { name string - inputTriggers []string + inputTriggers []gcpspanner.SubscriptionTrigger expectedItems []struct { IsUnknown bool Value string @@ -4232,9 +4236,9 @@ func TestSpannerTriggersToBackendTriggers(t *testing.T) { }{ { name: "All Valid Triggers", - inputTriggers: []string{ - string(backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), - string(backend.SubscriptionTriggerFeatureBaselineLimitedToNewly), + inputTriggers: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete, + gcpspanner.SubscriptionTriggerFeatureBaselinePromoteToNewly, }, expectedItems: []struct { IsUnknown bool @@ -4242,19 +4246,19 @@ func TestSpannerTriggersToBackendTriggers(t *testing.T) { RawValue *string }{ {IsUnknown: false, - Value: string(backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + Value: string(backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil}, {IsUnknown: false, - Value: string(backend.SubscriptionTriggerFeatureBaselineLimitedToNewly), + Value: string(backend.SubscriptionTriggerFeatureBaselineToNewly), RawValue: nil}, }, }, { name: "Mixed Valid and Unknown Triggers", - inputTriggers: []string{ - string(backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + inputTriggers: []gcpspanner.SubscriptionTrigger{ + gcpspanner.SubscriptionTriggerBrowserImplementationAnyComplete, "deprecated_trigger", - string(backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited), + gcpspanner.SubscriptionTriggerFeatureBaselineRegressionToLimited, "another_unknown", }, expectedItems: []struct { @@ -4263,13 +4267,13 @@ func TestSpannerTriggersToBackendTriggers(t *testing.T) { RawValue *string }{ {IsUnknown: false, - Value: string(backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete), + Value: string(backend.SubscriptionTriggerFeatureBrowserImplementationAnyComplete), RawValue: nil}, {IsUnknown: true, Value: string(backend.EnumUnknownValue), RawValue: valuePtr("deprecated_trigger")}, {IsUnknown: false, - Value: string(backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited), + Value: string(backend.SubscriptionTriggerFeatureBaselineRegressionToLimited), RawValue: nil}, {IsUnknown: true, Value: string(backend.EnumUnknownValue), @@ -4278,7 +4282,7 @@ func TestSpannerTriggersToBackendTriggers(t *testing.T) { }, { name: "All Unknown Triggers", - inputTriggers: []string{"unknown1", "unknown2"}, + inputTriggers: []gcpspanner.SubscriptionTrigger{"unknown1", "unknown2"}, expectedItems: []struct { IsUnknown bool Value string @@ -4290,7 +4294,7 @@ func TestSpannerTriggersToBackendTriggers(t *testing.T) { }, { name: "Empty Triggers", - inputTriggers: []string{}, + inputTriggers: []gcpspanner.SubscriptionTrigger{}, expectedItems: []struct { IsUnknown bool Value string diff --git a/lib/gcpspanner/spanneradapters/event_producer.go b/lib/gcpspanner/spanneradapters/event_producer.go index 802fe01fd..7fc363347 100644 --- a/lib/gcpspanner/spanneradapters/event_producer.go +++ b/lib/gcpspanner/spanneradapters/event_producer.go @@ -134,16 +134,17 @@ func NewEventProducer(client EventProducerSpannerClient) *EventProducer { func convertFrequencyToSnapshotType(freq workertypes.JobFrequency) gcpspanner.SavedSearchSnapshotType { switch freq { - // Eventually daily and unknown will be their own types. - case workertypes.FrequencyImmediate, workertypes.FrequencyDaily, workertypes.FrequencyUnknown: + case workertypes.FrequencyImmediate: return gcpspanner.SavedSearchSnapshotTypeImmediate case workertypes.FrequencyWeekly: return gcpspanner.SavedSearchSnapshotTypeWeekly case workertypes.FrequencyMonthly: return gcpspanner.SavedSearchSnapshotTypeMonthly + case workertypes.FrequencyUnknown: + return gcpspanner.SavedSearchSnapshotTypeUnknown } - return gcpspanner.SavedSearchSnapshotTypeImmediate + return gcpspanner.SavedSearchSnapshotTypeUnknown } func convertWorktypeReasonsToSpanner(reasons []workertypes.Reason) []string { diff --git a/lib/gcpspanner/spanneradapters/event_producer_test.go b/lib/gcpspanner/spanneradapters/event_producer_test.go index 3de8fdbfb..983a2b10b 100644 --- a/lib/gcpspanner/spanneradapters/event_producer_test.go +++ b/lib/gcpspanner/spanneradapters/event_producer_test.go @@ -200,8 +200,8 @@ func TestEventProducer_ReleaseLock(t *testing.T) { wantErr bool }{ { - name: "Daily maps to Immediate (as per implementation)", - freq: workertypes.FrequencyDaily, + name: "Regular release", + freq: workertypes.FrequencyImmediate, wantSnapshotType: gcpspanner.SavedSearchSnapshotTypeImmediate, mockErr: nil, wantErr: false, @@ -432,7 +432,7 @@ func TestEventProducer_GetLatestEvent(t *testing.T) { }, { name: "Spanner error", - freq: workertypes.FrequencyDaily, + freq: workertypes.FrequencyImmediate, wantSnapshotType: gcpspanner.SavedSearchSnapshotTypeImmediate, mockResp: nil, mockErr: errors.New("db error"), diff --git a/lib/workertypes/types.go b/lib/workertypes/types.go index 7bd6470de..e6342fb2e 100644 --- a/lib/workertypes/types.go +++ b/lib/workertypes/types.go @@ -572,7 +572,6 @@ type JobFrequency string const ( FrequencyUnknown JobFrequency = "UNKNOWN" FrequencyImmediate JobFrequency = "IMMEDIATE" - FrequencyDaily JobFrequency = "DAILY" FrequencyWeekly JobFrequency = "WEEKLY" FrequencyMonthly JobFrequency = "MONTHLY" ) diff --git a/openapi/backend/openapi.yaml b/openapi/backend/openapi.yaml index 2cf6b4f40..efacfe7ca 100644 --- a/openapi/backend/openapi.yaml +++ b/openapi/backend/openapi.yaml @@ -1813,22 +1813,28 @@ components: - EnumUnknownValue SubscriptionFrequency: type: string - description: The frequency for a subscription. Currently, only 'daily' is supported. + description: The frequency for a subscription. enum: - - daily + - immediate + - weekly + - monthly x-enumNames: - - SubscriptionFrequencyDaily + - SubscriptionFrequencyImmediate + - SubscriptionFrequencyWeekly + - SubscriptionFrequencyMonthly SubscriptionTriggerWritable: type: string description: The set of valid, user-selectable triggers for a subscription. enum: - - feature_baseline_limited_to_newly - - feature_any_browser_implementation_complete - - feature_baseline_regression_newly_to_limited + - feature_baseline_to_newly + - feature_baseline_to_widely + - feature_browser_implementation_any_complete + - feature_baseline_regression_to_limited x-enumNames: - - SubscriptionTriggerFeatureBaselineLimitedToNewly - - SubscriptionTriggerFeatureAnyBrowserImplementationComplete - - SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited + - SubscriptionTriggerFeatureBaselineToNewly + - SubscriptionTriggerFeatureBaselineToWidely + - SubscriptionTriggerFeatureBrowserImplementationAnyComplete + - SubscriptionTriggerFeatureBaselineRegressionToLimited SubscriptionTriggerResponseValue: description: > Represents a subscription trigger value. Includes 'unknown' for handling deprecated triggers. diff --git a/workers/event_producer/pkg/producer/batch_handler_test.go b/workers/event_producer/pkg/producer/batch_handler_test.go index 7e89fe90c..d0955d9dc 100644 --- a/workers/event_producer/pkg/producer/batch_handler_test.go +++ b/workers/event_producer/pkg/producer/batch_handler_test.go @@ -100,7 +100,7 @@ func TestProcessBatchUpdate(t *testing.T) { pub := &mockCommandPublisher{err: tc.pubErr, commands: nil} handler := NewBatchUpdateHandler(lister, pub) - err := handler.ProcessBatchUpdate(context.Background(), "trigger-1", workertypes.FrequencyDaily) + err := handler.ProcessBatchUpdate(context.Background(), "trigger-1", workertypes.FrequencyImmediate) if (err != nil) != tc.wantErr { t.Errorf("ProcessBatchUpdate() error = %v, wantErr %v", err, tc.wantErr) @@ -121,7 +121,7 @@ func TestProcessBatchUpdate(t *testing.T) { if pub.commands[0].SearchID != "s1" { t.Errorf("Command data mismatch") } - if pub.commands[0].Frequency != workertypes.FrequencyDaily { + if pub.commands[0].Frequency != workertypes.FrequencyImmediate { t.Errorf("Frequency mismatch") } }