feat(v2/events/snowplow): add WithContext option to TrackEvent
What does this MR do and why?
Adds a WithContext functional option to Tracker.TrackEvent so callers can attach one or more Snowplow context entities (SelfDescribingJSON) alongside an event payload — for example, the gitlab_standard context, or any service-specific context.
Closes #103 (closed).
Note on CI: I haven't enabled instance runners on my fork (blocked on free-account identity verification, which I'd rather not pursue just for this MR). Labkit has
ci_allow_fork_pipelines_to_run_in_parent_project: true, so a maintainer can run the pipeline in the parent project context with one click. Local verification on this branch is already green:cd v2 && go test -race ./events/snowplow/...passes,golangci-lint run ./events/snowplow/...reports 0 issues.
Why this design
- Functional options match existing labkit idioms (
WithCallback,WithTokenSource,WithMetricsCollectorOptions) — no new public types other than the option function. - Backward compatible: existing
TrackEvent(name, props)calls compile and behave identically. No context is encoded when none is supplied. Satisfies the v2 stability principle ("never add required parameters"). - Encoding mirrors billing events: contexts are wrapped in a
SchemaContextsenvelope and base64-encoded intopayload.ContextEncoded, the same path thatTrackBillingEventWithOptionalInputalready uses for thebillable_usagecontext. - Multiple contexts accumulate;
nilentries are filtered.
Usage
gitlabStandard := &snowplow.SelfDescribingJSON{
Schema: "iglu:com.gitlab/gitlab_standard/jsonschema/1-1-8",
Data: map[string]any{
"environment": "production",
"source": "artifact-registry",
},
}
err := tracker.TrackEvent(
"artifact_downloaded",
map[string]any{"size_bytes": 1048576},
snowplow.WithContext(gitlabStandard),
)Changes
- New file
v2/events/snowplow/tracker_options.go— definesTrackEventOptionand theWithContextoption. tracker.go— addsopts ...TrackEventOptiontoTrackEvent; encodes contexts when present, otherwise leavesContextEncodedempty.tracker_test.go— addsTestTrackEvent_Context, a table-driven test with five subtests covering: no options, nil-only context filtered, single context, multiple contexts via one variadic call, and multipleWithContextcalls accumulating.examples_test.go— addsExampleTracker_TrackEvent_withContextdemonstrating attaching agitlab_standardcontext.
Public API surface added: WithContext and TrackEventOption. Both are documented.
How to test
cd v2 && go test -race ./events/snowplow/...All existing tests still pass. New tests verify both the encoded-payload shape (the SchemaContexts envelope, contained schemas in declared order) and the no-context path (ContextEncoded stays empty).
golangci-lint run ./events/snowplow/... reports 0 issues.
Notes for reviewers
- I deliberately avoided changing
TrackBillingEvent*— it already attaches a context via a different (non-optional) path. Unifying the two would be scope creep for this issue; happy to do a follow-up MR if preferred. - Variadic
opts ...TrackEventOptionis preferred over a siblingTrackEventWithContextmethod because (a) it composes with future options without further API churn and (b) it mirrorsNewEmitter(url, opts...)already in this package.