workertypes

package
v0.0.0-...-ad5db61 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 8 Imported by: 2

Documentation

Index

Constants

View Source
const (
	// VersionEventSummaryV1 defines the schema version for v1 of the EventSummary.
	VersionEventSummaryV1 = "v1"
	// MaxHighlights caps the number of detailed items stored in Spanner (The full highlights are stored in GCS).
	// Spanner's 10MB limit can easily accommodate this.
	// Calculation details:
	// A typical highlight contains:
	// - Feature info (ID, Name): ~50-80 bytes
	// - 2 DocLinks (URL, Title, Slug): ~250 bytes
	// - Changes metadata: ~50 bytes
	// - JSON structure overhead: ~50 bytes
	// Total ≈ 450-500 bytes.
	// 10,000 highlights * 500 bytes = 5MB, which is 50% of the 10MB column limit.
	MaxHighlights = 10000
)
View Source
const (
	ReasonQueryChanged = "QUERY_CHANGED"
	ReasonDataUpdated  = "DATA_UPDATED"
)

Variables

View Source
var (
	ErrUnknownSummaryVersion    = errors.New("unknown summary version")
	ErrFailedToSerializeSummary = errors.New("failed to serialize summary")
	ErrLatestEventNotFound      = errors.New("latest event not found")
)
View Source
var (
	// ErrUnrecoverableSystemFailureEmailSending indicates that there's a system failure that should not be retried.
	// Examples: System auth issue.
	ErrUnrecoverableSystemFailureEmailSending = errors.New("unrecoverable user failure trying to send email")
	// ErrUnrecoverableUserFailureEmailSending indicates that there's a user failure that should not be retried.
	// Examples: Bad email address.
	ErrUnrecoverableUserFailureEmailSending = errors.New("unrecoverable user failure trying to send email")
)

Functions

func ParseEventSummary

func ParseEventSummary(data []byte, v SummaryVisitor) error

ParseEventSummary handles the version detection and dispatching logic. Consumers (like the Delivery Worker) should use this instead of raw json.Unmarshal.

Types

type BaseSummaryVisitor

type BaseSummaryVisitor struct {
	Summary CategorizedSummary
	// contains filtered or unexported fields
}

BaseSummaryVisitor implements SummaryVisitor and centralizes highlight filtering, category grouping, promotion logic, and double-dispatching. BaseSummaryVisitor is stateful and is NOT safe for concurrent use across goroutines. Create a new instance per EventSummary categorization pass.

func (*BaseSummaryVisitor) Dispatch

Dispatch executes double-dispatch on the provided CategorizedSummaryVisitor target.

func (*BaseSummaryVisitor) HasContent

func (v *BaseSummaryVisitor) HasContent() bool

HasContent returns true if any query errors or categorized highlights are present.

func (*BaseSummaryVisitor) VisitV1

func (v *BaseSummaryVisitor) VisitV1(s EventSummary) error

VisitV1 processes an EventSummary, filters highlights by triggers, categorizes them, applies promotion rules, and dispatches to the target visitor if provided.

type BaselineStatus

type BaselineStatus string
const (
	BaselineStatusLimited BaselineStatus = "limited"
	BaselineStatusNewly   BaselineStatus = "newly"
	BaselineStatusWidely  BaselineStatus = "widely"
	BaselineStatusUnknown BaselineStatus = "unknown"
)

type BaselineValue

type BaselineValue struct {
	Status   BaselineStatus `json:"status"`
	LowDate  *time.Time     `json:"low_date,omitempty"`
	HighDate *time.Time     `json:"high_date,omitempty"`
}

type BrowserName

type BrowserName string
const (
	BrowserChrome         BrowserName = "chrome"
	BrowserChromeAndroid  BrowserName = "chrome_android"
	BrowserEdge           BrowserName = "edge"
	BrowserFirefox        BrowserName = "firefox"
	BrowserFirefoxAndroid BrowserName = "firefox_android"
	BrowserSafari         BrowserName = "safari"
	BrowserSafariIos      BrowserName = "safari_ios"
)

type BrowserStatus

type BrowserStatus string
const (
	BrowserStatusAvailable   BrowserStatus = "available"
	BrowserStatusUnavailable BrowserStatus = "unavailable"
	BrowserStatusUnknown     BrowserStatus = ""
)

type BrowserValue

type BrowserValue struct {
	Status  BrowserStatus `json:"status"`
	Version *string       `json:"version,omitempty"`
	Date    *time.Time    `json:"date,omitempty"`
}

type CategorizedSummary

type CategorizedSummary struct {
	Text                string
	Truncated           bool
	QueryErrors         []SummaryQueryError
	ResolvedQueryErrors []SummaryQueryError
	Added               []SummaryHighlight
	Removed             []SummaryHighlight
	Changed             []SummaryHighlight
	Moved               []SummaryHighlight
	Split               []SummaryHighlight
	Deleted             []SummaryHighlight
}

CategorizedSummary contains pre-grouped highlights and error slices resulting from BaseSummaryVisitor categorization.

func NewEmptyCategorizedSummary

func NewEmptyCategorizedSummary() CategorizedSummary

NewEmptyCategorizedSummary returns a zero-initialized CategorizedSummary satisfying exhaustruct requirements.

type CategorizedSummaryVisitor

type CategorizedSummaryVisitor interface {
	VisitQueryErrors(errors []SummaryQueryError) error
	VisitResolvedQueryErrors(errors []SummaryQueryError) error
	VisitAddedFeatures(features []SummaryHighlight) error
	VisitRemovedFeatures(features []SummaryHighlight) error
	VisitChangedFeatures(features []SummaryHighlight) error
	VisitMovedFeatures(features []SummaryHighlight) error
	VisitSplitFeatures(features []SummaryHighlight) error
	VisitDeletedFeatures(features []SummaryHighlight) error
}

CategorizedSummaryVisitor defines the strongly-typed contract for consuming categorized summary elements. Delivery channels (e.g. RSS, Email, Webhook, Slack) implement this interface to receive filtered and promoted categories via double-dispatch.

Documenting & Enforcing a Successfully Tested Renderer: Standard delivery channel visitors MUST satisfy the following contract invariants and testing standards:

Runtime Implementation Invariants:

  1. Nil & Empty Slice Safety: All Visit* methods MUST safely handle nil or empty ([]T{}) slices without panicking or dereferencing nil pointers.
  2. Error Propagation: Rendering failures (e.g., template execution errors) MUST be returned as non-nil errors to allow BaseSummaryVisitor.Dispatch() to fail fast.
  3. State Isolation: Visitor instances must maintain state isolation across separate dispatch passes.

5-Part Unit Testing Blueprint (Symmetrical Test Parity Standard): Package unit tests for new delivery channels MUST implement the 5 symmetrical test suites:

  1. Test<Channel>_FeatureCategories: Table-driven tests covering all 6 categories (Added, Removed, Changed, Moved, Split, Deleted).
  2. Test<Channel>_QueryErrors_RenderMessage: Table-driven tests covering all SummaryQueryErrorCode enums.
  3. Test<Channel>_TriggerFiltering: Verifying highlight filtering by subscriber triggers.
  4. Test<Channel>_NilPointerGuards: Verifying zero panics when handling optional diff structs (Moved/Split = nil).
  5. Test<Channel>_Golden: Output regression testing using .golden snapshot files and cmp.Diff.

type Change

type Change[T any] struct {
	From T `json:"from"`
	To   T `json:"to"`
}

Change represents a value transition from Old to New.

type DeliveryMetadata

type DeliveryMetadata struct {
	EventID     string
	SearchID    string
	SearchName  string
	Query       string
	Frequency   JobFrequency
	GeneratedAt time.Time
}

DeliveryMetadata contains the necessary context from the original event required for rendering notifications (e.g. generating links), decoupled from the upstream event format.

type DispatchEventMetadata

type DispatchEventMetadata struct {
	EventID     string
	SearchID    string
	SearchName  string
	Frequency   JobFrequency
	Query       string
	GeneratedAt time.Time
}
type DocLink struct {
	URL   string  `json:"url"`
	Title *string `json:"title,omitempty"`
	Slug  *string `json:"slug,omitempty"`
}

type Docs

type Docs struct {
	MDNDocs []DocLink `json:"mdn_docs,omitempty"`
}

type EmailDeliveryJob

type EmailDeliveryJob struct {
	SubscriptionID string
	RecipientEmail string
	ChannelID      string
	Triggers       []JobTrigger
	// SummaryRaw is the opaque JSON payload describing the event.
	SummaryRaw []byte
	// Metadata contains context for links and tracking.
	Metadata DeliveryMetadata
}

EmailDeliveryJob represents a task to send an email.

type EmailSubscriber

type EmailSubscriber struct {
	SubscriptionID string
	UserID         string
	Triggers       []JobTrigger
	EmailAddress   string
	ChannelID      string
}

EmailSubscriber represents a subscriber using an Email channel.

type EventSummary

type EventSummary struct {
	SchemaVersion       string              `json:"schemaVersion"`
	Text                string              `json:"text"`
	Categories          SummaryCategories   `json:"categories,omitzero"`
	Truncated           bool                `json:"truncated"`
	SnapshotOrigin      SnapshotOrigin      `json:"snapshotOrigin,omitempty"`
	QueryErrors         []SummaryQueryError `json:"queryErrors,omitempty"`
	ResolvedQueryErrors []SummaryQueryError `json:"resolvedQueryErrors,omitempty"`
	Highlights          []SummaryHighlight  `json:"highlights"`
}

EventSummary matches the JSON structure stored in the database 'Summary' column.

func NewEmptyEventSummary

func NewEmptyEventSummary() EventSummary

func (*EventSummary) Accept

func (s *EventSummary) Accept(v CategorizedSummaryVisitor, triggers []JobTrigger) error

Accept filters highlights against triggers and executes double-dispatch via BaseSummaryVisitor.

func (*EventSummary) AddHighlight

func (s *EventSummary) AddHighlight(h SummaryHighlight)

AddHighlight adds a highlight to the summary.

func (*EventSummary) Categorize

func (s *EventSummary) Categorize(triggers []JobTrigger) (*BaseSummaryVisitor, error)

Categorize filters and categorizes the summary highlights against the provided triggers. It returns a BaseSummaryVisitor containing the categorized summary and any processing errors.

func (*EventSummary) ExtractUniqueFeatureIDs

func (s *EventSummary) ExtractUniqueFeatureIDs(triggers []JobTrigger) []string

ExtractUniqueFeatureIDs returns deduplicated feature IDs for highlights matching the given triggers.

func (*EventSummary) SetQueryErrors

func (s *EventSummary) SetQueryErrors(errs []SummaryQueryError)

SetQueryErrors sets the active query errors on the summary.

func (*EventSummary) SetResolvedQueryErrors

func (s *EventSummary) SetResolvedQueryErrors(errs []SummaryQueryError)

SetResolvedQueryErrors sets the resolved query errors on the summary.

type FeatureDiffV1SummaryGenerator

type FeatureDiffV1SummaryGenerator struct{}

func (FeatureDiffV1SummaryGenerator) GenerateJSONSummary

func (g FeatureDiffV1SummaryGenerator) GenerateJSONSummary(
	d v1.FeatureDiff) ([]byte, error)

GenerateJSONEventSummaryFromFeatureDiffV1 generates and serializes.

type FeatureRef

type FeatureRef struct {
	ID         string           `json:"id"`
	Name       string           `json:"name"`
	QueryMatch QueryMatchStatus `json:"query_match,omitzero"`
}

type FetchFeaturesResult

type FetchFeaturesResult struct {
	Features  []backend.Feature
	UserError *UserError
}

FetchFeaturesResult contains the result of FetchFeatures.

type IncomingEmailDeliveryJob

type IncomingEmailDeliveryJob struct {
	EmailDeliveryJob
	// The ID from the queued event for this specific email job.
	// This will be generated by the queuing service.
	// This is different from the EventID in the Metadata which is for the original event that triggered
	// the event producer in the very beginning.
	EmailEventID string
}

type IncomingWebhookDeliveryJob

type IncomingWebhookDeliveryJob struct {
	WebhookDeliveryJob
	// The ID from the queued event for this specific webhook job.
	// This will be generated by the queuing service.
	WebhookEventID string
}

type JobFrequency

type JobFrequency string

JobFrequency defines how often a saved search should be checked.

const (
	FrequencyUnknown   JobFrequency = "UNKNOWN"
	FrequencyImmediate JobFrequency = "IMMEDIATE"
	FrequencyWeekly    JobFrequency = "WEEKLY"
	FrequencyMonthly   JobFrequency = "MONTHLY"
)

type JobTrigger

type JobTrigger string
const (
	FeaturePromotedToNewly           JobTrigger = "FEATURE_PROMOTED_TO_NEWLY"
	FeaturePromotedToWidely          JobTrigger = "FEATURE_PROMOTED_TO_WIDELY"
	FeatureRegressedToLimited        JobTrigger = "FEATURE_REGRESSED_TO_LIMITED"
	BrowserImplementationAnyComplete JobTrigger = "BROWSER_IMPLEMENTATION_ANY_COMPLETE"
)

func ToJobTrigger

func ToJobTrigger(trigger backend.SubscriptionTriggerWritable) (JobTrigger, bool)

ToJobTrigger converts a backend.SubscriptionTriggerWritable to a workertypes.JobTrigger.

type LatestEventInfo

type LatestEventInfo struct {
	EventID       string
	StateBlobPath string
}

type NotificationEventCreatedV1

type NotificationEventCreatedV1 struct {
	ID string `json:"id"`
}

NotificationEventCreatedV1 lets consumers know that a particular notification has been created.

func (NotificationEventCreatedV1) APIVersion

func (e NotificationEventCreatedV1) APIVersion() string

func (NotificationEventCreatedV1) Kind

type NotificationEventRequest

type NotificationEventRequest struct {
	EventID      string
	SearchID     string
	SnapshotType string
	Reasons      []string
	DiffBlobPath string
	Summary      EventSummary
	NewStatePath string
	WorkerID     string
}

NotificationEventRequest encapsulates the data needed to insert a row into the Events table.

type PublishEventRequest

type PublishEventRequest struct {
	EventID       string
	StateID       string
	StateBlobPath string
	DiffID        string
	DiffBlobPath  string
	SearchID      string
	SearchName    string
	Query         string
	Summary       []byte
	Reasons       []Reason
	Frequency     JobFrequency
	GeneratedAt   time.Time
}

type QueryMatchStatus

type QueryMatchStatus string
const (
	QueryMatchMatch   QueryMatchStatus = "match"
	QueryMatchNoMatch QueryMatchStatus = "no_match"
)

type Reason

type Reason string

type RefreshSearchCommand

type RefreshSearchCommand struct {
	SearchID   string
	SearchName string
	Query      string
	Frequency  JobFrequency
	Timestamp  time.Time
}

type SavedSearchState

type SavedSearchState struct {
	StateBlobPath *string
}

type SavedSearchStateUpdateRequest

type SavedSearchStateUpdateRequest struct {
	StateBlobPath *string

	UpdateMask []SavedSearchStateUpdateRequestUpdateMask
}

type SavedSearchStateUpdateRequestUpdateMask

type SavedSearchStateUpdateRequestUpdateMask string
const (
	SavedSearchStateUpdateRequestStateBlobPath SavedSearchStateUpdateRequestUpdateMask = "state_blob_path"
)

type SearchJob

type SearchJob struct {
	ID    string
	Name  string
	Query string
}

type SnapshotOrigin

type SnapshotOrigin string
const (
	OriginUnknown          SnapshotOrigin = "UNKNOWN"
	OriginLive             SnapshotOrigin = "LIVE"
	OriginFallbackPrevious SnapshotOrigin = "FALLBACK_PREVIOUS"
)

func (SnapshotOrigin) Normalize

func (o SnapshotOrigin) Normalize() SnapshotOrigin

type SplitChange

type SplitChange struct {
	From FeatureRef   `json:"from"`
	To   []FeatureRef `json:"to"`
}

type SubscriberSet

type SubscriberSet struct {
	Emails   []EmailSubscriber
	Webhooks []WebhookSubscriber
}

SubscriberSet groups subscribers by channel type to avoid runtime type assertions.

type SummaryCategories

type SummaryCategories struct {
	QueryChanged    int `json:"query_changed,omitzero"`
	Added           int `json:"added,omitzero"`
	Removed         int `json:"removed,omitzero"`
	Deleted         int `json:"deleted,omitzero"`
	Moved           int `json:"moved,omitzero"`
	Split           int `json:"split,omitzero"`
	Updated         int `json:"updated,omitzero"`
	UpdatedImpl     int `json:"updated_impl,omitzero"`
	UpdatedRename   int `json:"updated_rename,omitzero"`
	UpdatedBaseline int `json:"updated_baseline,omitzero"`
}

SummaryCategories defines the specific counts for different change types.

func NewEmptySummaryCategories

func NewEmptySummaryCategories() SummaryCategories

type SummaryHighlight

type SummaryHighlight struct {
	Type        SummaryHighlightType `json:"type"`
	FeatureID   string               `json:"feature_id"`
	FeatureName string               `json:"feature_name"`
	Docs        *Docs                `json:"docs,omitempty"`

	// Strongly typed change fields to support i18n and avoid interface{}
	NameChange     *Change[string]                       `json:"name_change,omitempty"`
	BaselineChange *Change[BaselineValue]                `json:"baseline_change,omitempty"`
	BrowserChanges map[BrowserName]*Change[BrowserValue] `json:"browser_changes,omitempty"`

	// Moved details feature rename/location changes. May be nil if the highlight
	// type is not SummaryHighlightTypeMoved or if unmarshaled from historical/partial event payloads.
	Moved *Change[FeatureRef] `json:"moved,omitempty"`

	// Split details feature split changes into sub-features. May be nil if the highlight
	// type is not SummaryHighlightTypeSplit or if unmarshaled from historical/partial event payloads.
	Split *SplitChange `json:"split,omitempty"`
}

func FilterHighlights

func FilterHighlights(highlights []SummaryHighlight, triggers []JobTrigger) []SummaryHighlight

FilterHighlights filters highlights based on triggers.

func (SummaryHighlight) MatchesTrigger

func (h SummaryHighlight) MatchesTrigger(t JobTrigger) bool

type SummaryHighlightType

type SummaryHighlightType string
const (
	SummaryHighlightTypeAdded   SummaryHighlightType = "Added"
	SummaryHighlightTypeRemoved SummaryHighlightType = "Removed"
	SummaryHighlightTypeChanged SummaryHighlightType = "Changed"
	SummaryHighlightTypeMoved   SummaryHighlightType = "Moved"
	SummaryHighlightTypeSplit   SummaryHighlightType = "Split"
	SummaryHighlightTypeDeleted SummaryHighlightType = "Deleted"
)

type SummaryQueryError

type SummaryQueryError struct {
	Code SummaryQueryErrorCode `json:"code"`
}

SummaryQueryError contains the error code for a query error. We only include the Code (and drop the Message) to allow the specific renderer (email, Slack, etc.) to decide how to render the error.

type SummaryQueryErrorCode

type SummaryQueryErrorCode string

SummaryQueryErrorCode defines the error codes for query errors in the summary.

const (
	SummaryQueryErrorCodeFeatureNotFound          SummaryQueryErrorCode = "feature_not_found"
	SummaryQueryErrorCodeInvalidQuery             SummaryQueryErrorCode = "invalid_query"
	SummaryQueryErrorCodeSavedSearchNotFound      SummaryQueryErrorCode = "saved_search_not_found"
	SummaryQueryErrorCodeHotlistNotFound          SummaryQueryErrorCode = "hotlist_not_found"
	SummaryQueryErrorCodeSavedSearchCycleDetected SummaryQueryErrorCode = "saved_search_cycle_detected"
	SummaryQueryErrorCodeMaxDepthExceeded         SummaryQueryErrorCode = "saved_search_max_depth_exceeded"
	SummaryQueryErrorCodeQueryGrammar             SummaryQueryErrorCode = "query_grammar_invalid"
	SummaryQueryErrorCodeUnknown                  SummaryQueryErrorCode = "unknown"
)

func (SummaryQueryErrorCode) Message

func (c SummaryQueryErrorCode) Message() string

type SummaryVisitor

type SummaryVisitor interface {
	VisitV1(s EventSummary) error
}

SummaryVisitor defines the contract for consuming immutable Event Summaries. Unlike state blobs which are migrated to the latest schema, summaries are historical records that should be rendered as-is. The Visitor pattern forces consumers to explicitly handle each schema version (e.g. V1, V2) independently.

type UserError

type UserError struct {
	QueryErrors []SummaryQueryError
}

UserError contains expected user-facing errors.

type WebhookDeliveryJob

type WebhookDeliveryJob struct {
	SubscriptionID string
	WebhookURL     string
	WebhookType    WebhookType
	ChannelID      string
	Triggers       []JobTrigger
	// SummaryRaw is the opaque JSON payload describing the event.
	SummaryRaw []byte
	// Metadata contains context for links and tracking.
	Metadata DeliveryMetadata
}

WebhookDeliveryJob represents a task to send a webhook.

type WebhookSubscriber

type WebhookSubscriber struct {
	SubscriptionID string
	UserID         string
	Triggers       []JobTrigger
	WebhookURL     string
	WebhookType    WebhookType
	ChannelID      string
}

WebhookSubscriber represents a subscriber using a Webhook channel.

type WebhookType

type WebhookType string
const (
	WebhookTypeSlack WebhookType = "slack"
)

Directories

Path Synopsis
Named comparables instead of comparable to not conflict with the standard library's "comparable" interface
Named comparables instead of comparable to not conflict with the standard library's "comparable" interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL