Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion FlagShip/FlagShipTests/FSOnExposureTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ final class FSOnExposureTest: XCTestCase {
let variation = FSVariation(idVariation: "varId", variationName: "varName", nil, isReference: false)
let modif = FSModification(aCampaign: camp, aVariation: variation, valueForFlag: "flagVlaue")
let mettdata = FSFlagMetadata(modif)
let flagTest = FSExposedFlag(key: "keyFlag", defaultValue: "dfl", metadata: mettdata, value: "flagVlaue")
let flagTest = FSExposedFlag(key: "keyFlag", defaultValue: "dfl", metadata: mettdata, value: "flagVlaue", alreadyActivatedCampaign: true)

XCTAssertTrue(flagTest.toDictionary()["value"] as? String == "flagVlaue")
XCTAssertTrue(flagTest.toDictionary()["key"] as? String == "keyFlag")
XCTAssertTrue(flagTest.metadata.campaignId == "campId")
XCTAssertTrue(flagTest.toDictionary()["alreadyActivatedCampaign"] as? Bool == true)

XCTAssertTrue(flagTest.toJson()?.count ?? 0 > 0)
}

Expand All @@ -94,4 +96,25 @@ final class FSOnExposureTest: XCTestCase {
XCTAssertTrue((visitorObject.toDictionary()["context"] as? [String: Any])?["key1"] as? String == "val1")
XCTAssertTrue(visitorObject.toJson()?.count ?? 0 > 0)
}

func testDeduplication() {
let expectationSync = XCTestExpectation(description: "Service-deduplication")

testVisitor?.fetchFlags(onFetchCompleted: {
if let flag = self.testVisitor?.getFlag(key: "btnTitle") {
XCTAssertTrue(flag.value(defaultValue: "dfl") == "Alpha_demoApp")
// Activate again
XCTAssertTrue(flag.value(defaultValue: "dfl") == "Alpha_demoApp")

// Check deduplication
XCTAssertTrue(self.testVisitor?.isDeduplicatedFlag(campId: "bvcdqksmicqghldq9agg", varGrpId: "bvcdqksmicqghldq9ahg") ?? false)
// Send hit to reset timer
self.testVisitor?.sendHit(FSScreen("testLocatoin"))
XCTAssertTrue(self.testVisitor?.isDeduplicatedFlag(campId: "bvcdqksmicqghldq9agg", varGrpId: "bvcdqksmicqghldq9ahg") ?? false)
}
expectationSync.fulfill()

})
wait(for: [expectationSync], timeout: 5.0)
}
}
21 changes: 19 additions & 2 deletions FlagShip/Source/Core/FSExposedFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ protocol IFlag {

// Get metadata
var metadata: FSFlagMetadata { get set }

// Already activated campaign

var alreadyActivatedCampaign: Bool? { get }
}

@objc public class FSExposedFlag: NSObject, IFlag {
Expand All @@ -32,18 +36,23 @@ protocol IFlag {
// Value for flag
public private(set) var value: Any?

init(key: String, defaultValue: Any? = nil, metadata: FSFlagMetadata, value: Any?) {
// Already activated campaign
public var alreadyActivatedCampaign: Bool? = true

init(key: String, defaultValue: Any? = nil, metadata: FSFlagMetadata, value: Any?, alreadyActivatedCampaign: Bool = false) {
self.key = key
self.defaultValue = defaultValue
self.metadata = metadata
self.value = value
self.alreadyActivatedCampaign = alreadyActivatedCampaign
}

init(expoedInfo: [String: Any]) {
self.key = expoedInfo["key"] as? String ?? ""
self.value = expoedInfo["value"]
self.defaultValue = expoedInfo["defaultValue"]
self.metadata = FSFlagMetadata(metadataDico: expoedInfo["metadata"] as? [String: Any] ?? [:])
self.alreadyActivatedCampaign = expoedInfo["alreadyActivatedCampaign"] as? Bool ?? false // TODO: Review & test later
}

/// Dictionary that represent the Exposed Flag
Expand All @@ -62,6 +71,10 @@ protocol IFlag {
result.updateValue(aValue, forKey: "value")
}

if let aAlreadyActivatedCampaign = alreadyActivatedCampaign {
result.updateValue(aAlreadyActivatedCampaign, forKey: "alreadyActivatedCampaign")
}

return result
}

Expand All @@ -81,9 +94,13 @@ protocol IFlag {
result.updateValue(aValue, forKey: "value")
}

if let aAlreadyActivated = alreadyActivatedCampaign {
result.updateValue(aAlreadyActivated, forKey: "alreadyActivatedCampaign")
}

guard let jsonData = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) else {
return nil
}
return jsonData.jsonString
return jsonData.prettyPrintedJSONString as String?
}
}
44 changes: 44 additions & 0 deletions FlagShip/Source/Core/FSVisitor+Flags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

import Foundation

let FS_SESSION_VISITOR: TimeInterval = 30.0 // 30 * 60 /// 30 mn

public extension FSVisitor {
/// Get FSFlag object
/// - Parameter key: key represent key modification
/// - Returns: FSFLag object
func getFlag(key: String) -> FSFlag {
/// Init the session
self.sessionStartDate = Date()
// We dispaly a warning if the flag's status is not fetched
if self.fetchStatus != .FETCHED {
FlagshipLogManager.Log(level: .ALL, tag: .FLAG, messageToDisplay: FSLogMessage.MESSAGE(self.requiredFetchReason.warningMessage(key, self.visitorId)))
Expand All @@ -23,13 +27,53 @@ public extension FSVisitor {
/// Getting FSFlagCollection
/// - Returns: an instance of FSFlagCollection with flags
func getFlags() -> FSFlagCollection {
/// Init the session
self.sessionStartDate = Date()
var ret: [String: FSFlag] = [:]
self.currentFlags.forEach { (key: String, _: FSModification) in

ret.updateValue(FSFlag(key, self.strategy), forKey: key)
}
return FSFlagCollection(flags: ret)
}

internal func isDeduplicatedFlag(campId: String, varGrpId: String) -> Bool {
var ret = true
let duration = Date().timeIntervalSince(self.sessionStartDate)

print("the timeinterval duration is \(round(duration))")

if round(duration) > FS_SESSION_VISITOR {
print("La session a duré plus de 30 minutes ----------")
print("vider les saves et envoyer les activates quant meeme")

// Clear all saved ids for flags
self.activatedVariations.removeAll()

/// Register et ordonner l envoie
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

clean

self.activatedVariations.merge([campId: varGrpId]) { _, new in new }

ret = false
} else {
print("la session a moins que 30 minutes ===> alors on check avant d envoyer activate")

if self.activatedVariations.keys.contains(campId) {
if varGrpId == self.activatedVariations[campId] {
/// The activation is already done
print("========== Already activated ============")
ret = true
}
} else {
/// Register et ordonner l envoie
self.activatedVariations.merge([campId: varGrpId]) { _, new in new }
print("========== GO for activation ============")
ret = false
}
}
/// reset the timestamps
self.sessionStartDate = Date()
return ret
}
}

// We keep FlagMap instead using FSFlagV4 wich is also possible
Expand Down
4 changes: 4 additions & 0 deletions FlagShip/Source/Core/FSVisitor+Reconcilliation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public extension FSVisitor {
/// - Important: After using this method, you should use Flagship.fetchFlags method to update the visitor informations
/// - Requires: Make sure that the experience continuity option is enabled on the flagship platform before using this method
@objc func authenticate(visitorId: String) {
/// Init the session
self.sessionStartDate = Date()
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .AUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
Expand All @@ -27,6 +29,8 @@ public extension FSVisitor {

/// Use authenticate methode to go from Logged in session to logged out session
@objc func unauthenticate() {
/// Init the session
self.sessionStartDate = Date()
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .UNAUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
Expand Down
31 changes: 30 additions & 1 deletion FlagShip/Source/Core/FSVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ import UIKit

// Score value
public internal(set) var emotionScoreAI: String? = nil

// sesstion timestamps
var sessionStartDate: Date

// List of activated variations
var activatedVariations: [String: String] = [:] /// campId:varGrpId

// Refonte status
public internal(set) var fetchStatus: FSFlagStatus = .FETCH_REQUIRED {
Expand Down Expand Up @@ -129,7 +135,7 @@ import UIKit
self.visitorId = FSTools.manageVisitorId(aVisitorId)
self.anonymousId = nil
}

// Set the user context
self.context = FSContext(aContext, visitorId: aVisitorId)

Expand All @@ -149,9 +155,14 @@ import UIKit
self._onFlagStatusChanged = pOnFlagStatusChanged
self._onFlagStatusFetchRequired = pOnFlagStatusFetchRequired
self._onFlagStatusFetched = pOnFlagStatusFetched

// init sessionStartTimestamp
self.sessionStartDate = Date()
}

@objc public func fetchFlags(onFetchCompleted: @escaping () -> Void) {
/// Init the session
self.sessionStartDate = Date()
self.prepareEmotionAI(onCompleted: { score, _ in
// Set the score
self.emotionScoreAI = score
Expand Down Expand Up @@ -213,6 +224,8 @@ import UIKit
}

public func collectEmotionsAIEvents(window: UIWindow?, screenName: String? = nil, usingSwizzling: Bool = false) {
/// Init the session
self.sessionStartDate = Date()
if Flagship.sharedInstance.eaiCollectEnabled == true {
self.strategy?.getStrategy().collectEmotionsAIEvents(window: window, screenName: screenName, usingSwizzling: usingSwizzling)
} else {
Expand All @@ -231,6 +244,8 @@ import UIKit
// Update Context
// - Parameter newContext: user's context
@objc public func updateContext(_ context: [String: Any]) {
/// Init the session
self.sessionStartDate = Date()
self._updateContext(context)
}

Expand All @@ -239,6 +254,8 @@ import UIKit
// - key: key for the given value
// - newValue: value for teh given key
public func updateContext(_ key: String, _ newValue: Any) {
/// Init the session
self.sessionStartDate = Date()
self._updateContext([key: newValue])
}

Expand All @@ -247,6 +264,8 @@ import UIKit
// - presetKey: name of the preset context, see PresetContext
// - newValue: the value for the given key
public func updateContext(_ flagshipContext: FlagshipContext, _ value: Any) {
/// Init the session
self.sessionStartDate = Date()
/// Check the validity value
if !flagshipContext.chekcValidity(value) {
FlagshipLogManager.Log(level: .ALL, tag: .UPDATE_CONTEXT, messageToDisplay: FSLogMessage.UPDATE_PRE_CONTEXT_FAILED(flagshipContext.rawValue))
Expand All @@ -266,17 +285,24 @@ import UIKit
// Get the current context
// - Returns: Dictionary that represent a user context
@objc public func getContext() -> [String: Any] {
/// Init the session
self.sessionStartDate = Date()
return self.context.getCurrentContext()
}

// Clear the current context
@objc public func clearContext() {
/// Init the session
self.sessionStartDate = Date()
self.context.clearContext()
}

// Send Hits
// - Parameter T: Hit object
public func sendHit<T: FSTrackingProtocol>(_ event: T) {
/// Init the session
self.sessionStartDate = Date()

self.strategy?.getStrategy().sendHit(event)
}

Expand All @@ -285,6 +311,9 @@ import UIKit
// Set the conssent
// - Parameter newValue: if true, then flush all stored visitor data
@objc public func setConsent(hasConsented: Bool) {
/// Init the session
self.sessionStartDate = Date()

self.hasConsented = hasConsented
self.strategy?.getStrategy().setConsent(newValue: hasConsented)

Expand Down
54 changes: 38 additions & 16 deletions FlagShip/Source/Strategy/FSDefaultStrategy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,52 @@ class FSDefaultStrategy: FSDelegateStrategy {
func activateFlag(_ flag: FSFlag) {
if let aModification = visitor.currentFlags[flag.key] {
// Define Exposed flag and exposed visitor
var exposedFlag, exposedVisitor: String?
// var exposedFlag, exposedVisitor: String?

var exposedFlag: FSExposedFlag?
var exposedVisitor: FSVisitorExposed?

if visitor.configManager.flagshipConfig.onVisitorExposed != nil {
// Create flag exposed object
exposedFlag = FSExposedFlag(key: flag.key, defaultValue: flag.defaultValue, metadata: flag.metadata(), value: flag.value(defaultValue: flag.defaultValue, visitorExposed: false)).toJson()
exposedFlag = FSExposedFlag(key: flag.key, defaultValue: flag.defaultValue, metadata: flag.metadata(), value: flag.value(defaultValue: flag.defaultValue, visitorExposed: false))
// Create visitor expose object
exposedVisitor = FSVisitorExposed(id: visitor.visitorId, anonymousId: visitor.anonymousId, context: visitor.getContext()).toJson()
exposedVisitor = FSVisitorExposed(id: visitor.visitorId, anonymousId: visitor.anonymousId, context: visitor.getContext())
}

let activateToSend = Activate(visitor.visitorId, visitor.anonymousId, modification: aModification, exposedFlag, exposedVisitor)
visitor.configManager.trackingManager?.sendActivate(activateToSend, onCompletion: { error, exposedInfosArray in

if error == nil {
/// Is callback is defined ===> Trigger it
if let aOnVisitorExposed = self.visitor.configManager.flagshipConfig.onVisitorExposed {
exposedInfosArray?.forEach { item in
aOnVisitorExposed(item.visitorExposed, item.exposedFlag)
let activateToSend = Activate(visitor.visitorId, visitor.anonymousId, modification: aModification, exposedFlag?.toJson(), exposedVisitor?.toJson())

/// If dediplicate
if visitor.isDeduplicatedFlag(campId: flag.metadata().campaignId, varGrpId: flag.metadata().variationGroupId) {
// Skip the activate
print("Skip the activation ..... the variation is already activated")
if let aExposedVisitor = exposedVisitor {
if let aExposedFlag = exposedFlag {
// Update exposed flag
aExposedFlag.alreadyActivatedCampaign = true
/// Is callback is defined ===> Trigger it
if let aOnVisitorExposed = visitor.configManager.flagshipConfig.onVisitorExposed {
aOnVisitorExposed(aExposedVisitor, aExposedFlag)
}
}
} else {
// The flag error
}
})
// Troubleshooitng activate
FSDataUsageTracking.sharedInstance.processTSHits(label: CriticalPoints.VISITOR_SEND_ACTIVATE.rawValue, visitor: visitor, hit: activateToSend)

} else {
visitor.configManager.trackingManager?.sendActivate(activateToSend, onCompletion: { error, exposedInfosArray in

if error == nil {
/// Is callback is defined ===> Trigger it
if let aOnVisitorExposed = self.visitor.configManager.flagshipConfig.onVisitorExposed {
exposedInfosArray?.forEach { item in
aOnVisitorExposed(item.visitorExposed, item.exposedFlag)
}
}
} else {
// The flag error
}
})
// Troubleshooitng activate
FSDataUsageTracking.sharedInstance.processTSHits(label: CriticalPoints.VISITOR_SEND_ACTIVATE.rawValue, visitor: visitor, hit: activateToSend)
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions iOS Example/QApp/FSConfigViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ class FSConfigViewController: UIViewController, UITextFieldDelegate, FSJsonEdito
}
}.withLogLevel(.ALL).withOnVisitorExposed { visitorExposed, fromFlag in

print("------- On visitor exposed callback ----------")
print(visitorExposed.toJson())
//print("------- On visitor exposed callback ----------")
// print(visitorExposed.toJson())

print(fromFlag.toJson())
print("------- On visitor exposed callback ----------")
}
Expand Down