From adeff2d60e6c1f23a440d21c9e1a1dbdd1d363f6 Mon Sep 17 00:00:00 2001 From: ABTastyAdel Date: Mon, 21 Aug 2023 16:40:28 +0100 Subject: [PATCH 1/5] add warning log for the unsync flag --- .../Core/FSVisitor+Reconcilliation.swift | 16 +++-- FlagShip/Source/Core/FSVisitor.swift | 41 +++++++++++- .../Source/Logger/FlagshipConstants.swift | 2 +- .../Source/Logger/FlagshipLogManager.swift | 66 ++++++++----------- FlagShip/Source/Models/FSContext.swift | 46 +++++-------- 5 files changed, 94 insertions(+), 77 deletions(-) diff --git a/FlagShip/Source/Core/FSVisitor+Reconcilliation.swift b/FlagShip/Source/Core/FSVisitor+Reconcilliation.swift index 2b91c02c..11c6ac39 100644 --- a/FlagShip/Source/Core/FSVisitor+Reconcilliation.swift +++ b/FlagShip/Source/Core/FSVisitor+Reconcilliation.swift @@ -7,22 +7,24 @@ import Foundation -extension FSVisitor { - +public extension FSVisitor { /// Use authenticate methode to go from Logged-out session to logged-in session /// /// - Parameters: /// - visitorId: newVisitorId to authenticate /// - 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 public func authenticate(visitorId: String) { - - self.strategy?.getStrategy().authenticateVisitor(visitorId: visitorId) + @objc func authenticate(visitorId: String) { + self.strategy?.getStrategy().authenticateVisitor(visitorId: visitorId) + + // Update the flagSyncStatus + self.flagSyncStatus = .AUTHENTICATED } /// Use authenticate methode to go from Logged in session to logged out session - @objc public func unauthenticate() { - + @objc func unauthenticate() { self.strategy?.getStrategy().unAuthenticateVisitor() + // Update the flagSyncStatus + self.flagSyncStatus = .UNAUTHENTICATED } } diff --git a/FlagShip/Source/Core/FSVisitor.swift b/FlagShip/Source/Core/FSVisitor.swift index 15b2c593..34d2a6de 100644 --- a/FlagShip/Source/Core/FSVisitor.swift +++ b/FlagShip/Source/Core/FSVisitor.swift @@ -24,6 +24,35 @@ import Foundation case NEW_INSTANCE } +/** + * This status represent the flag status depend on visitor actions + */ +@objc public enum FlagSynchStatus: Int { + case CREATED + case CONTEXT_UPDATED + case FLAGS_FETCHED + case AUTHENTICATED + case UNAUTHENTICATED + + func warningMessage(_ flagKey: String, _ visitorId: String) -> String { + var ret = "Visitor `\(visitorId)` has been created without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + switch self { + case .CREATED: + break + case .CONTEXT_UPDATED: + ret = "Visitor context for visitor `\(visitorId)` has been updated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + case .AUTHENTICATED: + ret = "Visitor `\(visitorId)` has been authenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + case .UNAUTHENTICATED: + ret = "Visitor `\(visitorId)` has been unauthenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + default: + break + } + + return ret + } +} + /// Visitor class @objc public class FSVisitor: NSObject { let fsQueue = DispatchQueue(label: "com.flagshipVisitor.queue", attributes: .concurrent) @@ -55,6 +84,8 @@ import Foundation /// Assigned hsitory internal var assignedVariationHistory: [String: String] = [:] + + public internal(set) var flagSyncStatus: FlagSynchStatus = .CREATED init(aVisitorId: String, aContext: [String: Any], aConfigManager: FSConfigManager, aHasConsented: Bool, aIsAuthenticated: Bool) { /// Set authenticated @@ -99,6 +130,9 @@ import Foundation if self.configManager.flagshipConfig.mode == .BUCKETING, Flagship.sharedInstance.currentStatus != .PANIC_ON { self.sendHit(FSSegment(self.getContext())) } + + // Update the flagSyncStatus + self.flagSyncStatus = .FLAGS_FETCHED }) } @@ -116,6 +150,8 @@ import Foundation /// - Parameter newContext: user's context @objc public func updateContext(_ context: [String: Any]) { self.strategy?.getStrategy().updateContext(context) + // Update the flagSyncStatus + self.flagSyncStatus = .CONTEXT_UPDATED } /// Update context with one @@ -207,6 +243,10 @@ import Foundation /// - Parameter defaultValue:flag default value /// - Returns: FSFlag object, If no flag match the given key, an empty flag will be returned public func getFlag(key: String, defaultValue: T?) -> FSFlag { + // We dispaly a warning when the flag status is not fetched + if self.flagSyncStatus != .FLAGS_FETCHED { + FlagshipLogManager.Log(level: .ALL, tag: .FLAG, messageToDisplay: FSLogMessage.MESSAGE(self.flagSyncStatus.warningMessage(key, self.visitorId))) + } /// Check the key if exist guard let modification = self.currentFlags[key] else { return FSFlag(key, nil, defaultValue, self.strategy) @@ -221,7 +261,6 @@ import Foundation /// // ///////////////// - /// Send Hit consent internal func sendHitConsent(_ hasConsented: Bool) { // create the hit consent diff --git a/FlagShip/Source/Logger/FlagshipConstants.swift b/FlagShip/Source/Logger/FlagshipConstants.swift index fb250184..dc69bc8d 100644 --- a/FlagShip/Source/Logger/FlagshipConstants.swift +++ b/FlagShip/Source/Logger/FlagshipConstants.swift @@ -82,7 +82,7 @@ enum FSLogMessage: CustomStringConvertible { /// Universal case MESSAGE(_ key: String?) - + var description: String { var ret: String diff --git a/FlagShip/Source/Logger/FlagshipLogManager.swift b/FlagShip/Source/Logger/FlagshipLogManager.swift index cfc7e637..84a9d53e 100644 --- a/FlagShip/Source/Logger/FlagshipLogManager.swift +++ b/FlagShip/Source/Logger/FlagshipLogManager.swift @@ -7,53 +7,45 @@ import Foundation -public enum FSTag:String { - - case GLOBAL = "GLOBAL" - case VISITOR = "VISITOR" - case INITIALIZATION = "INITIALIZATION" - case CONFIGURATION = "CONFIGURATION" - case BUCKETING = "BUCKETING" - case UPDATE_CONTEXT = "UPDATE_CONTEXT" - case CLEAR_CONTEXT = "CLEAR_CONTEXT" - case SYNCHRONIZE = "SYNCHRONIZE" - case CAMPAIGNS = "CAMPAIGNS" - case PARSING = "PARSING" - case TARGETING = "TARGETING" - case ALLOCATION = "ALLOCATION" - case GET_MODIFICATION = "GET_MODIFICATION" - case GET_MODIFICATION_INFO = "GET_MODIFICATION_INFO" - case TRACKING = "HIT" - case ACTIVATE = "ACTIVATE" - case AUTHENTICATE = "AUTHENTICATE" - case UNAUTHENTICATE = "UNAUTHENTICATE" - case CONSENT = "CONSENT" - case EXCEPTION = "EXCEPTION" - case STORAGE = "CACHE" - +public enum FSTag: String { + case GLOBAL + case VISITOR + case INITIALIZATION + case CONFIGURATION + case BUCKETING + case UPDATE_CONTEXT + case CLEAR_CONTEXT + case SYNCHRONIZE + case CAMPAIGNS + case PARSING + case TARGETING + case ALLOCATION + case GET_MODIFICATION + case GET_MODIFICATION_INFO + case TRACKING = "HIT" + case ACTIVATE + case AUTHENTICATE + case UNAUTHENTICATE + case CONSENT + case EXCEPTION + case STORAGE = "CACHE" + case FLAG } - -class FlagshipLogManager:FSLogManager{ - - override init(){ - +class FlagshipLogManager: FSLogManager { + override init() { super.init() } - static func Log(level: FSLevel, tag: FSTag, messageToDisplay:FSLogMessage) { - - if isAllowed(level){ - - print("Flagship - \(tag.rawValue) - \(messageToDisplay.description)") /// Do not delete this print + static func Log(level: FSLevel, tag: FSTag, messageToDisplay: FSLogMessage) { + if isAllowed(level) { + print("Flagship - \(tag.rawValue) - \(messageToDisplay.description)") /// Do not delete this print } } - static private func isAllowed(_ newLevel:FSLevel)-> Bool{ - + private static func isAllowed(_ newLevel: FSLevel) -> Bool { let currentLevel = Flagship.sharedInstance.currentConfig.logLevel return ((newLevel.rawValue < currentLevel.rawValue) || (newLevel.rawValue == currentLevel.rawValue)) } - } diff --git a/FlagShip/Source/Models/FSContext.swift b/FlagShip/Source/Models/FSContext.swift index dedd7cbb..a3fa26a1 100644 --- a/FlagShip/Source/Models/FSContext.swift +++ b/FlagShip/Source/Models/FSContext.swift @@ -5,30 +5,25 @@ // Created by Adel on 07/09/2021. // - internal class FSContext { + private var _currentContext: [String: Any] = [:] - - private var _currentContext:[String:Any] = [:] - - - init(_ contextValues:[String:Any]){ + init(_ contextValues: [String: Any]) { /// Clean context with none valide type - self._currentContext = contextValues.filter{ $0.value is Int || $0.value is Double || $0.value is String || $0.value is Bool} + self._currentContext = contextValues.filter { $0.value is Int || $0.value is Double || $0.value is String || $0.value is Bool } self._currentContext = contextValues // Set all_users key - self._currentContext.updateValue("", forKey: ALL_USERS) + _currentContext.updateValue("", forKey: ALL_USERS) } - public func updateContext(_ newValues:[String : Any]){ - + public func updateContext(_ newValues: [String: Any]) { FlagshipLogManager.Log(level: .INFO, tag: .UPDATE_CONTEXT, messageToDisplay: FSLogMessage.UPDATE_CONTEXT) - for key in newValues.keys{ - if let val = newValues[key]{ + for key in newValues.keys { + if let val = newValues[key] { switch val { case is Int, is Double, is String, is Bool: updateContext(key, val) - break + default: FlagshipLogManager.Log(level: .ERROR, tag: .UPDATE_CONTEXT, messageToDisplay: FSLogMessage.UPDATE_CONTEXT_FAILED(key)) } @@ -36,35 +31,24 @@ internal class FSContext { } } - - public func updateContext(_ key:String , _ newValue:Any){ - + public func updateContext(_ key: String, _ newValue: Any) { _currentContext.updateValue(newValue, forKey: key) } - - /// Load preSet Context - func loadPreSetContext(){ - - _currentContext.merge(FlagshipContextManager.getPresetContextForApp()) { (_, new) in new } + func loadPreSetContext() { + _currentContext.merge(FlagshipContextManager.getPresetContextForApp()) { _, new in new } } - - func getCurrentContext()->[String:Any]{ - + func getCurrentContext() -> [String: Any] { return _currentContext } - func clearContext(){ - + func clearContext() { _currentContext.removeAll() } - func mergeContext(_ ctxValue:[String:Any]){ - - /// To do later + func mergeContext(_ ctxValue: [String: Any]) { + /// To do later } - - } From 82bc0244fad02a7bf34e55a76e7c5b8f1ba65b47 Mon Sep 17 00:00:00 2001 From: ABTastyAdel Date: Tue, 22 Aug 2023 11:06:44 +0100 Subject: [PATCH 2/5] add unit test --- FlagShip/FlagShipTests/FSFlagTest.swift | 18 ++++++++++++++++++ FlagShip/Source/Core/FSVisitor.swift | 6 +----- .../Source/Strategy/FSDefaultStrategy.swift | 2 ++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/FlagShip/FlagShipTests/FSFlagTest.swift b/FlagShip/FlagShipTests/FSFlagTest.swift index b8b3e1c9..941a34d8 100644 --- a/FlagShip/FlagShipTests/FSFlagTest.swift +++ b/FlagShip/FlagShipTests/FSFlagTest.swift @@ -44,6 +44,8 @@ class FSFlagTest: XCTestCase { /// Create new visitor testVisitor = Flagship.sharedInstance.newVisitor("alias").build() + // Check if the flagsync is Created + XCTAssertTrue(testVisitor?.flagSyncStatus == .CREATED) /// Set fake session if let aUrlFakeSession = urlFakeSession { testVisitor?.configManager.decisionManager?.networkService.serviceSession = aUrlFakeSession @@ -54,6 +56,8 @@ class FSFlagTest: XCTestCase { let expectationSync = XCTestExpectation(description: "Service-GetScript") testVisitor?.fetchFlags(onFetchCompleted: { + /// Check if flagSync is fetched + XCTAssertTrue(self.testVisitor?.flagSyncStatus == .FLAGS_FETCHED) if let flag = self.testVisitor?.getFlag(key: "btnTitle", defaultValue: "dfl") { XCTAssertTrue(flag.value() as! String == "Alpha_demoApp") XCTAssertTrue(flag.exists()) @@ -94,6 +98,20 @@ class FSFlagTest: XCTestCase { XCTAssertTrue(FSFlagMetadata(nil).slug == "") } + func testFlagSyncStatus() { + let syncUser = Flagship.sharedInstance.newVisitor("userSync", instanceType: .NEW_INSTANCE).build() + XCTAssertTrue(syncUser.flagSyncStatus == .CREATED) + // Update context + syncUser.updateContext(["keySync": "valSync"]) + XCTAssertTrue(syncUser.flagSyncStatus == .CONTEXT_UPDATED) + // Autenticate + syncUser.authenticate(visitorId: "syncUser") + XCTAssertTrue(syncUser.flagSyncStatus == .AUTHENTICATED) + // Unauthenticate + syncUser.unauthenticate() + XCTAssertTrue(syncUser.flagSyncStatus == .UNAUTHENTICATED) + } + func testGetFlagOnPanic() { let expectationSync = XCTestExpectation(description: "Service-GetScript") do { diff --git a/FlagShip/Source/Core/FSVisitor.swift b/FlagShip/Source/Core/FSVisitor.swift index 34d2a6de..14617c57 100644 --- a/FlagShip/Source/Core/FSVisitor.swift +++ b/FlagShip/Source/Core/FSVisitor.swift @@ -130,10 +130,6 @@ import Foundation if self.configManager.flagshipConfig.mode == .BUCKETING, Flagship.sharedInstance.currentStatus != .PANIC_ON { self.sendHit(FSSegment(self.getContext())) } - - // Update the flagSyncStatus - self.flagSyncStatus = .FLAGS_FETCHED - }) } @@ -243,7 +239,7 @@ import Foundation /// - Parameter defaultValue:flag default value /// - Returns: FSFlag object, If no flag match the given key, an empty flag will be returned public func getFlag(key: String, defaultValue: T?) -> FSFlag { - // We dispaly a warning when the flag status is not fetched + // We dispaly a warning if the flag's status is not fetched if self.flagSyncStatus != .FLAGS_FETCHED { FlagshipLogManager.Log(level: .ALL, tag: .FLAG, messageToDisplay: FSLogMessage.MESSAGE(self.flagSyncStatus.warningMessage(key, self.visitorId))) } diff --git a/FlagShip/Source/Strategy/FSDefaultStrategy.swift b/FlagShip/Source/Strategy/FSDefaultStrategy.swift index fb706784..7d796644 100644 --- a/FlagShip/Source/Strategy/FSDefaultStrategy.swift +++ b/FlagShip/Source/Strategy/FSDefaultStrategy.swift @@ -89,6 +89,8 @@ class FSDefaultStrategy: FSDelegateStrategy { Flagship.sharedInstance.currentStatus = .READY // Resume the process batching when the panic mode is OFF self.visitor.configManager.trackingManager?.resumeBatchingProcess() + // Update the flagSyncStatus + self.visitor.flagSyncStatus = .FLAGS_FETCHED onSyncCompleted(.READY) } } else { From 9f4e67d68399d38b0b4f72d905712ad6eea26956 Mon Sep 17 00:00:00 2001 From: ABTastyAdel Date: Tue, 22 Aug 2023 16:09:41 +0100 Subject: [PATCH 3/5] clean code --- FlagShip/Source/Core/FSFlag.swift | 34 ++++++++++++++++++++++++++++ FlagShip/Source/Core/FSVisitor.swift | 31 +------------------------ 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/FlagShip/Source/Core/FSFlag.swift b/FlagShip/Source/Core/FSFlag.swift index 7cd178fd..ec486a29 100644 --- a/FlagShip/Source/Core/FSFlag.swift +++ b/FlagShip/Source/Core/FSFlag.swift @@ -128,3 +128,37 @@ public class FSFlag: NSObject { "slug": slug] } } + +/** + * This status represent the flag status depend on visitor actions + */ +@objc public enum FlagSynchStatus: Int { + // When visitor is created + case CREATED + // When visitor context is updated + case CONTEXT_UPDATED + // When visitor Fetched flags + case FLAGS_FETCHED + // When visitor is authenticated + case AUTHENTICATED + // When visitor is unauthorised + case UNAUTHENTICATED + + func warningMessage(_ flagKey: String, _ visitorId: String)->String { + var ret = "Visitor `\(visitorId)` has been created without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + switch self { + case .CREATED: + break + case .CONTEXT_UPDATED: + ret = "Visitor context for visitor `\(visitorId)` has been updated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + case .AUTHENTICATED: + ret = "Visitor `\(visitorId)` has been authenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + case .UNAUTHENTICATED: + ret = "Visitor `\(visitorId)` has been unauthenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + default: + break + } + + return ret + } +} diff --git a/FlagShip/Source/Core/FSVisitor.swift b/FlagShip/Source/Core/FSVisitor.swift index 14617c57..197711f1 100644 --- a/FlagShip/Source/Core/FSVisitor.swift +++ b/FlagShip/Source/Core/FSVisitor.swift @@ -24,35 +24,6 @@ import Foundation case NEW_INSTANCE } -/** - * This status represent the flag status depend on visitor actions - */ -@objc public enum FlagSynchStatus: Int { - case CREATED - case CONTEXT_UPDATED - case FLAGS_FETCHED - case AUTHENTICATED - case UNAUTHENTICATED - - func warningMessage(_ flagKey: String, _ visitorId: String) -> String { - var ret = "Visitor `\(visitorId)` has been created without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." - switch self { - case .CREATED: - break - case .CONTEXT_UPDATED: - ret = "Visitor context for visitor `\(visitorId)` has been updated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." - case .AUTHENTICATED: - ret = "Visitor `\(visitorId)` has been authenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." - case .UNAUTHENTICATED: - ret = "Visitor `\(visitorId)` has been unauthenticated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." - default: - break - } - - return ret - } -} - /// Visitor class @objc public class FSVisitor: NSObject { let fsQueue = DispatchQueue(label: "com.flagshipVisitor.queue", attributes: .concurrent) @@ -85,7 +56,7 @@ import Foundation /// Assigned hsitory internal var assignedVariationHistory: [String: String] = [:] - public internal(set) var flagSyncStatus: FlagSynchStatus = .CREATED + internal var flagSyncStatus: FlagSynchStatus = .CREATED init(aVisitorId: String, aContext: [String: Any], aConfigManager: FSConfigManager, aHasConsented: Bool, aIsAuthenticated: Bool) { /// Set authenticated From 2809ff1ebe0e81d20e39bdf981dfca563106cede Mon Sep 17 00:00:00 2001 From: ABTastyAdel Date: Wed, 23 Aug 2023 10:14:06 +0100 Subject: [PATCH 4/5] clean code --- FlagShip.podspec | 2 +- FlagShip/Flagship.xcodeproj/project.pbxproj | 4 +- FlagShip/Source/Core/FSFlag.swift | 2 +- .../Source/Logger/FlagshipConstants.swift | 4 +- FlagShip/Source/Tools/FlagShipVersion.swift | 2 +- .../QApp/FSModificationsViewCtrl.swift | 435 ++++++------------ 6 files changed, 157 insertions(+), 292 deletions(-) diff --git a/FlagShip.podspec b/FlagShip.podspec index a19a100b..ac5c961c 100644 --- a/FlagShip.podspec +++ b/FlagShip.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "FlagShip" - s.version = "3.2.0" + s.version = "3.2.1" s.summary = "Flagship SDK" # This description is used to generate tags and improve search results. diff --git a/FlagShip/Flagship.xcodeproj/project.pbxproj b/FlagShip/Flagship.xcodeproj/project.pbxproj index bc91387f..85547962 100644 --- a/FlagShip/Flagship.xcodeproj/project.pbxproj +++ b/FlagShip/Flagship.xcodeproj/project.pbxproj @@ -1681,7 +1681,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.2.0; + MARKETING_VERSION = 3.2.1; OTHER_LDFLAGS = ""; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip; @@ -1714,7 +1714,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.2.0; + MARKETING_VERSION = 3.2.1; OTHER_LDFLAGS = ""; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip; diff --git a/FlagShip/Source/Core/FSFlag.swift b/FlagShip/Source/Core/FSFlag.swift index ec486a29..e44e9031 100644 --- a/FlagShip/Source/Core/FSFlag.swift +++ b/FlagShip/Source/Core/FSFlag.swift @@ -24,7 +24,7 @@ public class FSFlag: NSObject { if let flagModification = strategy?.getStrategy().getFlagModification(key) { if isSameType(flagModification.value) { /// _ have type same with default value /// - FlagshipLogManager.Log(level: .ALL, tag: .GET_MODIFICATION, messageToDisplay: .MESSAGE("Return the value for flag \(flagModification.value)")) + FlagshipLogManager.Log(level: .ALL, tag: .GET_MODIFICATION, messageToDisplay: .MESSAGE("The value of the flag `\(key)` is `\(flagModification.value)")) result = flagModification.value } else { diff --git a/FlagShip/Source/Logger/FlagshipConstants.swift b/FlagShip/Source/Logger/FlagshipConstants.swift index dc69bc8d..97f5ad36 100644 --- a/FlagShip/Source/Logger/FlagshipConstants.swift +++ b/FlagShip/Source/Logger/FlagshipConstants.swift @@ -150,9 +150,9 @@ enum FSLogMessage: CustomStringConvertible { case .IGNORE_UNAUTHENTICATE: ret = "UnAuthenticateVisitor method will be ignored in Bucketing configuration" case .GET_CAMPAIGN(let key): - ret = "Get campaign, the context used for is \(key)" + ret = "Fetching flags, the user context used for is:\(key)" case .GET_CAMPAIGN_URL(let key): - ret = "The url for the get campaign is \(key)" + ret = "Fetch flags request: \(key)" case .GET_CAMPAIGN_RESPONSE(let key): ret = "Response for fetch flags is \(key)" case .GET_SCRIPT_RESPONSE(let key): diff --git a/FlagShip/Source/Tools/FlagShipVersion.swift b/FlagShip/Source/Tools/FlagShipVersion.swift index 20de694e..b1c75736 100644 --- a/FlagShip/Source/Tools/FlagShipVersion.swift +++ b/FlagShip/Source/Tools/FlagShipVersion.swift @@ -9,4 +9,4 @@ import Foundation /// This file is automatically updated 2.0.0 -public let FlagShipVersion = "3.2.0" +public let FlagShipVersion = "3.2.1" diff --git a/iOS Example/QApp/FSModificationsViewCtrl.swift b/iOS Example/QApp/FSModificationsViewCtrl.swift index b82494b2..44ffd4e6 100644 --- a/iOS Example/QApp/FSModificationsViewCtrl.swift +++ b/iOS Example/QApp/FSModificationsViewCtrl.swift @@ -6,157 +6,110 @@ // Copyright © 2020 FlagShip. All rights reserved. // -import UIKit import Flagship - - +import UIKit internal enum FSValueType { - case DoubleType case IntegerType case StringType case BooleanType } - - class FSModificationsViewCtrl: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate { - - - - - let sourcePicker:[String] = ["String","Integer","Double","Boolean"] + let sourcePicker: [String] = ["String", "Integer", "Double", "Boolean"] - - - - @IBOutlet var valueLabel:FSLabel? - @IBOutlet var variationIdLabel:FSLabel? - @IBOutlet var variationGroupIdLabel:FSLabel? - @IBOutlet var campaigIdLabel:FSLabel? - @IBOutlet var isReferenceLabel:FSLabel? + @IBOutlet var valueLabel: FSLabel? + @IBOutlet var variationIdLabel: FSLabel? + @IBOutlet var variationGroupIdLabel: FSLabel? + @IBOutlet var campaigIdLabel: FSLabel? + @IBOutlet var isReferenceLabel: FSLabel? + @IBOutlet var keyTextField: UITextField? + @IBOutlet var defaultValueField: UITextField? - @IBOutlet var keyTextField:UITextField? - - @IBOutlet var defaultValueField:UITextField? - - @IBOutlet var defaultValueSwitch:UISwitch? + @IBOutlet var defaultValueSwitch: UISwitch? - @IBOutlet var typePicker:UIPickerView? + @IBOutlet var typePicker: UIPickerView? - @IBOutlet var activateBtn:UIButton? - @IBOutlet var getBtn:UIButton? - @IBOutlet var jsonViewBtn:UIButton? + @IBOutlet var activateBtn: UIButton? + @IBOutlet var getBtn: UIButton? + @IBOutlet var jsonViewBtn: UIButton? - @IBOutlet var jsonView:UITextView? - @IBOutlet var scrollView:UIScrollView? + @IBOutlet var jsonView: UITextView? + @IBOutlet var scrollView: UIScrollView? - var flagObject:FSFlag? + var flagObject: FSFlag? - - - - - override var preferredStatusBarStyle: UIStatusBarStyle{ - + override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } - - - override func viewDidLoad() { super.viewDidLoad() let redPlaceholderText = NSAttributedString(string: "Default value", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray]) + attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray]) defaultValueField?.attributedPlaceholder = redPlaceholderText // Set the default value for picker - typePicker?.selectRow(0, inComponent:0, animated:true) + typePicker?.selectRow(0, inComponent: 0, animated: true) defaultValueSwitch?.isHidden = true - // Do any additional setup after loading the view. - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideKeyBoard))) + view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideKeyBoard))) FSCTools.roundButton(activateBtn) activateBtn?.isEnabled = false FSCTools.roundButton(getBtn) - // getBtn?.isEnabled = false + // getBtn?.isEnabled = false FSCTools.roundButton(jsonViewBtn) - - - } // Hide KeyBoard - @objc func hideKeyBoard(){ - - self.view.endEditing(true) + @objc func hideKeyBoard() { + view.endEditing(true) } - - - - /// Actions - @IBAction func onClikcGetValue(){ - let result:Any? + @IBAction func onClikcGetValue() { + let result: Any? // get default value if let defaultValueInput = defaultValueField?.text { - if let keyValueInput = keyTextField?.text { - /// Get the current selected let typeValue = getTypeValue() switch typeValue { - case .BooleanType: - flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue:defaultValueSwitch?.isOn ?? false) + flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue: defaultValueSwitch?.isOn ?? false) - break case .StringType: - flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue:defaultValueInput) + flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue: defaultValueInput) - break case .IntegerType: let inputInt = Int(String(format: "%@", defaultValueInput)) ?? 0 - flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue:inputInt) + flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue: inputInt) - break case .DoubleType: let inputDbl = Double(String(format: "%@", defaultValueInput)) ?? 0 - flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue:inputDbl) - break + flagObject = Flagship.sharedInstance.sharedVisitor?.getFlag(key: keyValueInput, defaultValue: inputDbl) } let dicoInfo = flagObject?.metadata().toJson() - /// check the existing key - - if let aFlagObject = flagObject{ - - if aFlagObject.exists(){ - - print("the key exist") - } - } + /// check the existing key DispatchQueue.main.async { - self.valueLabel?.text = "\(self.flagObject?.value() ?? "unknown")" self.variationIdLabel?.text = "\(dicoInfo?["variationId"] ?? "unknown")" @@ -165,250 +118,182 @@ class FSModificationsViewCtrl: UIViewController, UIPickerViewDelegate, UIPickerV self.campaigIdLabel?.text = "\(dicoInfo?["campaignId"] ?? "unknown")" - self.isReferenceLabel?.text = (dicoInfo?["isReference"] as? Bool ?? false) ? "YES": "NO" + self.isReferenceLabel?.text = (dicoInfo?["isReference"] as? Bool ?? false) ? "YES" : "NO" } + } } } - -} - - -@IBAction func onClickActivate(){ - - if let keyToActivate = keyTextField?.text { - - if keyToActivate.count != 0{ - - if let flag = flagObject { - var msg:String = "" - if flag.exists(){ - - msg = "Activate \(keyToActivate)" - flag.userExposed() - }else{ - - msg = "\(keyToActivate) not found" - } + @IBAction func onClickActivate() { + if let keyToActivate = keyTextField?.text { + if keyToActivate.count != 0 { + if let flag = flagObject { + var msg = "" + if flag.exists() { + msg = "Activate \(keyToActivate)" + flag.userExposed() + + } else { + msg = "\(keyToActivate) not found" + } - let alertCtrl = UIAlertController(title: "Flagship/Activate", message:msg, preferredStyle: .alert) - alertCtrl.addAction(UIAlertAction(title: "OK", style: .cancel,handler: nil)) - self.present(alertCtrl, animated: true, completion: nil) - - } + let alertCtrl = UIAlertController(title: "Flagship/Activate", message: msg, preferredStyle: .alert) + alertCtrl.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) + present(alertCtrl, animated: true, completion: nil) + } + } } } -} - - @IBAction func onClickJsonView(){ - + @IBAction func onClickJsonView() { /// If sselected ====> Bucketing mode - if let isSelected = jsonViewBtn?.isSelected{ - + if let isSelected = jsonViewBtn?.isSelected { jsonViewBtn?.isSelected = !isSelected - if (!isSelected){ - jsonView?.isHidden = false + if !isSelected { + jsonView?.isHidden = false jsonView?.text = "All the modifictaions : \n" - - - if let dataAPi = readCampaignFromCache(){ - + if let dataAPi = readCampaignFromCache() { do { let dico = try JSONSerialization.jsonObject(with: dataAPi, options: .fragmentsAllowed) jsonView?.text.append("\n\n\n") jsonView?.text.append("Api Response is : \n \(dico)") - } catch { - - - } - - - + } catch {} } scrollView?.setContentOffset(.zero, animated: true) - }else{ - - jsonView?.isHidden = true - } + } else { + jsonView?.isHidden = true + } } - } + //// delegate picker + func numberOfComponents(in pickerView: UIPickerView) -> Int { + 1 + } -//// delegate picker - -func numberOfComponents(in pickerView: UIPickerView) -> Int { - - 1 -} - -func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return sourcePicker.count -} - - - - - -func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - - return sourcePicker[row] -} + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return sourcePicker.count + } - + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return sourcePicker[row] + } -func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + print(sourcePicker[row] as String) - print(sourcePicker[row] as String) - - if sourcePicker[row] == "Boolean"{ - - /// change textfiled to switch - DispatchQueue.main.async { - - self.defaultValueSwitch?.isHidden = false - self.defaultValueField?.isHidden = true - - } - }else{ - - DispatchQueue.main.async { - - self.defaultValueSwitch?.isHidden = true - self.defaultValueField?.isHidden = false + if sourcePicker[row] == "Boolean" { + /// change textfiled to switch + DispatchQueue.main.async { + self.defaultValueSwitch?.isHidden = false + self.defaultValueField?.isHidden = true + } + } else { + DispatchQueue.main.async { + self.defaultValueSwitch?.isHidden = true + self.defaultValueField?.isHidden = false + } } - } -} - - -// ["String","Integer","Double","Boolean"] -private func getTypeValue()->FSValueType{ - - switch typePicker?.selectedRow(inComponent: 0) { - case 0: - return .StringType - case 1: - return .IntegerType - case 2: - return .DoubleType - case 3: - return .BooleanType + // ["String","Integer","Double","Boolean"] + + private func getTypeValue() -> FSValueType { + switch typePicker?.selectedRow(inComponent: 0) { + case 0: + return .StringType + case 1: + return .IntegerType + case 2: + return .DoubleType + case 3: + return .BooleanType - default: - return .StringType + default: + return .StringType + } } -} - - -//// delegate text field -/// Review this part -func textFieldDidEndEditing(_ textField: UITextField) { - // - // var inputDouble:Double - // var inputInt:Int - // - // if (textField.tag == 2020){ - // - // if (textField.text?.contains(",") ?? false){ - // - // inputDouble = textField.text?.doubleValue ?? 0 - // Flagship.sharedInstance.updateContext(NumberKey, inputDouble) - // - // }else if (textField.text?.contains(".") ?? false){ - // - // inputDouble = textField.text?.doubleValue ?? 0 - // Flagship.sharedInstance.updateContext(NumberKey, inputDouble) - // }else{ - // - // inputInt = Int(String(format: "%@", textField.text ?? "0")) ?? 0 - // Flagship.sharedInstance.updateContext(NumberKey, inputInt) - // } - // } - -} + //// delegate text field + /// Review this part + func textFieldDidEndEditing(_ textField: UITextField) { + // + // var inputDouble:Double + // var inputInt:Int + // + // if (textField.tag == 2020){ + // + // if (textField.text?.contains(",") ?? false){ + // + // inputDouble = textField.text?.doubleValue ?? 0 + // Flagship.sharedInstance.updateContext(NumberKey, inputDouble) + // + // }else if (textField.text?.contains(".") ?? false){ + // + // inputDouble = textField.text?.doubleValue ?? 0 + // Flagship.sharedInstance.updateContext(NumberKey, inputDouble) + // }else{ + // + // inputInt = Int(String(format: "%@", textField.text ?? "0")) ?? 0 + // Flagship.sharedInstance.updateContext(NumberKey, inputInt) + // } + // } + } + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if textField.tag == 100 { + let currentText = textField.text ?? "" + // attempt to read the range they are trying to change, or exit if we can't + guard let stringRange = Range(range, in: currentText) else { return false } -func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if (textField.tag == 100){ + // add their new text to the existing text + let updatedText = currentText.replacingCharacters(in: stringRange, with: string) - let currentText = textField.text ?? "" - - // attempt to read the range they are trying to change, or exit if we can't - guard let stringRange = Range(range, in: currentText) else { return false } + if updatedText.count > 0 { + activateBtn?.isEnabled = true + getBtn?.isEnabled = true - // add their new text to the existing text - let updatedText = currentText.replacingCharacters(in: stringRange, with: string) + } else { + activateBtn?.isEnabled = false + getBtn?.isEnabled = false + } + } else if typePicker?.selectedRow(inComponent: 0) == 2 || typePicker?.selectedRow(inComponent: 0) == 1 { + let invalidCharacters = CharacterSet(charactersIn: "0123456789.").inverted - if (updatedText.count > 0){ - - activateBtn?.isEnabled = true - getBtn?.isEnabled = true - - - }else{ - - activateBtn?.isEnabled = false - getBtn?.isEnabled = false - + return (string.rangeOfCharacter(from: invalidCharacters) == nil) } - - } - else if(typePicker?.selectedRow(inComponent:0) == 2 || typePicker?.selectedRow(inComponent:0) == 1){ - - let invalidCharacters = CharacterSet(charactersIn: "0123456789.").inverted - - return (string.rangeOfCharacter(from: invalidCharacters) == nil) - + return true } - return true -} - - - -func textFieldShouldReturn(_ textField: UITextField) -> Bool { - - self.view.endEditing(true) - -} - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + view.endEditing(true) + } - func readCampaignFromCache()->Data?{ - - if var url:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { + func readCampaignFromCache() -> Data? { + if var url: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { // Path url.appendPathComponent("FlagShipCampaign", isDirectory: true) // add file name url.appendPathComponent("campaigns.json") - if (FileManager.default.fileExists(atPath: url.path) == true){ - - do{ - - + if FileManager.default.fileExists(atPath: url.path) == true { + do { let data = try Data(contentsOf: url) return data - }catch{ + } catch { print("errror on read data") return nil } - }else{ - + } else { return nil } } @@ -416,7 +301,6 @@ func textFieldShouldReturn(_ textField: UITextField) -> Bool { } } - extension Data { var prettyPrintedJSONString: NSString? { /// NSString gives us a nice sanitized debugDescription guard let object = try? JSONSerialization.jsonObject(with: self, options: []), @@ -427,25 +311,9 @@ extension Data { } } - - - - - - - - - - - - - -class FSLabel:UILabel{ - - +class FSLabel: UILabel { override init(frame: CGRect) { super.init(frame: frame) - } override func draw(_ rect: CGRect) { @@ -454,10 +322,7 @@ class FSLabel:UILabel{ layer.borderColor = UIColor(red: 223/250, green: 68/250, blue: 110/250, alpha: 1).cgColor } - required init?(coder: NSCoder) { super.init(coder: coder) } - - } From 1948c700893f14122a6bedd474c7d6903b770e87 Mon Sep 17 00:00:00 2001 From: ABTastyAdel Date: Wed, 23 Aug 2023 10:53:05 +0100 Subject: [PATCH 5/5] post review --- FlagShip/Source/Core/FSFlag.swift | 10 +++++++--- FlagShip/Source/Core/FSVisitor.swift | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/FlagShip/Source/Core/FSFlag.swift b/FlagShip/Source/Core/FSFlag.swift index e44e9031..492ac084 100644 --- a/FlagShip/Source/Core/FSFlag.swift +++ b/FlagShip/Source/Core/FSFlag.swift @@ -132,7 +132,7 @@ public class FSFlag: NSObject { /** * This status represent the flag status depend on visitor actions */ -@objc public enum FlagSynchStatus: Int { +@objc internal enum FlagSynchStatus: Int { // When visitor is created case CREATED // When visitor context is updated @@ -144,11 +144,15 @@ public class FSFlag: NSObject { // When visitor is unauthorised case UNAUTHENTICATED + /** + Return the string for the flag warning message. + Note: No message for FLAGS_FETCHED state + */ func warningMessage(_ flagKey: String, _ visitorId: String)->String { - var ret = "Visitor `\(visitorId)` has been created without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." + var ret = "" switch self { case .CREATED: - break + ret = "Visitor `\(visitorId)` has been created without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." case .CONTEXT_UPDATED: ret = "Visitor context for visitor `\(visitorId)` has been updated without calling `fetchFlags` method afterwards, the value of the flag `\(flagKey)` may be outdated." case .AUTHENTICATED: diff --git a/FlagShip/Source/Core/FSVisitor.swift b/FlagShip/Source/Core/FSVisitor.swift index 197711f1..45b549ad 100644 --- a/FlagShip/Source/Core/FSVisitor.swift +++ b/FlagShip/Source/Core/FSVisitor.swift @@ -56,6 +56,7 @@ import Foundation /// Assigned hsitory internal var assignedVariationHistory: [String: String] = [:] + // Initial value for the status .CREATED internal var flagSyncStatus: FlagSynchStatus = .CREATED init(aVisitorId: String, aContext: [String: Any], aConfigManager: FSConfigManager, aHasConsented: Bool, aIsAuthenticated: Bool) {