diff --git a/src/main/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProvider.java b/src/main/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProvider.java index 6176b85..50357b0 100644 --- a/src/main/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProvider.java +++ b/src/main/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProvider.java @@ -619,18 +619,36 @@ protected float calculateVariantHash(String contextValue, String flagKey, String * @param context the evaluation context * @param reportExposure whether to track exposure events for flag evaluations * @return list of selected variants for all flags where a variant was selected + * @deprecated Use {@link #getAllVariantsByFlag(Map, boolean)} which returns a map keyed by + * flag key, so each result can be associated with the flag it was selected for. */ + @Deprecated public List> getAllVariants(Map context, boolean reportExposure) { - List> results = new ArrayList<>(); + return new ArrayList<>(getAllVariantsByFlag(context, reportExposure).values()); + } + + /** + * Evaluates all flags and returns their selected variants keyed by flag key. + *

+ * Evaluates all flag definitions for the given context and returns a map from + * flag key to the successfully selected variant. Flags whose evaluation fell + * back (i.e., did not produce a successful variant) are omitted from the map. + *

+ * + * @param context the evaluation context + * @param reportExposure whether to track exposure events for flag evaluations + * @return map from flag key to selected variant for all flags where a variant was selected + */ + public Map> getAllVariantsByFlag(Map context, boolean reportExposure) { + Map> results = new HashMap<>(); Map definitions = flagDefinitions.get(); for (ExperimentationFlag flag : definitions.values()) { SelectedVariant fallback = new SelectedVariant<>(null); SelectedVariant result = getVariant(flag.getKey(), fallback, context, reportExposure); - // Only include successfully selected variants (not fallbacks) if (result.isSuccess()) { - results.add(result); + results.put(flag.getKey(), result); } } diff --git a/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java b/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java index 3e3e3af..44bae7d 100644 --- a/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java +++ b/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java @@ -999,10 +999,10 @@ public void testUseMostRecentPolledFlagDefinitions() throws Exception { } // #endregion - // #region getAllVariants Tests + // #region getAllVariantsByFlag Tests @Test - public void testGetAllVariantsReturnsAllSuccessfullySelectedVariants() { + public void testGetAllVariantsByFlagReturnsAllSuccessfullySelectedVariants() { // Create multiple flags with 100% rollout List flags = Arrays.asList( new FlagDefinition("flag-1", distinctIdContextKey, @@ -1021,32 +1021,32 @@ public void testGetAllVariantsReturnsAllSuccessfullySelectedVariants() { provider.startPollingForDefinitions(); Map context = buildContext("user-123"); - List> results = provider.getAllVariants(context, true); + Map> results = provider.getAllVariantsByFlag(context, true); assertEquals(3, results.size()); - - // Verify all variants are successful (not fallbacks) - for (SelectedVariant variant : results) { - assertTrue(variant.isSuccess()); - assertNotNull(variant.getVariantKey()); - } + assertEquals("variant-a", results.get("flag-1").getVariantKey()); + assertEquals("value-a", results.get("flag-1").getVariantValue()); + assertEquals("variant-b", results.get("flag-2").getVariantKey()); + assertEquals("value-b", results.get("flag-2").getVariantValue()); + assertEquals("variant-c", results.get("flag-3").getVariantKey()); + assertEquals("value-c", results.get("flag-3").getVariantValue()); } @Test - public void testGetAllVariantsReturnsEmptyListWhenNoFlagsDefined() { + public void testGetAllVariantsByFlagReturnsEmptyMapWhenNoFlagsDefined() { String response = "{\"flags\":[]}"; provider = createProviderWithResponse(response); provider.startPollingForDefinitions(); Map context = buildContext("user-123"); - List> results = provider.getAllVariants(context, true); + Map> results = provider.getAllVariantsByFlag(context, true); assertNotNull(results); assertEquals(0, results.size()); } @Test - public void testGetAllVariantsReturnsOnlySuccessfulVariants() { + public void testGetAllVariantsByFlagOmitsFallbacks() { // Create flags with mixed rollout percentages List flags = Arrays.asList( new FlagDefinition("flag-success-1", distinctIdContextKey, @@ -1065,19 +1065,18 @@ public void testGetAllVariantsReturnsOnlySuccessfulVariants() { provider.startPollingForDefinitions(); Map context = buildContext("user-123"); - List> results = provider.getAllVariants(context, true); + Map> results = provider.getAllVariantsByFlag(context, true); - // Should only return the 2 successful variants + // Should only return the 2 successful variants, keyed by flag assertEquals(2, results.size()); - - // Verify all returned variants are successful - for (SelectedVariant variant : results) { - assertTrue(variant.isSuccess()); - } + assertTrue(results.containsKey("flag-success-1")); + assertTrue(results.containsKey("flag-success-2")); + assertFalse(results.containsKey("flag-fail-1")); } + @SuppressWarnings("deprecation") @Test - public void testGetAllVariantsTracksExposureEventsWhenReportExposureTrue() { + public void testDeprecatedGetAllVariantsTracksExposureEventsWhenReportExposureTrue() { // Create 3 flags with 100% rollout List flags = Arrays.asList( new FlagDefinition("flag-1", distinctIdContextKey, @@ -1104,8 +1103,9 @@ public void testGetAllVariantsTracksExposureEventsWhenReportExposureTrue() { assertEquals(3, results.size()); } + @SuppressWarnings("deprecation") @Test - public void testGetAllVariantsDoesNotTrackExposureEventsWhenReportExposureFalse() { + public void testDeprecatedGetAllVariantsDoesNotTrackExposureEventsWhenReportExposureFalse() { // Create 3 flags with 100% rollout List flags = Arrays.asList( new FlagDefinition("flag-1", distinctIdContextKey, @@ -1138,7 +1138,7 @@ public void testGetAllVariantsDoesNotTrackExposureEventsWhenReportExposureFalse( } @Test - public void testGetAllVariantsReturnsVariantsWithExperimentMetadata() { + public void testGetAllVariantsByFlagReturnsVariantsWithExperimentMetadata() { // Create test UUIDs UUID experimentId1 = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); UUID experimentId2 = UUID.fromString("223e4567-e89b-12d3-a456-426614174001"); @@ -1160,29 +1160,17 @@ public void testGetAllVariantsReturnsVariantsWithExperimentMetadata() { provider.startPollingForDefinitions(); Map context = buildContext("user-123"); - List> results = provider.getAllVariants(context, true); + Map> results = provider.getAllVariantsByFlag(context, true); assertEquals(2, results.size()); - // Find variants by their value (order is not guaranteed from HashMap) - SelectedVariant variantA = null; - SelectedVariant variantB = null; - for (SelectedVariant variant : results) { - if ("value-a".equals(variant.getVariantValue())) { - variantA = variant; - } else if ("value-b".equals(variant.getVariantValue())) { - variantB = variant; - } - } - - // Verify both variants were found with their experiment metadata - assertNotNull("variant-a should be present", variantA); - assertNotNull(variantA.getExperimentId()); + SelectedVariant variantA = results.get("flag-1"); + assertNotNull("flag-1 should be present", variantA); assertEquals(experimentId1, variantA.getExperimentId()); assertEquals(true, variantA.getIsExperimentActive()); - assertNotNull("variant-b should be present", variantB); - assertNotNull(variantB.getExperimentId()); + SelectedVariant variantB = results.get("flag-2"); + assertNotNull("flag-2 should be present", variantB); assertEquals(experimentId2, variantB.getExperimentId()); assertEquals(false, variantB.getIsExperimentActive()); }