Skip to content

Commit e592264

Browse files
authored
feat: Identity overrides in local evaluation mode (#121)
* feat: Identity overrides in local evaluation mode
1 parent 569276f commit e592264

File tree

5 files changed

+104
-16
lines changed

5 files changed

+104
-16
lines changed

client.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ type Client struct {
2222
apiKey string
2323
config config
2424

25-
environment atomic.Value
25+
environment atomic.Value
26+
identitiesWithOverrides atomic.Value
2627

2728
analyticsProcessor *AnalyticsProcessor
2829
defaultFlagHandler func(string) (Flag, error)
@@ -138,7 +139,7 @@ func (c *Client) GetIdentityFlags(ctx context.Context, identifier string, traits
138139
// Returns an array of segments that the given identity is part of.
139140
func (c *Client) GetIdentitySegments(identifier string, traits []*Trait) ([]*segments.SegmentModel, error) {
140141
if env, ok := c.environment.Load().(*environments.EnvironmentModel); ok {
141-
identity := buildIdentityModel(identifier, env.APIKey, traits)
142+
identity := c.getIdentityModel(identifier, env.APIKey, traits)
142143
return flagengine.GetIdentitySegments(env, &identity), nil
143144
}
144145
return nil, &FlagsmithClientError{msg: "flagsmith: Local evaluation required to obtain identity segments"}
@@ -214,7 +215,7 @@ func (c *Client) getIdentityFlagsFromEnvironment(identifier string, traits []*Tr
214215
if !ok {
215216
return Flags{}, fmt.Errorf("flagsmith: local environment has not yet been updated")
216217
}
217-
identity := buildIdentityModel(identifier, env.APIKey, traits)
218+
identity := c.getIdentityModel(identifier, env.APIKey, traits)
218219
featureStates := flagengine.GetIdentityFeatureStates(env, &identity)
219220
flags := makeFlagsFromFeatureStates(
220221
featureStates,
@@ -276,15 +277,28 @@ func (c *Client) UpdateEnvironment(ctx context.Context) error {
276277
return errors.New(e["detail"])
277278
}
278279
c.environment.Store(&env)
280+
identitiesWithOverrides := make(map[string]identities.IdentityModel)
281+
for _, id := range env.IdentityOverrides {
282+
identitiesWithOverrides[id.Identifier] = *id
283+
}
284+
c.identitiesWithOverrides.Store(identitiesWithOverrides)
279285

280286
return nil
281287
}
282288

283-
func buildIdentityModel(identifier string, apiKey string, traits []*Trait) identities.IdentityModel {
289+
func (c *Client) getIdentityModel(identifier string, apiKey string, traits []*Trait) identities.IdentityModel {
284290
identityTraits := make([]*TraitModel, len(traits))
285291
for i, trait := range traits {
286292
identityTraits[i] = trait.ToTraitModel()
287293
}
294+
295+
identitiesWithOverrides, _ := c.identitiesWithOverrides.Load().(map[string]identities.IdentityModel)
296+
identity, ok := identitiesWithOverrides[identifier]
297+
if ok {
298+
identity.IdentityTraits = identityTraits
299+
return identity
300+
}
301+
288302
return identities.IdentityModel{
289303
Identifier: identifier,
290304
IdentityTraits: identityTraits,

client_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,32 @@ func TestGetIdentityFlagsUseslocalEnvironmentWhenAvailable(t *testing.T) {
244244
assert.Equal(t, fixtures.Feature1Value, allFlags[0].Value)
245245
}
246246

247+
func TestGetIdentityFlagsUseslocalOverridesWhenAvailable(t *testing.T) {
248+
// Given
249+
ctx := context.Background()
250+
server := httptest.NewServer(http.HandlerFunc(fixtures.EnvironmentDocumentHandler))
251+
defer server.Close()
252+
// When
253+
client := flagsmith.NewClient(fixtures.EnvironmentAPIKey, flagsmith.WithLocalEvaluation(ctx),
254+
flagsmith.WithBaseURL(server.URL+"/api/v1/"))
255+
err := client.UpdateEnvironment(ctx)
256+
257+
// Then
258+
assert.NoError(t, err)
259+
260+
flags, err := client.GetIdentityFlags(ctx, "overridden-id", nil)
261+
262+
assert.NoError(t, err)
263+
264+
allFlags := flags.AllFlags()
265+
266+
assert.Equal(t, 1, len(allFlags))
267+
268+
assert.Equal(t, fixtures.Feature1Name, allFlags[0].FeatureName)
269+
assert.Equal(t, fixtures.Feature1ID, allFlags[0].FeatureID)
270+
assert.Equal(t, fixtures.Feature1OverriddenValue, allFlags[0].Value)
271+
}
272+
247273
func TestGetIdentityFlagsCallsAPIWhenLocalEnvironmentNotAvailableWithTraits(t *testing.T) {
248274
// Given
249275
ctx := context.Background()

fixtures/environment.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,30 @@
5454
"segment_id": null,
5555
"enabled": true
5656
}
57+
],
58+
"identity_overrides": [
59+
{
60+
"identifier": "overridden-id",
61+
"identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01",
62+
"created_date": "2019-08-27T14:53:45.698555Z",
63+
"updated_at": "2023-07-14 16:12:00.000000",
64+
"environment_api_key": "B62qaMZNwfiqT76p38ggrQ",
65+
"identity_features": [
66+
{
67+
"id": 1,
68+
"feature": {
69+
"id": 1,
70+
"name": "feature_1",
71+
"type": "STANDARD"
72+
},
73+
"featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f",
74+
"feature_state_value": "some-overridden-value",
75+
"enabled": false,
76+
"environment": 1,
77+
"identity": null,
78+
"feature_segment": null
79+
}
80+
]
81+
}
5782
]
58-
}
83+
}

fixtures/fixture.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const Feature1Value = "some_value"
1111
const Feature1Name = "feature_1"
1212
const Feature1ID = 1
1313

14+
const Feature1OverriddenValue = "some-overridden-value"
15+
1416
const EnvironmentJson = `
1517
{
1618
"api_key": "B62qaMZNwfiqT76p38ggrQ",
@@ -58,7 +60,32 @@ const EnvironmentJson = `
5860
},
5961
"segment_id": null,
6062
"enabled": true
61-
}]
63+
}],
64+
"identity_overrides": [
65+
{
66+
"identifier": "overridden-id",
67+
"identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01",
68+
"created_date": "2019-08-27T14:53:45.698555Z",
69+
"updated_at": "2023-07-14 16:12:00.000000",
70+
"environment_api_key": "B62qaMZNwfiqT76p38ggrQ",
71+
"identity_features": [
72+
{
73+
"id": 1,
74+
"feature": {
75+
"id": 1,
76+
"name": "feature_1",
77+
"type": "STANDARD"
78+
},
79+
"featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f",
80+
"feature_state_value": "some-overridden-value",
81+
"enabled": false,
82+
"environment": 1,
83+
"identity": null,
84+
"feature_segment": null
85+
}
86+
]
87+
}
88+
]
6289
}
6390
`
6491

flagengine/environments/models.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
package environments
22

33
import (
4-
"github.com/Flagsmith/flagsmith-go-client/v3/flagengine/environments/integrations"
54
"github.com/Flagsmith/flagsmith-go-client/v3/flagengine/features"
5+
"github.com/Flagsmith/flagsmith-go-client/v3/flagengine/identities"
66
"github.com/Flagsmith/flagsmith-go-client/v3/flagengine/projects"
77
)
88

99
type EnvironmentModel struct {
10-
ID int `json:"id"`
11-
APIKey string `json:"api_key"`
12-
Project *projects.ProjectModel `json:"project"`
13-
FeatureStates []*features.FeatureStateModel `json:"feature_states"`
14-
15-
AmplitudeConfig *integrations.IntegrationModel `json:"amplitude_config"`
16-
SegmentConfig *integrations.IntegrationModel `json:"segment_config"`
17-
MixpanelConfig *integrations.IntegrationModel `json:"mixpanel_config"`
18-
HeapConfig *integrations.IntegrationModel `json:"heap_config"`
10+
ID int `json:"id"`
11+
APIKey string `json:"api_key"`
12+
Project *projects.ProjectModel `json:"project"`
13+
FeatureStates []*features.FeatureStateModel `json:"feature_states"`
14+
IdentityOverrides []*identities.IdentityModel `json:"identity_overrides"`
1915
}

0 commit comments

Comments
 (0)