Skip to content

Commit f39784c

Browse files
authored
feat: added GetAllFeatureVariables API. (#318)
1 parent 943f8b9 commit f39784c

11 files changed

+481
-23
lines changed

Sources/Data Model/FeatureVariable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ struct FeatureVariable: Codable, Equatable {
5252
}
5353

5454
mutating func overrideTypeIfJSON() {
55-
if type == "string" && subType == "json" {
56-
type = "json"
55+
if type == Constants.VariableValueType.string.rawValue && subType == Constants.VariableValueType.json.rawValue {
56+
type = Constants.VariableValueType.json.rawValue
5757
}
5858
}
5959
}

Sources/Optimizely/OptimizelyClient+ObjC.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,23 @@ extension OptimizelyClient {
316316
attributes: attributes)
317317
}
318318

319+
@objc(getAllFeatureVariablesWithFeatureKey:userId:attributes:error:)
320+
/// Gets all the variables for a given feature.
321+
///
322+
/// - Parameters:
323+
/// - featureKey: The key for the feature flag.
324+
/// - userId: The user ID to be used for bucketing.
325+
/// - attributes: The user's attributes.
326+
/// - Returns: all the variables for a given feature.
327+
/// - Throws: `OptimizelyError` if feature parameter is not valid
328+
public func objcGetAllFeatureVariables(featureKey: String,
329+
userId: String,
330+
attributes: [String: Any]?) throws -> OptimizelyJSON {
331+
return try self.getAllFeatureVariables(featureKey: featureKey,
332+
userId: userId,
333+
attributes: attributes)
334+
}
335+
319336
@available(swift, obsoleted: 1.0)
320337
@objc(getEnabledFeaturesWithUserId:attributes:)
321338
/// Get array of features that are enabled for the user.

Sources/Optimizely/OptimizelyClient.swift

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -547,33 +547,31 @@ open class OptimizelyClient: NSObject {
547547
logger.i(.userReceivedDefaultVariableValue(userId, featureKey, variableKey))
548548
}
549549

550-
var typeName: String?
550+
var type: Constants.VariableValueType?
551551
var valueParsed: T?
552552

553553
switch T.self {
554554
case is String.Type:
555-
typeName = "string"
555+
type = .string
556556
valueParsed = featureValue as? T
557557
case is Int.Type:
558-
typeName = "integer"
558+
type = .integer
559559
valueParsed = Int(featureValue) as? T
560560
case is Double.Type:
561-
typeName = "double"
561+
type = .double
562562
valueParsed = Double(featureValue) as? T
563563
case is Bool.Type:
564-
typeName = "boolean"
564+
type = .boolean
565565
valueParsed = Bool(featureValue) as? T
566566
case is OptimizelyJSON.Type:
567-
typeName = "json"
568-
if let optimizelyJSON = OptimizelyJSON(payload: featureValue) {
569-
valueParsed = optimizelyJSON as? T
570-
}
567+
type = .json
568+
valueParsed = OptimizelyJSON(payload: featureValue) as? T
571569
default:
572570
break
573571
}
574572

575573
guard let value = valueParsed,
576-
variable.type == typeName else {
574+
type?.rawValue == variable.type else {
577575
throw OptimizelyError.variableValueInvalid(variableKey)
578576
}
579577

@@ -591,12 +589,88 @@ open class OptimizelyClient: NSObject {
591589
feature: featureFlag,
592590
featureEnabled: featureEnabled,
593591
variableKey: variableKey,
594-
variableType: typeName,
592+
variableType: variable.type,
595593
variableValue: value)
596594

597595
return value
598596
}
599597

598+
/// Gets all the variables for a given feature.
599+
///
600+
/// - Parameters:
601+
/// - featureKey: The key for the feature flag.
602+
/// - userId: The user ID to be used for bucketing.
603+
/// - attributes: The user's attributes.
604+
/// - Returns: all the variables for a given feature.
605+
/// - Throws: `OptimizelyError` if feature parameter is not valid
606+
public func getAllFeatureVariables(featureKey: String,
607+
userId: String,
608+
attributes: OptimizelyAttributes? = nil) throws -> OptimizelyJSON {
609+
guard let config = self.config else { throw OptimizelyError.sdkNotReady }
610+
var variableMap = [String: Any]()
611+
var enabled = false
612+
613+
guard let featureFlag = config.getFeatureFlag(key: featureKey) else {
614+
throw OptimizelyError.featureKeyInvalid(featureKey)
615+
}
616+
617+
let decision = self.decisionService.getVariationForFeature(config: config,
618+
featureFlag: featureFlag,
619+
userId: userId,
620+
attributes: attributes ?? OptimizelyAttributes())
621+
if let featureEnabled = decision?.variation?.featureEnabled {
622+
enabled = featureEnabled
623+
}
624+
625+
for (_, v) in featureFlag.variablesMap {
626+
var featureValue = v.value
627+
if enabled, let variable = decision?.variation?.getVariable(id: v.id) {
628+
featureValue = variable.value
629+
}
630+
631+
var valueParsed: Any? = featureValue
632+
633+
if let valueType = Constants.VariableValueType(rawValue: v.type) {
634+
switch valueType {
635+
case .string:
636+
break
637+
case .integer:
638+
valueParsed = Int(featureValue)
639+
break
640+
case .double:
641+
valueParsed = Double(featureValue)
642+
break
643+
case .boolean:
644+
valueParsed = Bool(featureValue)
645+
break
646+
case .json:
647+
valueParsed = OptimizelyJSON(payload: featureValue)?.toMap()
648+
break
649+
}
650+
}
651+
652+
if let value = valueParsed {
653+
variableMap[v.key] = value
654+
} else {
655+
logger.e(OptimizelyError.variableValueInvalid(v.key))
656+
}
657+
}
658+
659+
guard let optimizelyJSON = OptimizelyJSON(map: variableMap) else {
660+
throw OptimizelyError.invalidDictionary
661+
}
662+
663+
sendDecisionNotification(decisionType: .allFeatureVariables,
664+
userId: userId,
665+
attributes: attributes,
666+
experiment: decision?.experiment,
667+
variation: decision?.variation,
668+
feature: featureFlag,
669+
featureEnabled: enabled,
670+
variableValues: variableMap)
671+
return optimizelyJSON
672+
}
673+
600674
/// Get array of features that are enabled for the user.
601675
///
602676
/// - Parameters:
@@ -783,6 +857,7 @@ extension OptimizelyClient {
783857
variableKey: String? = nil,
784858
variableType: String? = nil,
785859
variableValue: Any? = nil,
860+
variableValues: [String: Any]? = nil,
786861
async: Bool = true) {
787862
self.sendNotification(type: .decision,
788863
args: [decisionType.rawValue,
@@ -795,7 +870,8 @@ extension OptimizelyClient {
795870
featureEnabled: featureEnabled,
796871
variableKey: variableKey,
797872
variableType: variableType,
798-
variableValue: variableValue)],
873+
variableValue: variableValue,
874+
variableValues: variableValues)],
799875
async: async)
800876
}
801877

@@ -810,7 +886,8 @@ extension OptimizelyClient {
810886
featureEnabled: Bool? = nil,
811887
variableKey: String? = nil,
812888
variableType: String? = nil,
813-
variableValue: Any? = nil) -> [String: Any] {
889+
variableValue: Any? = nil,
890+
variableValues: [String: Any]? = nil) -> [String: Any] {
814891

815892
var decisionInfo = [String: Any]()
816893

@@ -821,7 +898,7 @@ extension OptimizelyClient {
821898
decisionInfo[Constants.ExperimentDecisionInfoKeys.experiment] = experiment.key
822899
decisionInfo[Constants.ExperimentDecisionInfoKeys.variation] = variation?.key ?? NSNull()
823900

824-
case .feature, .featureVariable:
901+
case .feature, .featureVariable, .allFeatureVariables:
825902
guard let feature = feature, let featureEnabled = featureEnabled else { return decisionInfo }
826903

827904
decisionInfo[Constants.DecisionInfoKeys.feature] = feature.key
@@ -841,15 +918,20 @@ extension OptimizelyClient {
841918

842919
if decisionType == .featureVariable {
843920
guard let variableKey = variableKey, let variableType = variableType, let variableValue = variableValue else {
844-
return decisionInfo
921+
return decisionInfo
845922
}
846923

847924
decisionInfo[Constants.DecisionInfoKeys.variable] = variableKey
848925
decisionInfo[Constants.DecisionInfoKeys.variableType] = variableType
849926
decisionInfo[Constants.DecisionInfoKeys.variableValue] = variableValue
927+
} else if decisionType == .allFeatureVariables {
928+
guard let variableValues = variableValues else {
929+
return decisionInfo
930+
}
931+
decisionInfo[Constants.DecisionInfoKeys.variableValues] = variableValues
850932
}
851933
}
852-
934+
853935
return decisionInfo
854936
}
855937

Sources/Optimizely/OptimizelyError.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ public enum OptimizelyError: Error {
7373

7474
case eventDispatchFailed(_ reason: String)
7575
case eventDispatcherConfigError(_ reason: String)
76+
77+
// MARK: - OptimizelyJSON Errors
78+
79+
case invalidDictionary
7680
}
7781

7882
// MARK: - CustomStringConvertible
@@ -133,6 +137,7 @@ extension OptimizelyError: CustomStringConvertible {
133137

134138
case .eventDispatchFailed(let hint): message = "Event dispatch failed (\(hint))"
135139
case .eventDispatcherConfigError(let hint): message = "EventDispatcher config error (\(hint))"
140+
case .invalidDictionary: message = "Unable to initialize OptimizelyJSON with the provided dictionary."
136141
}
137142

138143
return message

Sources/Utils/Constants.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,19 @@ struct Constants {
2323
static let OptimizelyUserAgent = "$opt_user_agent"
2424
}
2525

26+
enum VariableValueType: String {
27+
case string = "string"
28+
case integer = "integer"
29+
case double = "double"
30+
case boolean = "boolean"
31+
case json = "json"
32+
}
33+
2634
enum DecisionType: String {
2735
case abTest = "ab-test"
2836
case feature = "feature"
2937
case featureVariable = "feature-variable"
38+
case allFeatureVariables = "all-feature-variables"
3039
case featureTest = "feature-test"
3140
}
3241

@@ -43,6 +52,7 @@ struct Constants {
4352
static let variable = "variableKey"
4453
static let variableType = "variableType"
4554
static let variableValue = "variableValue"
55+
static let variableValues = "variableValues"
4656
}
4757

4858
struct ExperimentDecisionInfoKeys {

Tests/OptimizelyTests-APIs/OptimizelyClientTests_Invalid.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ extension OptimizelyClientTests_Invalid {
104104
XCTAssertNil(result)
105105
}
106106

107+
func testGetAllFeatureVariables_WhenManagerNonInitialized() {
108+
let result = try? self.optimizely.getAllFeatureVariables(featureKey: kFeatureKey, userId: kUserId)
109+
XCTAssertNil(result)
110+
}
111+
107112
func testGetEnabledFeatures_WhenManagerNonInitialized() {
108113
let result = self.optimizely.getEnabledFeatures(userId: kUserId)
109114
XCTAssert(result.count == 0)

Tests/OptimizelyTests-APIs/OptimizelyClientTests_ObjcAPIs.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,18 @@ - (void)testGetFeatureVariableJSON {
202202
error:nil];
203203
XCTAssertEqualObjects([result toString], kVariableValueJSON);
204204
}
205+
206+
- (void)testGetAllFeatureVariables {
207+
OptimizelyJSON *optimizelyJSON = [self.optimizely getAllFeatureVariablesWithFeatureKey:kFeatureKey userId:kUserId attributes:self.attributes error:nil];
208+
209+
NSDictionary *featureVariables = [optimizelyJSON toMap];
210+
XCTAssert([kVariableValueString isEqualToString: [featureVariables valueForKey:kVariableKeyString]]);
211+
XCTAssertEqual([[featureVariables objectForKey:kVariableKeyInt] integerValue], kVariableValueInt);
212+
XCTAssertEqual([[featureVariables objectForKey:kVariableKeyDouble] doubleValue], kVariableValueDouble);
213+
XCTAssertEqual([[featureVariables objectForKey:kVariableKeyBool] boolValue], kVariableValueBool);
214+
NSDictionary *jsonValue = [featureVariables valueForKey:kVariableKeyJSON];
215+
XCTAssertEqual([[jsonValue objectForKey:@"value"] integerValue], 1);
216+
}
205217

206218
- (void)testGetEnabledFeatures {
207219
NSArray *result = [self.optimizely getEnabledFeaturesWithUserId:kUserId

0 commit comments

Comments
 (0)