Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for fetching feature flag payloads #64

Merged
merged 5 commits into from
Aug 13, 2024

Conversation

jvitoroc
Copy link
Contributor

@jvitoroc jvitoroc commented Aug 11, 2024

Adds support for feature flag payloads. It works for both local evaluation and decide endpoint, also supports multivariate flags. Usage:

	posthogClient, err = posthog.NewWithConfig(
		"phc_",
		posthog.Config{
			BatchSize:                          1,
			Endpoint:                           "https://us.i.posthog.com",
			DefaultFeatureFlagsPollingInterval: 1 * time.Second,
			Transport: &loghttp.Transport{
				Transport: http.DefaultTransport,
			},
		},
	)

	payload, err := posthogClient.GetFeatureFlagPayload(posthog.FeatureFlagPayload{
		Key:        "test-flag",
		DistinctId: "1",
	})

Questions
When should we return an empty string and nil as the payload?

Copy link
Contributor

@dmarticus dmarticus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work! I have a few minor notes and a suggestion but this is great work and I really appreciate the thorough testing. Thank you for helping us make our Go SDK better! Let me know what you think of my notes and then I'm happy to approve this and get it merged.

posthog_test.go Outdated
Comment on lines 892 to 906
// should we be capturing an event here?
// lastEvent := client.GetLastCapturedEvent()
// if lastEvent == nil || lastEvent.Event != "$feature_flag_called" {
// t.Errorf("Expected a $feature_flag_called event, got: %v", lastEvent)
// }

// Check that the properties of the captured event match the response from /decide
// if lastEvent != nil {
// if lastEvent.Properties["$feature_flag"] != "beta-feature" {
// t.Errorf("Expected feature flag key 'beta-feature', got: %v", lastEvent.Properties["$feature_flag"])
// }
// if lastEvent.Properties["$feature_flag_response"] != expectedPayload {
// t.Errorf("Expected feature flag response %v, got: %v", expectedPayload, lastEvent.Properties["$feature_flag_response"])
// }
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// should we be capturing an event here?
// lastEvent := client.GetLastCapturedEvent()
// if lastEvent == nil || lastEvent.Event != "$feature_flag_called" {
// t.Errorf("Expected a $feature_flag_called event, got: %v", lastEvent)
// }
// Check that the properties of the captured event match the response from /decide
// if lastEvent != nil {
// if lastEvent.Properties["$feature_flag"] != "beta-feature" {
// t.Errorf("Expected feature flag key 'beta-feature', got: %v", lastEvent.Properties["$feature_flag"])
// }
// if lastEvent.Properties["$feature_flag_response"] != expectedPayload {
// t.Errorf("Expected feature flag response %v, got: %v", expectedPayload, lastEvent.Properties["$feature_flag_response"])
// }
// }

yeah, this is a reasonable callout! I included this originally so that I could test events being captured correctly, but it doesn't make sense to include it in the SDK overall since it's one extra capture call that's not strictly necessary. I'm fine to remove this.

Comment on lines +303 to +305
if err := flagConfig.validate(); err != nil {
return "", err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be inclined to return false instead of "" here, since we're returning the more permissive interface{} type. This is also the pattern we use elsewhere throughout this SDK, e.g. here

Comment on lines +690 to +702
func (c *client) getFeatureFlagPayloadFromDecide(key string, distinctId string, groups Groups, personProperties Properties, groupProperties map[string]Properties) (string, error) {
decideResponse, err := c.makeDecideRequest(distinctId, groups, personProperties, groupProperties)
if err != nil {
return "", err
}

if value, ok := decideResponse.FeatureFlagPayloads[key]; ok {
return value, nil
}

return "", nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean when you're asking about when to return "", nil, since this is an example where you do that. Currently, the model we've kept for this SDK (that I've sure you've seen) is to have all of these methods return interface{}, error, instead of something with better types. The original decision was before my time, but I think the idea was probably to flexibly handle the fact that a feature flag could either be true, false, or a string.

However, now that we're venturing into handling payloads, I'm fine with this approach of more explicit typing, since the featureFlagPayload values should always be strings.

Since you're the one implementing this new feature (no one else is using payloads in the Go SDK yet), I think it's up to you – if you want to work with feature flag payload values as strings everywhere, then we can keep it like this. Perhaps it's worth calling out the difference between feature flag payload values vs feature flag values (the types do a good job of explaining what, but maybe a comment is worth the why). But overall I think this approach is reasonable.

Copy link
Contributor Author

@jvitoroc jvitoroc Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! I'm inclined to change the return type from interface{} to string across the board, including in the user-facing method GetFeatureFlagPayload. If I have the opportunity to use explicit types, I'll take it.

The changes I'll be making are:

  • Changing the return type from interface{} to string.
  • Returning an empty string and a nil error if there was no actual error, but the feature flag doesn't have a payload defined. I don't think there is a need to return special value or error if the feature flag doesn't have a payload, but you are explicitly trying to fetch it.
  • Return an empty string and an error if an actual error occurred.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, that sounds good to me!

@jvitoroc jvitoroc requested a review from dmarticus August 13, 2024 17:24
Copy link
Contributor

@dmarticus dmarticus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again! Let's get this shipped 💪

@dmarticus dmarticus merged commit a26a569 into PostHog:master Aug 13, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants