Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flag-decisions): Add support for sending flag decisions along with decision metadata. #370

Merged
merged 15 commits into from
Oct 20, 2020
2 changes: 1 addition & 1 deletion Sources/Data Model/DispatchEvents/BatchEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ struct DispatchEvent: Codable, Equatable {
revenue: AttributeValue? = nil) {

// TODO: add validation and throw here for invalid value (int, double) and revenue (int) types

self.timestamp = timestamp
self.key = key
self.entityID = entityID
Expand Down
1 change: 1 addition & 0 deletions Sources/Implementation/DefaultDecisionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class DefaultDecisionService: OPTDecisionService {
}

return nil

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this empty line

}

func getVariationForFeatureExperiment(config: ProjectConfig,
Expand Down
20 changes: 6 additions & 14 deletions Sources/Implementation/Events/BatchEventBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class BatchEventBuilder {
// MARK: - Impression Event

static func createImpressionEvent(config: ProjectConfig,
experiment: Experiment,
experiment: Experiment?,
variation: Variation?,
userId: String,
attributes: OptimizelyAttributes?,
Expand All @@ -35,24 +35,16 @@ class BatchEventBuilder {
return nil
}

var variationId = "", variationKey = "", ruleKey = "", finalRuleType = ""
if let tmpVariation = variation {
variationId = tmpVariation.id
variationKey = tmpVariation.key
ruleKey = experiment.key
finalRuleType = ruleType
}

let metaData = DecisionMetadata(ruleType: finalRuleType, ruleKey: ruleKey, flagKey: flagKey, variationKey: variationKey)
let metaData = DecisionMetadata(ruleType: ruleType, ruleKey: experiment?.key ?? "", flagKey: flagKey, variationKey: variation?.key ?? "")

let decision = Decision(variationID: variationId,
campaignID: experiment.layerId,
experimentID: experiment.id,
let decision = Decision(variationID: variation?.id ?? "",
campaignID: experiment?.layerId ?? "",
experimentID: experiment?.id ?? "",
metaData: metaData)

let dispatchEvent = DispatchEvent(timestamp: timestampSince1970,
key: DispatchEvent.activateEventKey,
entityID: experiment.layerId,
entityID: experiment?.layerId ?? "",
uuid: uuid)

return createBatchEvent(config: config,
Expand Down
105 changes: 50 additions & 55 deletions Sources/Optimizely/OptimizelyClient.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/****************************************************************************
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
***************************************************************************/
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
***************************************************************************/

import Foundation

Expand All @@ -34,7 +34,7 @@ open class OptimizelyClient: NSObject {
atomicConfig.property = newValue
}
}

public var version: String {
return Utils.sdkVersion
}
Expand All @@ -48,7 +48,7 @@ open class OptimizelyClient: NSObject {
return false
}
}

// MARK: - Customizable Services

lazy var logger = OPTLoggerFactory.getLogger()
Expand All @@ -70,7 +70,7 @@ open class OptimizelyClient: NSObject {
public var notificationCenter: OPTNotificationCenter? {
return HandlerRegistryService.shared.injectNotificationCenter(sdkKey: self.sdkKey)
}

// MARK: - Public interfaces

/// OptimizelyClient init
Expand Down Expand Up @@ -184,7 +184,7 @@ open class OptimizelyClient: NSObject {
func configSDK(datafile: Data) throws {
do {
self.config = try ProjectConfig(datafile: datafile)

datafileHandler?.startUpdates(sdkKey: self.sdkKey) { data in
// new datafile came in
self.updateConfigFromBackgroundFetch(data: data)
Expand Down Expand Up @@ -223,10 +223,10 @@ open class OptimizelyClient: NSObject {
for component in HandlerRegistryService.shared.lookupComponents(sdkKey: self.sdkKey) ?? [] {
HandlerRegistryService.shared.reInitializeComponent(service: component, sdkKey: self.sdkKey)
}

self.sendDatafileChangeNotification(data: data)
}

/**
* Use the activate method to start an experiment.
*
Expand Down Expand Up @@ -259,7 +259,7 @@ open class OptimizelyClient: NSObject {
variation: variation,
userId: userId,
attributes: attributes,
flagKey: experimentKey,
flagKey: "",
ruleType: "experiment")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have this new ruleType "experiment"? If so, can we add it to DecisionSource enum?


return variation.key
Expand Down Expand Up @@ -374,32 +374,27 @@ open class OptimizelyClient: NSObject {
return false
}

guard let pair = decisionService.getVariationForFeature(config: config,
let pair = decisionService.getVariationForFeature(config: config,
featureFlag: featureFlag,
userId: userId,
attributes: attributes ?? OptimizelyAttributes()) else {
sendDecisionNotification(decisionType: .feature,
userId: userId,
attributes: attributes,
feature: featureFlag,
featureEnabled: false)
return false
}
let featureEnabled = pair.variation.featureEnabled ?? false
attributes: attributes ?? OptimizelyAttributes())

let source = pair?.source ?? Constants.DecisionSource.rollout.rawValue
let featureEnabled = pair?.variation.featureEnabled ?? false
if featureEnabled {
logger.i(.featureEnabledForUser(featureKey, userId))
} else {
logger.i(.featureNotEnabledForUser(featureKey, userId))
}

sendImpressionEvent(experiment: pair.experiment, variation: pair.variation, userId: userId, attributes: attributes, flagKey: featureKey, ruleType: pair.source)

sendImpressionEvent(experiment: pair?.experiment, variation: pair?.variation, userId: userId, attributes: attributes, flagKey: featureKey, ruleType: source)
sendDecisionNotification(decisionType: .feature,
userId: userId,
attributes: attributes,
experiment: pair.experiment,
variation: pair.variation,
source: pair.source,
experiment: pair?.experiment,
variation: pair?.variation,
source: source,
feature: featureFlag,
featureEnabled: featureEnabled)

Expand Down Expand Up @@ -479,7 +474,7 @@ open class OptimizelyClient: NSObject {
variableKey: String,
userId: String,
attributes: OptimizelyAttributes? = nil) throws -> String {

return try getFeatureVariable(featureKey: featureKey,
variableKey: variableKey,
userId: userId,
Expand Down Expand Up @@ -571,8 +566,8 @@ open class OptimizelyClient: NSObject {
}

guard let value = valueParsed,
type?.rawValue == variable.type else {
throw OptimizelyError.variableValueInvalid(variableKey)
type?.rawValue == variable.type else {
throw OptimizelyError.variableValueInvalid(variableKey)
}

// Decision Notification
Expand Down Expand Up @@ -656,7 +651,7 @@ open class OptimizelyClient: NSObject {
break
}
}

if let value = valueParsed {
variableMap[v.key] = value
} else {
Expand Down Expand Up @@ -723,7 +718,7 @@ open class OptimizelyClient: NSObject {

sendConversionEvent(eventKey: eventKey, userId: userId, attributes: attributes, eventTags: eventTags)
}

/// Read a copy of project configuration data model.
///
/// This call returns a snapshot of the current project configuration.
Expand All @@ -736,7 +731,7 @@ open class OptimizelyClient: NSObject {
/// - Throws: `OptimizelyError` if SDK is not ready
public func getOptimizelyConfig() throws -> OptimizelyConfig {
guard let config = self.config else { throw OptimizelyError.sdkNotReady }

return OptimizelyConfigImp(projectConfig: config)
}
}
Expand All @@ -745,7 +740,7 @@ open class OptimizelyClient: NSObject {

extension OptimizelyClient {

func sendImpressionEvent(experiment: Experiment,
func sendImpressionEvent(experiment: Experiment?,
variation: Variation?,
userId: String,
attributes: OptimizelyAttributes? = nil,
Expand All @@ -755,34 +750,34 @@ extension OptimizelyClient {
// non-blocking (event data serialization takes time)
eventLock.async {
guard let config = self.config else { return }

guard let body = BatchEventBuilder.createImpressionEvent(config: config,
experiment: experiment,
variation: variation,
userId: userId,
attributes: attributes,
flagKey: flagKey,
ruleType: ruleType) else {
self.logger.e(OptimizelyError.eventBuildFailure(DispatchEvent.activateEventKey))
return
self.logger.e(OptimizelyError.eventBuildFailure(DispatchEvent.activateEventKey))
return
}

let event = EventForDispatch(body: body)
self.sendEventToDispatcher(event: event, completionHandler: nil)

// send notification in sync mode (functionally same as async here since it's already in background thread),
// but this will make testing simpler (timing control)

if let tmpVariation = variation {
self.sendActivateNotification(experiment: experiment,
if let tmpExperiment = experiment, let tmpVariation = variation {
self.sendActivateNotification(experiment: tmpExperiment,
variation: tmpVariation,
userId: userId,
attributes: attributes,
event: event,
async: false)
}
}

}

func sendConversionEvent(eventKey: String,
Expand All @@ -793,22 +788,22 @@ extension OptimizelyClient {
// non-blocking (event data serialization takes time)
eventLock.async {
guard let config = self.config else { return }

guard let body = BatchEventBuilder.createConversionEvent(config: config,
eventKey: eventKey,
userId: userId,
attributes: attributes,
eventTags: eventTags) else {
self.logger.e(OptimizelyError.eventBuildFailure(eventKey))
return
self.logger.e(OptimizelyError.eventBuildFailure(eventKey))
return
}

let event = EventForDispatch(body: body)
self.sendEventToDispatcher(event: event, completionHandler: nil)

// send notification in sync mode (functionally same as async here since it's already in background thread),
// but this will make testing simpler (timing control)

self.sendTrackNotification(eventKey: eventKey,
userId: userId,
attributes: attributes,
Expand Down Expand Up @@ -968,7 +963,7 @@ extension OptimizelyClient {
notify()
}
}

}

// MARK: - For test support
Expand Down
Loading