feat(workers): update workers and event producer for feature list changes#2422
feat(workers): update workers and event producer for feature list changes#2422jcscottiii wants to merge 2 commits intofeat/saved-search-apifrom
Conversation
28f4a2b to
9018e18
Compare
3412a5f to
8583cf9
Compare
…nges Updates the event producer and email digest worker to handle changes related to feature lists and saved searches. This includes updates to the differ, templates, and types used in the notification pipeline.
8583cf9 to
a0fa5b0
Compare
| Err error | ||
| } | ||
|
|
||
| func (e *QueryParseError) Error() string { |
There was a problem hiding this comment.
FYI: For those new to Go. This is how you implement the error interface
DanielRyanSmith
left a comment
There was a problem hiding this comment.
Gemini finding:
Major Logic Error in executePlan
In workers/event_producer/pkg/differ/differ.go, the executePlan method fetches features for the previous query when plan.QueryChanged is true:
if plan.QueryChanged {
result, err := d.client.FetchFeatures(ctx, plan.PreviousQuery)
if err == nil {
data.TargetSnapshot = comparables.NewFeatureMapFromBackendFeatures(result.Features)
} else {
// Fallback: If old query fails, we return nil TargetSnapshot.
// Run() detects this and skips diffing, treating it as a silent reset.
return executionData{
OldSnapshot: nil,
TargetSnapshot: nil,
NewSnapshot: data.NewSnapshot,
QueryErrors: data.QueryErrors,
SnapshotOrigin: data.SnapshotOrigin,
}, nil
}
}The Bug: The interface for FetchFeatures was changed to return (*workertypes.FetchFeaturesResult, error). For "user errors" (like ErrSavedSearchNotFound, invalid queries, etc.), FetchFeatures now returns a valid *FetchFeaturesResult with a populated UserError field, but the err return value is nil.
Because err is nil, the code will mistakenly enter the if err == nil block rather than the else fallback block. Since result.Features will be empty/nil when there is a UserError, TargetSnapshot becomes an empty map (map[]) rather than nil.
The Impact: Instead of skipping diffing and silently resetting (the intended fallback behavior for a failed previous query), the diff engine will compare the new features against an empty TargetSnapshot. This will erroneously report every feature in the new query as "Added" and spam users with notifications about "new" features.
The Fix: You need to check if result.UserError == nil in addition to err == nil:
if plan.QueryChanged {
result, err := d.client.FetchFeatures(ctx, plan.PreviousQuery)
if err == nil && result.UserError == nil {
data.TargetSnapshot = comparables.NewFeatureMapFromBackendFeatures(result.Features)
} else {
// Fallback ...|
@DanielRyanSmith good catch! fixed! |
feat: Report query errors in feature list diffs and notifications
This PR introduces the capability to capture, persist, and report errors encountered when executing search queries during the feature list diffing process. This ensures that users are notified when their saved searches or queries become invalid (e.g., due to deleted dependencies or cycles).
Key Changes:
FeatureDifferto capture specific non-fatal errors (likeErrSavedSearchNotFound) during feature fetching, instead of failing the entire run.FeatureFetcherInterface: UpdatedFetchFeaturesto return a structured*FetchFeaturesResultcontaining both features and optionalUserErrorobjects, encapsulating backend error interpretation inside the adapter layer.QueryErrorsfield toStateMetadata(v1) as a regular slice ofQueryErrorusing Go 1.24'somitzerotag, balancing consistency withEventID(which usesomitempty) and the project's use ofomitzerofor optional struct fields.depguardlinter in.golangci.yamland added rules to prevent package coupling. Specifically, preventedworkers/*/pkg/**andlib/workertypes/**from importingspanneradapters, enforcing the Staff SWE pattern of orchestrated error handling and clean service boundaries.Rationale for Multi-Layer Enums and "Duplication"
Reviewers may notice seemingly duplicate definitions of query error codes across different packages:
lib/workertypes/comparables: Used for internal diffing logic.lib/workertypes: Used for summary representations.lib/blobtypes/featurelist/v1andfeaturelistdiff/v1: Used for permanent storage in diff snapshots.Why are they duplicated?
This is an intentional application of the Multi-Layer Enum Strategy to ensure: