|
1 | 1 | /**************************************************************************** |
2 | | - * Copyright 2017-2022, 2024, Optimizely, Inc. and contributors * |
| 2 | + * Copyright 2017-2022, 2024, 2026, Optimizely, Inc. and contributors * |
3 | 3 | * * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * |
5 | 5 | * you may not use this file except in compliance with the License. * |
@@ -328,43 +328,66 @@ public List<DecisionResponse<FeatureDecision>> getVariationsForFeatureList(@Non |
328 | 328 |
|
329 | 329 | // Evaluate global holdouts at flag level (before any rules are iterated) |
330 | 330 | List<Holdout> globalHoldouts = projectConfig.getGlobalHoldouts(); |
| 331 | + FeatureDecision globalHoldoutDecision = null; |
| 332 | + boolean excludeTargetedDeliveries = false; |
331 | 333 | if (!globalHoldouts.isEmpty()) { |
332 | 334 | for (Holdout holdout : globalHoldouts) { |
333 | 335 | DecisionResponse<Variation> holdoutDecision = getVariationForHoldout(holdout, user, projectConfig); |
334 | 336 | reasons.merge(holdoutDecision.getReasons()); |
335 | 337 | if (holdoutDecision.getResult() != null) { |
336 | | - decisions.add(new DecisionResponse<>(new FeatureDecision(holdout, holdoutDecision.getResult(), FeatureDecision.DecisionSource.HOLDOUT), reasons)); |
337 | | - continue flagLoop; |
| 338 | + globalHoldoutDecision = new FeatureDecision(holdout, holdoutDecision.getResult(), FeatureDecision.DecisionSource.HOLDOUT); |
| 339 | + excludeTargetedDeliveries = holdout.isExcludeTargetedDeliveries(); |
| 340 | + break; |
338 | 341 | } |
339 | 342 | } |
340 | 343 | } |
341 | 344 |
|
342 | | - DecisionResponse<FeatureDecision> decisionVariationResponse = getVariationFromExperiment(projectConfig, featureFlag, user, options, userProfileTracker, decisionPath); |
343 | | - reasons.merge(decisionVariationResponse.getReasons()); |
| 345 | + if (globalHoldoutDecision != null && !excludeTargetedDeliveries) { |
| 346 | + decisions.add(new DecisionResponse<>(globalHoldoutDecision, reasons)); |
| 347 | + continue flagLoop; |
| 348 | + } |
| 349 | + |
| 350 | + if (globalHoldoutDecision != null && excludeTargetedDeliveries) { |
| 351 | + reasons.addInfo("Holdout \"%s\" has excludeTargetedDeliveries enabled, continuing to rollout evaluation.", globalHoldoutDecision.experiment.getKey()); |
| 352 | + } |
344 | 353 |
|
345 | | - FeatureDecision decision = decisionVariationResponse.getResult(); |
346 | | - boolean error = decisionVariationResponse.isError(); |
| 354 | + if (globalHoldoutDecision == null) { |
| 355 | + DecisionResponse<FeatureDecision> decisionVariationResponse = getVariationFromExperiment(projectConfig, featureFlag, user, options, userProfileTracker, decisionPath); |
| 356 | + reasons.merge(decisionVariationResponse.getReasons()); |
347 | 357 |
|
348 | | - if (decision != null) { |
349 | | - decisions.add(new DecisionResponse(decision, reasons, error, decision.cmabUuid)); |
350 | | - continue; |
| 358 | + FeatureDecision decision = decisionVariationResponse.getResult(); |
| 359 | + boolean error = decisionVariationResponse.isError(); |
| 360 | + |
| 361 | + if (decision != null) { |
| 362 | + decisions.add(new DecisionResponse(decision, reasons, error, decision.cmabUuid)); |
| 363 | + continue flagLoop; |
| 364 | + } |
351 | 365 | } |
352 | 366 |
|
353 | 367 | DecisionResponse<FeatureDecision> decisionFeatureResponse = getVariationForFeatureInRollout(featureFlag, user, projectConfig); |
354 | 368 | reasons.merge(decisionFeatureResponse.getReasons()); |
355 | | - decision = decisionFeatureResponse.getResult(); |
| 369 | + FeatureDecision decision = decisionFeatureResponse.getResult(); |
356 | 370 |
|
357 | | - String message; |
358 | | - if (decision.variation == null) { |
359 | | - message = reasons.addInfo("The user \"%s\" was not bucketed into a rollout for feature flag \"%s\".", |
| 371 | + if (decision != null && decision.variation != null) { |
| 372 | + if (globalHoldoutDecision != null) { |
| 373 | + decision.setHoldoutDecision(globalHoldoutDecision); |
| 374 | + } |
| 375 | + String message = reasons.addInfo("The user \"%s\" was bucketed into a rollout for feature flag \"%s\".", |
360 | 376 | user.getUserId(), featureFlag.getKey()); |
| 377 | + logger.info(message); |
| 378 | + decisions.add(new DecisionResponse(decision, reasons)); |
361 | 379 | } else { |
362 | | - message = reasons.addInfo("The user \"%s\" was bucketed into a rollout for feature flag \"%s\".", |
| 380 | + if (globalHoldoutDecision != null) { |
| 381 | + if (decision == null) { |
| 382 | + decision = new FeatureDecision(null, null, null); |
| 383 | + } |
| 384 | + decision.setHoldoutDecision(globalHoldoutDecision); |
| 385 | + } |
| 386 | + String message = reasons.addInfo("The user \"%s\" was not bucketed into a rollout for feature flag \"%s\".", |
363 | 387 | user.getUserId(), featureFlag.getKey()); |
| 388 | + logger.info(message); |
| 389 | + decisions.add(new DecisionResponse(decision, reasons)); |
364 | 390 | } |
365 | | - logger.info(message); |
366 | | - |
367 | | - decisions.add(new DecisionResponse(decision, reasons)); |
368 | 391 | } |
369 | 392 |
|
370 | 393 | if (userProfileService != null && !ignoreUPS) { |
|
0 commit comments