Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
graphqlbackend/telemetry: add and export interaction ID (#58539)
Browse files Browse the repository at this point in the history
#58016 adds a lightweight standard for propagating interaction ID via `X-Sourcegraph-Interaction-ID` - however, clientside, the recent implementations have all centered around explicitly providing and interaction ID when recording events.

This change adds an option to the GraphQL `telemetry { recordEvent }` mutation to accommodate this pattern as a first-class citizen. I'll update various client integrations in a follow-up.
  • Loading branch information
bobheadxi authored and vovakulikov committed Dec 12, 2023
1 parent aa2c060 commit d20bd80
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 55 deletions.
1 change: 1 addition & 0 deletions cmd/frontend/graphqlbackend/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type TelemetryEventParametersInput struct {
Metadata *[]TelemetryEventMetadataInput `json:"metadata,omitempty"`
PrivateMetadata *JSONValue `json:"privateMetadata,omitempty"`
BillingMetadata *TelemetryEventBillingMetadataInput `json:"billingMetadata,omitempty"`
InteractionID *string `json:"interactionID,omitempty"`
}

type TelemetryEventMetadataInput struct {
Expand Down
8 changes: 8 additions & 0 deletions cmd/frontend/graphqlbackend/telemetry.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ input TelemetryEventParametersInput {
Billing-related metadata.
"""
billingMetadata: TelemetryEventBillingMetadataInput
"""
Optional interaction ID that can be provided to indicate the interaction
this event belongs to. It overrides the X-Sourcegraph-Interaction-ID header
if one is set on the request recording the event.
This parameter is only available in Sourcegraph 5.2.4 and later.
"""
interactionID: String
}

"""
Expand Down
25 changes: 25 additions & 0 deletions cmd/frontend/graphqlbackend/telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/graph-gophers/graphql-go"
gqlerrors "github.com/graph-gophers/graphql-go/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"

Expand All @@ -33,6 +34,30 @@ func TestTelemetryRecordEvents(t *testing.T) {
// Assertions on received events.
assert func(t *testing.T, gotEvents []TelemetryEventInput)
}{
{
name: "simple event with interaction ID",
gqlEventsInput: `
{
feature: "cody.fixup"
action: "applied"
source: {
client: "VSCode.Cody"
clientVersion: "0.14.1"
}
parameters: {
version: 0
interactionID: "f1d1b784-c69b-4ca4-802a-4dcbca7d660f"
}
}
`,
assert: func(t *testing.T, gotEvents []TelemetryEventInput) {
require.Len(t, gotEvents, 1)
assert.NotNil(t, gotEvents[0].Parameters)
assert.NotNil(t, gotEvents[0].Parameters.InteractionID)
assert.Equal(t, "f1d1b784-c69b-4ca4-802a-4dcbca7d660f",
*gotEvents[0].Parameters.InteractionID)
},
},
{
name: "object privateMetadata",
gqlEventsInput: `
Expand Down
59 changes: 34 additions & 25 deletions cmd/frontend/internal/telemetry/resolvers/telemetrygateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,28 @@ func newTelemetryGatewayEvents(
ctx context.Context,
now time.Time,
newUUID func() string,
events []graphqlbackend.TelemetryEventInput,
gqlEvents []graphqlbackend.TelemetryEventInput,
) ([]*telemetrygatewayv1.Event, error) {
gatewayEvents := make([]*telemetrygatewayv1.Event, len(events))
for i, e := range events {
gatewayEvents := make([]*telemetrygatewayv1.Event, len(gqlEvents))
for i, gqlEvent := range gqlEvents {
event := telemetrygatewayv1.NewEventWithDefaults(ctx, now, newUUID)

event.Feature = e.Feature
event.Action = e.Action
event.Feature = gqlEvent.Feature
event.Action = gqlEvent.Action

// Override interaction ID, or just set it, if an interaction ID is
// explicitly provided as part of event data.
if gqlEvent.Parameters.InteractionID != nil && len(*gqlEvent.Parameters.InteractionID) > 0 {
if event.Interaction == nil {
event.Interaction = &telemetrygatewayv1.EventInteraction{}
}
event.Interaction.InteractionId = gqlEvent.Parameters.InteractionID
}

// Parse private metadata
var privateMetadata *structpb.Struct
if e.Parameters.PrivateMetadata != nil {
switch v := e.Parameters.PrivateMetadata.Value.(type) {
if gqlEvent.Parameters.PrivateMetadata != nil {
switch v := gqlEvent.Parameters.PrivateMetadata.Value.(type) {
// If the input is an object, turn it into proto struct as-is
case map[string]any:
var err error
Expand All @@ -51,25 +60,25 @@ func newTelemetryGatewayEvents(

// Configure parameters
event.Parameters = &telemetrygatewayv1.EventParameters{
Version: e.Parameters.Version,
Version: gqlEvent.Parameters.Version,
Metadata: func() map[string]int64 {
if e.Parameters.Metadata == nil || len(*e.Parameters.Metadata) == 0 {
if gqlEvent.Parameters.Metadata == nil || len(*gqlEvent.Parameters.Metadata) == 0 {
return nil
}
metadata := make(map[string]int64, len(*e.Parameters.Metadata))
for _, kv := range *e.Parameters.Metadata {
metadata := make(map[string]int64, len(*gqlEvent.Parameters.Metadata))
for _, kv := range *gqlEvent.Parameters.Metadata {
metadata[kv.Key] = int64(kv.Value)
}
return metadata
}(),
PrivateMetadata: privateMetadata,
BillingMetadata: func() *telemetrygatewayv1.EventBillingMetadata {
if e.Parameters.BillingMetadata == nil {
if gqlEvent.Parameters.BillingMetadata == nil {
return nil
}
return &telemetrygatewayv1.EventBillingMetadata{
Product: e.Parameters.BillingMetadata.Product,
Category: e.Parameters.BillingMetadata.Category,
Product: gqlEvent.Parameters.BillingMetadata.Product,
Category: gqlEvent.Parameters.BillingMetadata.Category,
}
}(),
}
Expand All @@ -78,21 +87,21 @@ func newTelemetryGatewayEvents(
Version: version.Version(),
},
Client: &telemetrygatewayv1.EventSource_Client{
Name: e.Source.Client,
Version: e.Source.ClientVersion,
Name: gqlEvent.Source.Client,
Version: gqlEvent.Source.ClientVersion,
},
}

if e.MarketingTracking != nil {
if gqlEvent.MarketingTracking != nil {
event.MarketingTracking = &telemetrygatewayv1.EventMarketingTracking{
Url: e.MarketingTracking.Url,
FirstSourceUrl: e.MarketingTracking.FirstSourceURL,
CohortId: e.MarketingTracking.CohortID,
Referrer: e.MarketingTracking.Referrer,
LastSourceUrl: e.MarketingTracking.LastSourceURL,
DeviceSessionId: e.MarketingTracking.DeviceSessionID,
SessionReferrer: e.MarketingTracking.SessionReferrer,
SessionFirstUrl: e.MarketingTracking.SessionFirstURL,
Url: gqlEvent.MarketingTracking.Url,
FirstSourceUrl: gqlEvent.MarketingTracking.FirstSourceURL,
CohortId: gqlEvent.MarketingTracking.CohortID,
Referrer: gqlEvent.MarketingTracking.Referrer,
LastSourceUrl: gqlEvent.MarketingTracking.LastSourceURL,
DeviceSessionId: gqlEvent.MarketingTracking.DeviceSessionID,
SessionReferrer: gqlEvent.MarketingTracking.SessionReferrer,
SessionFirstUrl: gqlEvent.MarketingTracking.SessionFirstURL,
}
}

Expand Down
1 change: 1 addition & 0 deletions doc/dev/background-information/telemetry/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ This page contains generated documentation for telemetry event data that gets ex
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| trace_id | [string](#string) | optional | <p>OpenTelemetry trace ID representing the interaction associated with the event.</p> |
| interaction_id | [string](#string) | optional | <p>Custom interaction ID representing the interaction associated with the event.</p> |
| geolocation | [EventInteraction.Geolocation](#telemetrygateway-v1-EventInteraction-Geolocation) | optional | <p>Geolocation associated with the interaction, typically inferred from the</p><p>originating client's IP address (which we do not collect).</p> |


Expand Down
1 change: 1 addition & 0 deletions internal/telemetrygateway/v1/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ go_library(
"//internal/actor",
"//internal/featureflag",
"//internal/requestclient",
"//internal/requestinteraction",
"//internal/trace",
"//lib/pointers",
"@com_github_google_uuid//:uuid",
Expand Down
14 changes: 11 additions & 3 deletions internal/telemetrygateway/v1/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/featureflag"
"github.com/sourcegraph/sourcegraph/internal/requestclient"
"github.com/sourcegraph/sourcegraph/internal/requestinteraction"
"github.com/sourcegraph/sourcegraph/internal/trace"
"github.com/sourcegraph/sourcegraph/lib/pointers"
)
Expand All @@ -35,6 +36,12 @@ func NewEventWithDefaults(ctx context.Context, now time.Time, newEventID func()
traceID = pointers.Ptr(eventTrace.TraceID().String())
}

// Get the interaction ID if provided
var interactionID *string
if it := requestinteraction.FromContext(ctx); it != nil {
interactionID = pointers.Ptr(it.ID)
}

// Get geolocation of request client, if there is one.
var geolocation *EventInteraction_Geolocation
if rc := requestclient.FromContext(ctx); rc != nil {
Expand All @@ -47,13 +54,14 @@ func NewEventWithDefaults(ctx context.Context, now time.Time, newEventID func()

// If we have nothing interesting to show, leave out Interaction
// entirely.
if traceID == nil && geolocation == nil {
if traceID == nil && interactionID == nil && geolocation == nil {
return nil
}

return &EventInteraction{
TraceId: traceID,
Geolocation: geolocation,
TraceId: traceID,
InteractionId: interactionID,
Geolocation: geolocation,
}
}(),
User: func() *EventUser {
Expand Down
63 changes: 38 additions & 25 deletions internal/telemetrygateway/v1/telemetrygateway.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/telemetrygateway/v1/telemetrygateway.proto
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ message EventMarketingTracking {
message EventInteraction {
// OpenTelemetry trace ID representing the interaction associated with the event.
optional string trace_id = 1;
// Reserve entry for client-provided interaction ID in follow-up change.
reserved 2;
// Custom interaction ID representing the interaction associated with the event.
optional string interaction_id = 2;

message Geolocation {
// Inferred ISO 3166-1 alpha-2 or alpha-3 country code
Expand Down

0 comments on commit d20bd80

Please sign in to comment.