From c33de775de406844dcf7a0a436a6102b10605589 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Tue, 28 Apr 2026 09:56:53 -0400 Subject: [PATCH 1/2] add getAllVariantsByFlag --- .../provider/LocalFlagsProvider.java | 24 +++- .../provider/LocalFlagsProviderTest.java | 131 ++++++++---------- 2 files changed, 82 insertions(+), 73 deletions(-) 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..a7ff79a 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,59 @@ 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()); + assertTrue(results.containsKey("flag-success-1")); + assertTrue(results.containsKey("flag-success-2")); + assertFalse(results.containsKey("flag-fail-1")); + } - // Verify all returned variants are successful - for (SelectedVariant variant : results) { - assertTrue(variant.isSuccess()); - } + @Test + public void testGetAllVariantsByFlagReturnsVariantsWithExperimentMetadata() { + // Create test UUIDs + UUID experimentId1 = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + UUID experimentId2 = UUID.fromString("223e4567-e89b-12d3-a456-426614174001"); + + // Create flags with experiment metadata + List flags = Arrays.asList( + new FlagDefinition("flag-1", distinctIdContextKey, + Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f)), + Arrays.asList(new Rollout(1.0f)), + null, experimentId1, true), + new FlagDefinition("flag-2", distinctIdContextKey, + Arrays.asList(new Variant("variant-b", "value-b", false, 1.0f)), + Arrays.asList(new Rollout(1.0f)), + null, experimentId2, false) + ); + + String response = buildMultipleFlagsResponse(flags); + provider = createProviderWithResponse(response); + provider.startPollingForDefinitions(); + + Map context = buildContext("user-123"); + Map> results = provider.getAllVariantsByFlag(context, true); + + assertEquals(2, results.size()); + + SelectedVariant variantA = results.get("flag-1"); + assertNotNull("flag-1 should be present", variantA); + assertEquals(experimentId1, variantA.getExperimentId()); + assertEquals(true, variantA.getIsExperimentActive()); + + SelectedVariant variantB = results.get("flag-2"); + assertNotNull("flag-2 should be present", variantB); + assertEquals(experimentId2, variantB.getExperimentId()); + assertEquals(false, variantB.getIsExperimentActive()); } + // Tests below cover the deprecated getAllVariants() wrapper. They exercise both reportExposure + // values to ensure the wrapper preserves exposure-tracking behavior of the underlying call. + + @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 +1144,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, @@ -1137,56 +1178,6 @@ public void testGetAllVariantsDoesNotTrackExposureEventsWhenReportExposureFalse( } } - @Test - public void testGetAllVariantsReturnsVariantsWithExperimentMetadata() { - // Create test UUIDs - UUID experimentId1 = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); - UUID experimentId2 = UUID.fromString("223e4567-e89b-12d3-a456-426614174001"); - - // Create flags with experiment metadata - List flags = Arrays.asList( - new FlagDefinition("flag-1", distinctIdContextKey, - Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f)), - Arrays.asList(new Rollout(1.0f)), - null, experimentId1, true), - new FlagDefinition("flag-2", distinctIdContextKey, - Arrays.asList(new Variant("variant-b", "value-b", false, 1.0f)), - Arrays.asList(new Rollout(1.0f)), - null, experimentId2, false) - ); - - String response = buildMultipleFlagsResponse(flags); - provider = createProviderWithResponse(response); - provider.startPollingForDefinitions(); - - Map context = buildContext("user-123"); - List> results = provider.getAllVariants(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()); - assertEquals(experimentId1, variantA.getExperimentId()); - assertEquals(true, variantA.getIsExperimentActive()); - - assertNotNull("variant-b should be present", variantB); - assertNotNull(variantB.getExperimentId()); - assertEquals(experimentId2, variantB.getExperimentId()); - assertEquals(false, variantB.getIsExperimentActive()); - } - // #endregion // #region isQaTester Calculation Tests From d4980413ea1ce0c6c1d9a0bbd68f7bc4aac85cbd Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Tue, 28 Apr 2026 09:59:29 -0400 Subject: [PATCH 2/2] shrink diff --- .../provider/LocalFlagsProviderTest.java | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) 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 a7ff79a..44bae7d 100644 --- a/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java +++ b/src/test/java/com/mixpanel/mixpanelapi/featureflags/provider/LocalFlagsProviderTest.java @@ -1074,47 +1074,6 @@ public void testGetAllVariantsByFlagOmitsFallbacks() { assertFalse(results.containsKey("flag-fail-1")); } - @Test - public void testGetAllVariantsByFlagReturnsVariantsWithExperimentMetadata() { - // Create test UUIDs - UUID experimentId1 = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); - UUID experimentId2 = UUID.fromString("223e4567-e89b-12d3-a456-426614174001"); - - // Create flags with experiment metadata - List flags = Arrays.asList( - new FlagDefinition("flag-1", distinctIdContextKey, - Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f)), - Arrays.asList(new Rollout(1.0f)), - null, experimentId1, true), - new FlagDefinition("flag-2", distinctIdContextKey, - Arrays.asList(new Variant("variant-b", "value-b", false, 1.0f)), - Arrays.asList(new Rollout(1.0f)), - null, experimentId2, false) - ); - - String response = buildMultipleFlagsResponse(flags); - provider = createProviderWithResponse(response); - provider.startPollingForDefinitions(); - - Map context = buildContext("user-123"); - Map> results = provider.getAllVariantsByFlag(context, true); - - assertEquals(2, results.size()); - - SelectedVariant variantA = results.get("flag-1"); - assertNotNull("flag-1 should be present", variantA); - assertEquals(experimentId1, variantA.getExperimentId()); - assertEquals(true, variantA.getIsExperimentActive()); - - SelectedVariant variantB = results.get("flag-2"); - assertNotNull("flag-2 should be present", variantB); - assertEquals(experimentId2, variantB.getExperimentId()); - assertEquals(false, variantB.getIsExperimentActive()); - } - - // Tests below cover the deprecated getAllVariants() wrapper. They exercise both reportExposure - // values to ensure the wrapper preserves exposure-tracking behavior of the underlying call. - @SuppressWarnings("deprecation") @Test public void testDeprecatedGetAllVariantsTracksExposureEventsWhenReportExposureTrue() { @@ -1178,6 +1137,44 @@ public void testDeprecatedGetAllVariantsDoesNotTrackExposureEventsWhenReportExpo } } + @Test + public void testGetAllVariantsByFlagReturnsVariantsWithExperimentMetadata() { + // Create test UUIDs + UUID experimentId1 = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + UUID experimentId2 = UUID.fromString("223e4567-e89b-12d3-a456-426614174001"); + + // Create flags with experiment metadata + List flags = Arrays.asList( + new FlagDefinition("flag-1", distinctIdContextKey, + Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f)), + Arrays.asList(new Rollout(1.0f)), + null, experimentId1, true), + new FlagDefinition("flag-2", distinctIdContextKey, + Arrays.asList(new Variant("variant-b", "value-b", false, 1.0f)), + Arrays.asList(new Rollout(1.0f)), + null, experimentId2, false) + ); + + String response = buildMultipleFlagsResponse(flags); + provider = createProviderWithResponse(response); + provider.startPollingForDefinitions(); + + Map context = buildContext("user-123"); + Map> results = provider.getAllVariantsByFlag(context, true); + + assertEquals(2, results.size()); + + SelectedVariant variantA = results.get("flag-1"); + assertNotNull("flag-1 should be present", variantA); + assertEquals(experimentId1, variantA.getExperimentId()); + assertEquals(true, variantA.getIsExperimentActive()); + + SelectedVariant variantB = results.get("flag-2"); + assertNotNull("flag-2 should be present", variantB); + assertEquals(experimentId2, variantB.getExperimentId()); + assertEquals(false, variantB.getIsExperimentActive()); + } + // #endregion // #region isQaTester Calculation Tests