1
1
/*
2
- * Copyright 2017-2022, Optimizely
2
+ * Copyright 2017-2022, 2024 Optimizely
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.
16
16
17
17
using System ;
18
18
using System . Collections . Generic ;
19
+ using System . Linq ;
19
20
using OptimizelySDK . Entity ;
20
21
using OptimizelySDK . ErrorHandler ;
21
22
using OptimizelySDK . Logger ;
@@ -706,9 +707,9 @@ OptimizelyDecideOption[] options
706
707
/// <summary>
707
708
/// Get the variation the user is bucketed into for the FeatureFlag
708
709
/// </summary>
709
- /// <param name = "featureFlag" >The feature flag the user wants to access.</param>
710
- /// <param name = "userId" >User Identifier </param>
711
- /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile .</param>
710
+ /// <param name= "featureFlag">The feature flag the user wants to access.</param>
711
+ /// <param name="user">The user context. </param>
712
+ /// <param name="config" >The project config .</param>
712
713
/// <returns>null if the user is not bucketed into any variation or the FeatureDecision entity if the user is
713
714
/// successfully bucketed.</returns>
714
715
public virtual Result < FeatureDecision > GetVariationForFeature ( FeatureFlag featureFlag ,
@@ -719,53 +720,168 @@ public virtual Result<FeatureDecision> GetVariationForFeature(FeatureFlag featur
719
720
new OptimizelyDecideOption [ ] { } ) ;
720
721
}
721
722
722
- /// <summary>
723
- /// Get the variation the user is bucketed into for the FeatureFlag
724
- /// </summary>
725
- /// <param name = "featureFlag" >The feature flag the user wants to access.</param>
726
- /// <param name = "userId" >User Identifier</param>
727
- /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile.</param>
728
- /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile.</param>
729
- /// <param name = "options" >An array of decision options.</param>
730
- /// <returns>null if the user is not bucketed into any variation or the FeatureDecision entity if the user is
731
- /// successfully bucketed.</returns>
732
- public virtual Result < FeatureDecision > GetVariationForFeature ( FeatureFlag featureFlag ,
723
+ private class UserProfileTracker
724
+ {
725
+ public UserProfile UserProfile { get ; set ; }
726
+ public bool ProfileUpdated { get ; set ; }
727
+
728
+ public UserProfileTracker ( UserProfile userProfile , bool profileUpdated )
729
+ {
730
+ UserProfile = userProfile ;
731
+ ProfileUpdated = profileUpdated ;
732
+ }
733
+ }
734
+
735
+ void SaveUserProfile ( UserProfile userProfile )
736
+ {
737
+ if ( UserProfileService == null )
738
+ {
739
+ return ;
740
+ }
741
+
742
+ try
743
+ {
744
+ UserProfileService . Save ( userProfile . ToMap ( ) ) ;
745
+ Logger . Log ( LogLevel . INFO ,
746
+ $ "Saved user profile of user \" { userProfile . UserId } \" .") ;
747
+ }
748
+ catch ( Exception exception )
749
+ {
750
+ Logger . Log ( LogLevel . WARN ,
751
+ $ "Failed to save user profile of user \" { userProfile . UserId } \" .") ;
752
+ ErrorHandler . HandleError ( new Exceptions . OptimizelyRuntimeException ( exception . Message ) ) ;
753
+ }
754
+ }
755
+
756
+ private UserProfile GetUserProfile ( String userId , DecisionReasons reasons )
757
+ {
758
+ UserProfile userProfile = null ;
759
+
760
+ try
761
+ {
762
+ var userProfileMap = UserProfileService . Lookup ( userId ) ;
763
+ if ( userProfileMap == null )
764
+ {
765
+ Logger . Log ( LogLevel . INFO ,
766
+ reasons . AddInfo (
767
+ "We were unable to get a user profile map from the UserProfileService." ) ) ;
768
+ }
769
+ else if ( UserProfileUtil . IsValidUserProfileMap ( userProfileMap ) )
770
+ {
771
+ userProfile = UserProfileUtil . ConvertMapToUserProfile ( userProfileMap ) ;
772
+ }
773
+ else
774
+ {
775
+ Logger . Log ( LogLevel . WARN ,
776
+ reasons . AddInfo ( "The UserProfileService returned an invalid map." ) ) ;
777
+ }
778
+ }
779
+ catch ( Exception exception )
780
+ {
781
+ Logger . Log ( LogLevel . ERROR , reasons . AddInfo ( exception . Message ) ) ;
782
+ ErrorHandler . HandleError (
783
+ new Exceptions . OptimizelyRuntimeException ( exception . Message ) ) ;
784
+ }
785
+
786
+ if ( userProfile == null )
787
+ {
788
+ userProfile = new UserProfile ( userId , new Dictionary < string , Decision > ( ) ) ;
789
+ }
790
+
791
+ return userProfile ;
792
+ }
793
+
794
+ public virtual List < Result < FeatureDecision > > GetVariationsForFeatureList (
795
+ List < FeatureFlag > featureFlags ,
733
796
OptimizelyUserContext user ,
734
- ProjectConfig config ,
797
+ ProjectConfig projectConfig ,
735
798
UserAttributes filteredAttributes ,
736
799
OptimizelyDecideOption [ ] options
737
800
)
738
801
{
739
- var reasons = new DecisionReasons ( ) ;
740
- var userId = user . GetUserId ( ) ;
741
- // Check if the feature flag has an experiment and the user is bucketed into that experiment.
742
- var decisionResult = GetVariationForFeatureExperiment ( featureFlag , user ,
743
- filteredAttributes , config , options ) ;
744
- reasons += decisionResult . DecisionReasons ;
802
+ var upsReasons = new DecisionReasons ( ) ;
745
803
746
- if ( decisionResult . ResultObject != null )
804
+ var ignoreUPS = options . Contains ( OptimizelyDecideOption . IGNORE_USER_PROFILE_SERVICE ) ;
805
+ UserProfileTracker userProfileTracker = null ;
806
+
807
+ if ( UserProfileService != null && ! ignoreUPS )
747
808
{
748
- return Result < FeatureDecision > . NewResult ( decisionResult . ResultObject , reasons ) ;
809
+ var userProfile = GetUserProfile ( user . GetUserId ( ) , upsReasons ) ;
810
+ userProfileTracker = new UserProfileTracker ( userProfile , false ) ;
749
811
}
750
812
751
- // Check if the feature flag has rollout and the the user is bucketed into one of its rules.
752
- decisionResult = GetVariationForFeatureRollout ( featureFlag , user , config ) ;
753
- reasons += decisionResult . DecisionReasons ;
813
+ var userId = user . GetUserId ( ) ;
814
+ var decisions = new List < Result < FeatureDecision > > ( ) ;
754
815
755
- if ( decisionResult . ResultObject != null )
816
+ foreach ( var featureFlag in featureFlags )
756
817
{
818
+ var reasons = new DecisionReasons ( ) ;
819
+ reasons += upsReasons ;
820
+
821
+ // Check if the feature flag has an experiment and the user is bucketed into that experiment.
822
+ var decisionResult = GetVariationForFeatureExperiment ( featureFlag , user ,
823
+ filteredAttributes , projectConfig , options ) ;
824
+ reasons += decisionResult . DecisionReasons ;
825
+
826
+ if ( decisionResult . ResultObject != null )
827
+ {
828
+ decisions . Add (
829
+ Result < FeatureDecision > . NewResult ( decisionResult . ResultObject , reasons ) ) ;
830
+ continue ;
831
+ }
832
+
833
+ // Check if the feature flag has rollout and the the user is bucketed into one of its rules.
834
+ decisionResult = GetVariationForFeatureRollout ( featureFlag , user , projectConfig ) ;
835
+ reasons += decisionResult . DecisionReasons ;
836
+
837
+ if ( decisionResult . ResultObject != null )
838
+ {
839
+ Logger . Log ( LogLevel . INFO ,
840
+ reasons . AddInfo (
841
+ $ "The user \" { userId } \" is bucketed into a rollout for feature flag \" { featureFlag . Key } \" .") ) ;
842
+ decisions . Add (
843
+ Result < FeatureDecision > . NewResult ( decisionResult . ResultObject , reasons ) ) ;
844
+ continue ;
845
+ }
846
+
757
847
Logger . Log ( LogLevel . INFO ,
758
848
reasons . AddInfo (
759
- $ "The user \" { userId } \" is bucketed into a rollout for feature flag \" { featureFlag . Key } \" .") ) ;
760
- return Result < FeatureDecision > . NewResult ( decisionResult . ResultObject , reasons ) ;
849
+ $ "The user \" { userId } \" is not bucketed into a rollout for feature flag \" { featureFlag . Key } \" .") ) ;
850
+ decisions . Add ( Result < FeatureDecision > . NewResult (
851
+ new FeatureDecision ( null , null , FeatureDecision . DECISION_SOURCE_ROLLOUT ) ,
852
+ reasons ) ) ;
761
853
}
762
854
763
- Logger . Log ( LogLevel . INFO ,
764
- reasons . AddInfo (
765
- $ "The user \" { userId } \" is not bucketed into a rollout for feature flag \" { featureFlag . Key } \" .") ) ;
766
- return Result < FeatureDecision > . NewResult (
767
- new FeatureDecision ( null , null , FeatureDecision . DECISION_SOURCE_ROLLOUT ) , reasons ) ;
768
- ;
855
+ if ( UserProfileService != null && ! ignoreUPS && userProfileTracker ? . ProfileUpdated == true )
856
+ {
857
+ SaveUserProfile ( userProfileTracker . UserProfile ) ;
858
+ }
859
+
860
+ return decisions ;
861
+ }
862
+
863
+ /// <summary>
864
+ /// Get the variation the user is bucketed into for the FeatureFlag
865
+ /// </summary>
866
+ /// <param name="featureFlag">The feature flag the user wants to access.</param>
867
+ /// <param name="user">The user context.</param>
868
+ /// <param name="config">The project config.</param>
869
+ /// <param name="filteredAttributes">The user's attributes. This should be filtered to just attributes in the Datafile.</param>
870
+ /// <param name="options">An array of decision options.</param>
871
+ /// <returns>null if the user is not bucketed into any variation or the FeatureDecision entity if the user is
872
+ /// successfully bucketed.</returns>
873
+ public virtual Result < FeatureDecision > GetVariationForFeature ( FeatureFlag featureFlag ,
874
+ OptimizelyUserContext user ,
875
+ ProjectConfig config ,
876
+ UserAttributes filteredAttributes ,
877
+ OptimizelyDecideOption [ ] options
878
+ )
879
+ {
880
+ return GetVariationsForFeatureList ( new List < FeatureFlag > { featureFlag } ,
881
+ user ,
882
+ config ,
883
+ filteredAttributes ,
884
+ options ) . First ( ) ;
769
885
}
770
886
771
887
/// <summary>
0 commit comments