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

feat(flags): fall back to /decide endpoint for GetFeatureFlag and GetAllFlags so that users can use this library without needing a personal API Key #46

Merged
merged 5 commits into from
Aug 7, 2024

Conversation

dmarticus
Copy link
Contributor

@dmarticus dmarticus commented Aug 6, 2024

Does what it says on the tin. I made the following changes:

  • Fallback logic in GetFeatureFlag and GetAllFlags that falls back to using the /decide endpoint for fetching feature flags if there's no PersonalApiKey provided
  • Tests for this new behavior

Potential TODOs:

  • Log the fallback?
  • improve error messages with missing token to tell the users they can fall back?

Other things:

@@ -325,7 +325,7 @@ func TestFlagGroup(t *testing.T) {
t.Errorf("Expected personProperties to be map[region:Canada], got %s", reqBody.PersonProperties)
}

groupPropertiesEquality := reflect.DeepEqual(reqBody.GroupProperties, map[string]Properties{"company": Properties{"name": "Project Name 1"}})
groupPropertiesEquality := reflect.DeepEqual(reqBody.GroupProperties, map[string]Properties{"company": {"name": "Project Name 1"}})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

compiler told me that Properties field was unused.

@@ -311,7 +311,7 @@ func (poller *FeatureFlagsPoller) computeFlagLocally(
groupName, exists := poller.groups[fmt.Sprintf("%d", *flag.Filters.AggregationGroupTypeIndex)]

if !exists {
errMessage := "Flag has unknown group type index"
errMessage := "flag has unknown group type index"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

compiler told me to lowercase error messages

@@ -467,7 +467,7 @@ func matchCohort(property FlagProperty, properties Properties, cohorts map[strin
cohortId := fmt.Sprint(property.Value)
propertyGroup, ok := cohorts[cohortId]
if !ok {
return false, fmt.Errorf("Can't match cohort: cohort %s not found", cohortId)
return false, fmt.Errorf("can't match cohort: cohort %s not found", cohortId)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

compiler told me to lowercase error messages

@@ -578,7 +578,7 @@ func matchProperty(property FlagProperty, properties Properties) (bool, error) {
return false, &InconclusiveMatchError{"Can't match properties with operator is_not_set"}
}

override_value, _ := properties[key]
override_value := properties[key]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

compiler told me this was unused.

@@ -637,7 +637,7 @@ func matchProperty(property FlagProperty, properties Properties) (bool, error) {
valueString = strconv.Itoa(valueInt)
r, err = regexp.Compile(valueString)
} else {
errMessage := "Regex expression not allowed"
errMessage := "regex expression not allowed"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

compiler told me to lowercase error messages

@@ -705,7 +706,7 @@ func TestClientMaxConcurrentRequests(t *testing.T) {
func(m APIMessage, e error) { errchan <- e },
},
Transport: testTransportDelayed,
// Only one concurreny request can be submitted, because the transport
// Only one concurrency request can be submitted, because the transport
Copy link
Contributor Author

Choose a reason for hiding this comment

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

drive-by spellcheck

Comment on lines +745 to +747
receivedErrors := [2]error{}
receivedErrors[0] = client.ReloadFeatureFlags()
_, receivedErrors[1] = client.IsFeatureEnabled(
FeatureFlagPayload{
Key: "some key",
DistinctId: "some id",
},
)
_, receivedErrors[2] = client.GetFeatureFlag(
FeatureFlagPayload{
Key: "some key",
DistinctId: "some id",
},
)
_, receivedErrors[3] = client.GetFeatureFlags()
_, receivedErrors[1] = client.GetFeatureFlags()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

we only have 2 methods that error on a missing PersonalApiKey, and neither of them are hot paths (i.e. users don't need them to use our feature flags).

@@ -766,6 +755,442 @@ func TestFeatureFlagsWithNoPersonalApiKey(t *testing.T) {

}

func TestIsFeatureEnabled(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this method didn't have tests, so now it does. Boy scout principle!

}
}

func TestGetFeatureFlagWithNoPersonalApiKey(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

test the fallbacks

Comment on lines -791 to -795

// flagValue, valueError := client.GetFeatureFlag("simpleFlag", "hey", false, Groups{}, NewProperties(), map[string]Properties{})
// if valueError != nil || flagValue != true {
// t.Errorf("simple flag with null rollout percentage should have value 'true'")
// }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was dead code

if c.featureFlagsPoller != nil {
// get feature flags from the poller, which uses the personal api key
flagValue, err = c.featureFlagsPoller.GetFeatureFlag(flagConfig)
} else {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should I log that we're falling back here? Does it matter?

Copy link
Member

Choose a reason for hiding this comment

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

maybe, but only at a trace/info/debug level. Might be good for investigations.

Copy link
Member

@fuziontech fuziontech left a comment

Choose a reason for hiding this comment

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

image

A few comments, but otherwise this is sick
🚢 it

PS nice tests!

Comment on lines +253 to +268
func (c *client) setLastCapturedEvent(event Capture) {
c.lastEventMutex.Lock()
defer c.lastEventMutex.Unlock()
c.lastCapturedEvent = &event
}

func (c *client) GetLastCapturedEvent() *Capture {
c.lastEventMutex.RLock()
defer c.lastEventMutex.RUnlock()
if c.lastCapturedEvent == nil {
return nil
}
// Return a copy to avoid data races
eventCopy := *c.lastCapturedEvent
return &eventCopy
}
Copy link
Member

Choose a reason for hiding this comment

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

eh, should be fine. Doesn't seem too unsafe to expose

if c.featureFlagsPoller != nil {
// get feature flags from the poller, which uses the personal api key
flagValue, err = c.featureFlagsPoller.GetFeatureFlag(flagConfig)
} else {
Copy link
Member

Choose a reason for hiding this comment

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

maybe, but only at a trace/info/debug level. Might be good for investigations.

Copy link

@Phanatic Phanatic left a comment

Choose a reason for hiding this comment

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

Ship it!

@dmarticus dmarticus merged commit 76dfd13 into master Aug 7, 2024
1 check passed
@dmarticus dmarticus deleted the feat/fallback-to-decide-with-no-api-token branch August 7, 2024 23:43
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.

3 participants