Skip to content

Commit f00c28e

Browse files
authored
filter gcir option (#260)
add option to filter to specific entity types for gcir
1 parent 4ca690b commit f00c28e

5 files changed

+153
-13
lines changed

client.go

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ func (c *Client) GetClientInitializeResponseImpl(user User, options *GCIROptions
338338
TargetAppID: options.TargetAppID,
339339
Hash: options.HashAlgorithm,
340340
IncludeConfigType: options.IncludeConfigType,
341+
ConfigTypesToInclude: options.ConfigTypesToInclude,
341342
})
342343
}
343344

client_initialize_response.go

+27-13
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,6 @@ type SDKInfo struct {
2323
SDKVersion string `json:"sdkVersion"`
2424
}
2525

26-
type ConfigType = string
27-
28-
const (
29-
FeatureGateType ConfigType = "feature_gate"
30-
HoldoutType ConfigType = "holdout"
31-
SegmentType ConfigType = "segment"
32-
DynamicConfigType ConfigType = "dynamic_config"
33-
ExperimentType ConfigType = "experiment"
34-
AutotuneType ConfigType = "autotune"
35-
LayerType ConfigType = "layer"
36-
UnknownType ConfigType = "unknown"
37-
)
38-
3926
type baseSpecInitializeResponse struct {
4027
Name string `json:"name"`
4128
RuleID string `json:"rule_id"`
@@ -254,13 +241,34 @@ func getClientInitializeResponse(
254241
}
255242
}
256243

244+
configTypesMap := make(map[ConfigType]struct{})
245+
if context.ConfigTypesToInclude != nil {
246+
for _, configType := range context.ConfigTypesToInclude {
247+
configTypesMap[configType] = struct{}{}
248+
}
249+
}
250+
251+
shouldFilterConfigType := func(specType ConfigType) bool {
252+
if context.ConfigTypesToInclude == nil {
253+
return false
254+
}
255+
if _, exists := configTypesMap[specType]; exists {
256+
return false
257+
}
258+
return true
259+
260+
}
261+
257262
featureGates := make(map[string]GateInitializeResponse)
258263
dynamicConfigs := make(map[string]ConfigInitializeResponse)
259264
layerConfigs := make(map[string]LayerInitializeResponse)
260265
for name, spec := range e.store.featureGates {
261266
if !spec.hasTargetAppID(appId) {
262267
continue
263268
}
269+
if shouldFilterConfigType(getConfigType(spec)) {
270+
continue
271+
}
264272
if filterByEntities {
265273
if _, ok := gatesLookup[name]; !ok {
266274
continue
@@ -275,6 +283,9 @@ func getClientInitializeResponse(
275283
if !spec.hasTargetAppID(appId) {
276284
continue
277285
}
286+
if shouldFilterConfigType(getConfigType(spec)) {
287+
continue
288+
}
278289
if filterByEntities {
279290
if _, ok := configsLookup[name]; !ok {
280291
continue
@@ -287,6 +298,9 @@ func getClientInitializeResponse(
287298
if !spec.hasTargetAppID(appId) {
288299
continue
289300
}
301+
if shouldFilterConfigType(getConfigType(spec)) {
302+
continue
303+
}
290304
hashedName, res := layerToResponse(name, spec)
291305
layerConfigs[hashedName] = res
292306
}

client_initialize_response_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,116 @@ func TestClientInitializeResponseOptions(t *testing.T) {
148148
}
149149
}
150150

151+
func TestClientInitializeResponseFilterOptions(t *testing.T) {
152+
user := User{
153+
UserID: "123",
154+
155+
Country: "US",
156+
Custom: map[string]interface{}{"test": "123"},
157+
CustomIDs: map[string]string{"stableID": "12345"},
158+
}
159+
160+
InitializeWithOptions(secret, &Options{
161+
OutputLoggerOptions: getOutputLoggerOptionsForTest(t),
162+
StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t),
163+
})
164+
defer ShutdownAndDangerouslyClearInstance()
165+
166+
tests := []struct {
167+
name string
168+
filter *GCIROptions
169+
expectGate bool
170+
expectDC bool
171+
expectExp bool
172+
expectAT bool
173+
expectLayer bool
174+
}{
175+
{
176+
name: "Include all entities",
177+
filter: &GCIROptions{HashAlgorithm: "none"},
178+
expectGate: true, expectDC: true, expectExp: true, expectAT: true, expectLayer: true,
179+
},
180+
{
181+
name: "Only feature gates",
182+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{FeatureGateType}},
183+
expectGate: true, expectDC: false, expectExp: false, expectAT: false, expectLayer: false,
184+
},
185+
{
186+
name: "Only dynamic configs",
187+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{DynamicConfigType}},
188+
expectGate: false, expectDC: true, expectExp: false, expectAT: false, expectLayer: false,
189+
},
190+
{
191+
name: "Only experiments",
192+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{ExperimentType}},
193+
expectGate: false, expectDC: false, expectExp: true, expectAT: false, expectLayer: false,
194+
},
195+
{
196+
name: "Only autotune configs",
197+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{AutotuneType}},
198+
expectGate: false, expectDC: false, expectExp: false, expectAT: true, expectLayer: false,
199+
},
200+
{
201+
name: "Only layers",
202+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{LayerType}},
203+
expectGate: false, expectDC: false, expectExp: false, expectAT: false, expectLayer: true,
204+
},
205+
{
206+
name: "Include multiple entities (feature gates and experiments)",
207+
filter: &GCIROptions{HashAlgorithm: "none", ConfigTypesToInclude: []ConfigType{FeatureGateType, ExperimentType}},
208+
expectGate: true, expectDC: false, expectExp: true, expectAT: false, expectLayer: false,
209+
},
210+
}
211+
212+
for _, tt := range tests {
213+
t.Run(tt.name, func(t *testing.T) {
214+
response := GetClientInitializeResponseWithOptions(user, tt.filter)
215+
216+
var featureGate GateInitializeResponse
217+
var dynamicConfig ConfigInitializeResponse
218+
var experiment ConfigInitializeResponse
219+
var autotune ConfigInitializeResponse
220+
var layer LayerInitializeResponse
221+
222+
for _, g := range response.FeatureGates {
223+
if g.Name == "test_public" {
224+
featureGate = g
225+
}
226+
}
227+
for _, c := range response.DynamicConfigs {
228+
if c.Name == "test_custom_config" {
229+
dynamicConfig = c
230+
} else if c.Name == "test_experiment_with_targeting" {
231+
experiment = c
232+
} else if c.Name == "test_autotune" {
233+
autotune = c
234+
}
235+
}
236+
for _, l := range response.LayerConfigs {
237+
if l.Name == "Basic_test_layer" {
238+
layer = l
239+
}
240+
}
241+
242+
if (featureGate.Name != "") != tt.expectGate {
243+
t.Errorf("Feature gate presence mismatch: got %v, want %v", featureGate.Name != "", tt.expectGate)
244+
}
245+
if (dynamicConfig.Name != "") != tt.expectDC {
246+
t.Errorf("Dynamic config presence mismatch: got %v, want %v", dynamicConfig.Name != "", tt.expectDC)
247+
}
248+
if (experiment.Name != "") != tt.expectExp {
249+
t.Errorf("Experiment presence mismatch: got %v, want %v", experiment.Name != "", tt.expectExp)
250+
}
251+
if (autotune.Name != "") != tt.expectAT {
252+
t.Errorf("Autotune config presence mismatch: got %v, want %v", autotune.Name != "", tt.expectAT)
253+
}
254+
if (layer.Name != "") != tt.expectLayer {
255+
t.Errorf("Layer presence mismatch: got %v, want %v", layer.Name != "", tt.expectLayer)
256+
}
257+
})
258+
}
259+
}
260+
151261
func filterHttpResponseAndReadBody(httpResponse *http.Response) ([]byte, error) {
152262
var interfaceBody ClientInitializeResponse
153263
// Initialize nullable fields so that JSON Unmarshal doesn't convert to null

statsig_context.go

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type evalContext struct {
2525
DisableLogExposures bool
2626
PersistedValues UserPersistedValues
2727
IncludeConfigType bool
28+
ConfigTypesToInclude []ConfigType
2829
}
2930

3031
type initContext struct {

statsig_options.go

+14
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,18 @@ type GCIROptions struct {
9191
TargetAppID string
9292
HashAlgorithm string
9393
IncludeConfigType bool
94+
ConfigTypesToInclude []ConfigType
9495
}
96+
97+
type ConfigType = string
98+
99+
const (
100+
FeatureGateType ConfigType = "feature_gate"
101+
HoldoutType ConfigType = "holdout"
102+
SegmentType ConfigType = "segment"
103+
DynamicConfigType ConfigType = "dynamic_config"
104+
ExperimentType ConfigType = "experiment"
105+
AutotuneType ConfigType = "autotune"
106+
LayerType ConfigType = "layer"
107+
UnknownType ConfigType = "unknown"
108+
)

0 commit comments

Comments
 (0)