@@ -21,6 +21,7 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
24
+ datafileEntities "github.com/optimizely/go-sdk/v2/pkg/config/datafileprojectconfig/entities"
24
25
"github.com/optimizely/go-sdk/v2/pkg/config/datafileprojectconfig/mappers"
25
26
"github.com/optimizely/go-sdk/v2/pkg/entities"
26
27
"github.com/optimizely/go-sdk/v2/pkg/logging"
@@ -58,8 +59,12 @@ type DatafileProjectConfig struct {
58
59
sdkKey string
59
60
environmentKey string
60
61
region string
61
-
62
- flagVariationsMap map [string ][]entities.Variation
62
+ flagVariationsMap map [string ][]entities.Variation
63
+ holdoutIDMap map [string ]entities.Holdout
64
+ globalHoldouts []entities.Holdout
65
+ includedHoldouts map [string ][]entities.Holdout
66
+ excludedHoldouts map [string ][]entities.Holdout
67
+ flagHoldoutsMap map [string ][]entities.Holdout
63
68
}
64
69
65
70
// GetDatafile returns a string representation of the environment's datafile
@@ -266,6 +271,56 @@ func (c DatafileProjectConfig) GetGroupByID(groupID string) (entities.Group, err
266
271
return entities.Group {}, fmt .Errorf (`group with ID "%s" not found` , groupID )
267
272
}
268
273
274
+ // GetHoldoutsForFlag returns the holdouts that apply to a specific flag
275
+ func (c * DatafileProjectConfig ) GetHoldoutsForFlag (flagKey string ) []entities.Holdout {
276
+ // Get flag ID from key
277
+ feature , exists := c .featureMap [flagKey ]
278
+ if ! exists {
279
+ return []entities.Holdout {}
280
+ }
281
+
282
+ flagID := feature .ID
283
+
284
+ // Check cache first
285
+ if cachedHoldouts , exists := c .flagHoldoutsMap [flagID ]; exists {
286
+ return cachedHoldouts
287
+ }
288
+
289
+ holdouts := []entities.Holdout {}
290
+
291
+ // Add global holdouts that don't exclude this flag
292
+ for _ , holdout := range c .globalHoldouts {
293
+ isExcluded := false
294
+ for _ , excludedFlagID := range holdout .ExcludedFlags {
295
+ if excludedFlagID == flagID {
296
+ isExcluded = true
297
+ break
298
+ }
299
+ }
300
+ if ! isExcluded {
301
+ holdouts = append (holdouts , holdout )
302
+ }
303
+ }
304
+
305
+ // Add holdouts that specifically include this flag
306
+ if includedHoldouts , exists := c .includedHoldouts [flagID ]; exists {
307
+ holdouts = append (holdouts , includedHoldouts ... )
308
+ }
309
+
310
+ // Cache the result
311
+ c .flagHoldoutsMap [flagID ] = holdouts
312
+
313
+ return holdouts
314
+ }
315
+
316
+ // GetHoldout returns a holdout by its ID
317
+ func (c DatafileProjectConfig ) GetHoldout (holdoutID string ) (entities.Holdout , error ) {
318
+ if holdout , ok := c .holdoutIDMap [holdoutID ]; ok {
319
+ return holdout , nil
320
+ }
321
+ return entities.Holdout {}, fmt .Errorf (`holdout with ID "%s" not found` , holdoutID )
322
+ }
323
+
269
324
// SendFlagDecisions determines whether impressions events are sent for ALL decision types
270
325
func (c DatafileProjectConfig ) SendFlagDecisions () bool {
271
326
return c .sendFlagDecisions
@@ -324,6 +379,48 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
324
379
audienceMap , audienceSegmentList := mappers .MapAudiences (append (datafile .TypedAudiences , datafile .Audiences ... ))
325
380
flagVariationsMap := mappers .MapFlagVariations (featureMap )
326
381
382
+ // Process holdouts
383
+ holdoutIDMap := make (map [string ]entities.Holdout )
384
+ globalHoldouts := []entities.Holdout {}
385
+ includedHoldouts := make (map [string ][]entities.Holdout )
386
+ excludedHoldouts := make (map [string ][]entities.Holdout )
387
+ flagHoldoutsMap := make (map [string ][]entities.Holdout )
388
+
389
+ for _ , datafileHoldout := range datafile .Holdouts {
390
+ // Only process running holdouts
391
+ if datafileHoldout .Status != datafileEntities .HoldoutStatusRunning {
392
+ continue
393
+ }
394
+
395
+ // Create runtime holdout entity
396
+ holdout := entities.Holdout {
397
+ ID : datafileHoldout .ID ,
398
+ Key : datafileHoldout .Key ,
399
+ Status : entities .HoldoutStatus (datafileHoldout .Status ),
400
+ IncludedFlags : datafileHoldout .IncludedFlags ,
401
+ ExcludedFlags : datafileHoldout .ExcludedFlags ,
402
+ }
403
+
404
+ // Add to ID map
405
+ holdoutIDMap [holdout .ID ] = holdout
406
+
407
+ // Categorize holdouts based on flag targeting
408
+ if len (datafileHoldout .IncludedFlags ) == 0 {
409
+ // This is a global holdout (applies to all flags unless excluded)
410
+ globalHoldouts = append (globalHoldouts , holdout )
411
+
412
+ // Add to excluded flags map
413
+ for _ , flagID := range datafileHoldout .ExcludedFlags {
414
+ excludedHoldouts [flagID ] = append (excludedHoldouts [flagID ], holdout )
415
+ }
416
+ } else {
417
+ // This holdout specifically includes certain flags
418
+ for _ , flagID := range datafileHoldout .IncludedFlags {
419
+ includedHoldouts [flagID ] = append (includedHoldouts [flagID ], holdout )
420
+ }
421
+ }
422
+ }
423
+
327
424
attributeKeyMap := make (map [string ]entities.Attribute )
328
425
attributeIDToKeyMap := make (map [string ]string )
329
426
@@ -365,6 +462,11 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
365
462
attributeKeyMap : attributeKeyMap ,
366
463
attributeIDToKeyMap : attributeIDToKeyMap ,
367
464
region : region ,
465
+ holdoutIDMap : holdoutIDMap ,
466
+ globalHoldouts : globalHoldouts ,
467
+ includedHoldouts : includedHoldouts ,
468
+ excludedHoldouts : excludedHoldouts ,
469
+ flagHoldoutsMap : flagHoldoutsMap ,
368
470
}
369
471
370
472
logger .Info ("Datafile is valid." )
0 commit comments