From af1dd141b9ef55dca1f7b62befd1a72f7cd91ef4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Nov 2024 11:17:14 +0000 Subject: [PATCH] RMET-3822 - Firebase Analytics - Prepare to release version 5.0.0-OS16 (#47) * RMET-3822 Firebase Analytics - Android - Convert values to double if necessary (#45) * fix: if key is "value", "tax", or "shipping", then its value should be a number (double) References: https://outsystemsrd.atlassian.net/browse/RMET-3822 * chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-3822 * RMET-3823 ::: iOS ::: Fix Ecommerce Decimal Values (#46) * fix(ios): make InputParameterData accept value types besides string This requires the code to validate the type being analysed. Considering that we want to split between the available types (Decimal and String), we declare the 'OSFANLInputDataFieldKey' values that should have a Decimal value and be treated differently. These changes allow us to - refactor the code a bit, namely the 'OSFANLManager''s 'validate(dataField:isRequired:eventData:)' method, making it much simpler than it was. - remove the now unused 'StringConvertable' initialiser. References: https://outsystemsrd.atlassian.net/browse/RMET-3823 * fix: remove default value when Decimal can't be initialised Instead of setting the value to 'zero' (which is wrong), throw an error mentioning that an invalid value was provided. Simplify the decimal verification call by using a boolean computed property. References: https://outsystemsrd.atlassian.net/browse/RMET-3823 * chore: add CHANGELOG entry References: https://outsystemsrd.atlassian.net/browse/RMET-3823 * chore: raise to version 5.0.0-OS16 References: https://outsystemsrd.atlassian.net/browse/RMET-3822 https://outsystemsrd.atlassian.net/browse/RMET-3823 --------- Co-authored-by: Ricardo Silva <97543217+OS-ricardomoreirasilva@users.noreply.github.com> --- CHANGELOG.md | 4 +++ package.json | 2 +- plugin.xml | 2 +- .../OSFANLEventParameterValidator.kt | 20 ++++++++++- .../Common/Extensions/StringConvertable.swift | 9 ----- src/ios/Common/OSFANLInputDataFieldKey.swift | 8 +++++ .../OSFANLInputTransformer.swift | 33 +++++++++++++------ src/ios/Manager/OSFANLManageable.swift | 2 +- .../OSFANLManager+EventValidationRules.swift | 21 ++++++------ src/ios/Manager/OSFANLManager.swift | 6 ++-- 10 files changed, 71 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8e16b..ab37d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The changes documented here do not include those from the original repository. +## 5.0.0-OS16 + +- Fix(Android & iOS): Convert values to double if necessary (https://outsystemsrd.atlassian.net/browse/RMET-3822 & https://outsystemsrd.atlassian.net/browse/RMET-3823). + ## 5.0.0-OS15 - Feat(Android & iOS): Add setConsent functionality to allow users to set consent for analytics (https://outsystemsrd.atlassian.net/browse/RMET-3677 & https://outsystemsrd.atlassian.net/browse/RMET-3678). diff --git a/package.json b/package.json index 136ba9a..b2b9468 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-firebase-analytics", - "version": "5.0.0-OS15", + "version": "5.0.0-OS16", "description": "Cordova plugin for Firebase Analytics", "cordova": { "id": "cordova-plugin-firebase-analytics", diff --git a/plugin.xml b/plugin.xml index 7851680..9b7d2dd 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,7 +1,7 @@ + version="5.0.0-OS16"> FirebaseAnalyticsPlugin Cordova plugin for Firebase Analytics diff --git a/src/android/com/outsystems/firebase/analytics/validator/OSFANLEventParameterValidator.kt b/src/android/com/outsystems/firebase/analytics/validator/OSFANLEventParameterValidator.kt index 3cd61de..ab6e954 100644 --- a/src/android/com/outsystems/firebase/analytics/validator/OSFANLEventParameterValidator.kt +++ b/src/android/com/outsystems/firebase/analytics/validator/OSFANLEventParameterValidator.kt @@ -1,6 +1,7 @@ package com.outsystems.firebase.analytics.validator import android.os.Bundle +import android.util.Log import com.outsystems.firebase.analytics.model.OSFANLError import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.CURRENCY @@ -8,6 +9,8 @@ import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.EVENT_PAR import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.KEY import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.TYPE_NUMBER import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.VALUE +import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.TAX +import com.outsystems.firebase.analytics.model.OSFANLInputDataFieldKey.SHIPPING import com.outsystems.firebase.analytics.model.putAny import org.json.JSONArray @@ -85,7 +88,22 @@ class OSFANLEventParameterValidator private constructor( hasCurrency = hasCurrency || key == CURRENCY.json parameterKeySet.add(key) - result.putAny(key, value) + + try { + // value, tax, and shipping are actually decimal values, not strings + if (key == VALUE.json || key == TAX.json || key == SHIPPING.json) { + result.putAny(key, value.toDouble()) // can throw NumberFormatException + } else { + result.putAny(key, value) + } + } catch (e: NumberFormatException) { + Log.d( + "OSFANLEventParameterValidator", + "Tried to convert non-number to double. Exception: ${e.message}" + ) + // if conversion isn't successful, we still try to send the event + result.putAny(key, value) + } } // validate value / currency diff --git a/src/ios/Common/Extensions/StringConvertable.swift b/src/ios/Common/Extensions/StringConvertable.swift index b4de8fc..e8fe580 100644 --- a/src/ios/Common/Extensions/StringConvertable.swift +++ b/src/ios/Common/Extensions/StringConvertable.swift @@ -1,20 +1,11 @@ protocol StringConvertable { static var variableType: String { get } - init?(value: String) } extension Decimal: StringConvertable { static var variableType: String { "Decimal" } - - init?(value: String) { - self.init(string: value) - } } extension String: StringConvertable { static var variableType: String { "Text" } - - init?(value: String) { - self.init(stringLiteral: value) - } } diff --git a/src/ios/Common/OSFANLInputDataFieldKey.swift b/src/ios/Common/OSFANLInputDataFieldKey.swift index b177865..86aee1c 100644 --- a/src/ios/Common/OSFANLInputDataFieldKey.swift +++ b/src/ios/Common/OSFANLInputDataFieldKey.swift @@ -15,3 +15,11 @@ enum OSFANLInputDataFieldKey: String { case transactionId = "transaction_id" case value } + +extension OSFANLInputDataFieldKey { + private static var decimalDataFields: [OSFANLInputDataFieldKey] { [.shipping, .tax, .value] } + + var isDecimalType: Bool { + return OSFANLInputDataFieldKey.decimalDataFields.contains(self) + } +} diff --git a/src/ios/InputTransformer/OSFANLInputTransformer.swift b/src/ios/InputTransformer/OSFANLInputTransformer.swift index 89a7f6c..8a7d579 100644 --- a/src/ios/InputTransformer/OSFANLInputTransformer.swift +++ b/src/ios/InputTransformer/OSFANLInputTransformer.swift @@ -1,22 +1,35 @@ struct OSFANLInputTransformer: OSFANLInputTransformable { func transform(_ eventParameterArray: [InputParameterData]?, _ itemArray: [InputItemData]?) throws -> OSFANLInputTransformableModel { - var eventParameterData: InputParameterData? - if let eventParameterArray { - guard let flatResult = try? self.flat(keyValueMapArray: eventParameterArray) else { - throw OSFANLError.duplicateItemsIn(parameter: OSFANLInputDataFieldKey.eventParameters.rawValue) + do { + var eventParameterData: InputParameterData? + if let eventParameterArray { + eventParameterData = try self.flat(keyValueMapArray: eventParameterArray) } - eventParameterData = flatResult + let itemArray = try self.transform(itemArray) + + return .init(eventParameterData, itemArray) + } catch OSFANLError.duplicateKeys { + throw OSFANLError.duplicateItemsIn(parameter: OSFANLInputDataFieldKey.eventParameters.rawValue) } - let itemArray = try self.transform(itemArray) - - return .init(eventParameterData, itemArray) } } private extension OSFANLInputTransformer { func flat(keyValueMapArray array: [InputParameterData]) throws -> InputParameterData { - let flatKeyValueArray = array.map { - [$0[OSFANLInputDataFieldKey.key.rawValue, default: ""]: $0[OSFANLInputDataFieldKey.value.rawValue, default: ""]] + let flatKeyValueArray = try array.reduce(into: [InputParameterData]()) { partialResult, current in + guard let dataFieldKey = current[OSFANLInputDataFieldKey.key.rawValue] as? String, + let dataValue = current[OSFANLInputDataFieldKey.value.rawValue] as? String + else { return } + + let value: StringConvertable + if let dataField = OSFANLInputDataFieldKey(rawValue: dataFieldKey), dataField.isDecimalType { + guard let decimalValue = Decimal(string: dataValue) else { throw OSFANLError.invalidType(dataFieldKey, type: Decimal.variableType) } + value = decimalValue + } else { + value = dataValue + } + + partialResult.append([dataFieldKey: value]) } let flatKeyValueDictionary = self.flat(dictionaryArray: flatKeyValueArray) diff --git a/src/ios/Manager/OSFANLManageable.swift b/src/ios/Manager/OSFANLManageable.swift index 714579e..7bedc73 100644 --- a/src/ios/Manager/OSFANLManageable.swift +++ b/src/ios/Manager/OSFANLManageable.swift @@ -8,7 +8,7 @@ extension DefaultKeyValueData { } } -typealias InputParameterData = [String: String] +typealias InputParameterData = [String: Any] typealias InputItemData = DefaultKeyValueData typealias OutputParameterData = DefaultKeyValueData diff --git a/src/ios/Manager/OSFANLManager+EventValidationRules.swift b/src/ios/Manager/OSFANLManager+EventValidationRules.swift index d72bb68..28a14be 100644 --- a/src/ios/Manager/OSFANLManager+EventValidationRules.swift +++ b/src/ios/Manager/OSFANLManager+EventValidationRules.swift @@ -49,19 +49,17 @@ private extension OSFANLManager { // Validate parameters first if let parameters { if parameters.contains(.currencyAndValue) { - let valueContainsValue = try self.validate(parameter: OSFANLInputDataFieldKey.value.rawValue, ofType: Decimal.self, eventData) - try self.validate( - parameter: OSFANLInputDataFieldKey.currency.rawValue, ofType: String.self, isRequired: valueContainsValue, eventData - ) + let valueContainsValue = try self.validate(dataField: .value, eventData) + try self.validate(dataField: .currency, isRequired: valueContainsValue, eventData) } if parameters.contains(.transactionId) { - try self.validate(parameter: OSFANLInputDataFieldKey.transactionId.rawValue, ofType: String.self, isRequired: true, eventData) + try self.validate(dataField: .transactionId, isRequired: true, eventData) } if parameters.contains(.shipping) { - try self.validate(parameter: OSFANLInputDataFieldKey.shipping.rawValue, ofType: Decimal.self, eventData) + try self.validate(dataField: .shipping, eventData) } if parameters.contains(.tax) { - try self.validate(parameter: OSFANLInputDataFieldKey.tax.rawValue, ofType: Decimal.self, eventData) + try self.validate(dataField: .tax, eventData) } } @@ -112,15 +110,18 @@ private extension OSFANLManager { } @discardableResult - private static func validate(parameter: String, ofType type: T.Type, isRequired: Bool = false, _ eventData: InputParameterData?) throws -> Bool { + private static func validate(dataField: OSFANLInputDataFieldKey, isRequired: Bool = false, _ eventData: InputParameterData?) throws -> Bool { func espace(_ isRequired: Bool, parameterMissing parameter: String) throws -> Bool { if isRequired { throw OSFANLError.missing(parameter) } return false // indicates that there's no value associated to `parameter` } guard let eventData, !eventData.isEmpty else { return try espace(isRequired, parameterMissing: "eventParameters") } - guard let parameterStringValue = eventData[parameter] else { return try espace(isRequired, parameterMissing: parameter) } - if T(value: parameterStringValue) == nil { throw OSFANLError.invalidType(parameter, type: T.variableType) } + let parameter = dataField.rawValue + guard let parameterValue = eventData[parameter] else { return try espace(isRequired, parameterMissing: parameter) } + + let dataField: (type: StringConvertable.Type, value: StringConvertable?) = dataField.isDecimalType ? (Decimal.self, parameterValue as? Decimal) : (String.self, parameterValue as? String) + if dataField.value == nil { throw OSFANLError.invalidType(parameter, type: dataField.type.variableType) } return true // indicates that there's a value associated to `parameter` } } diff --git a/src/ios/Manager/OSFANLManager.swift b/src/ios/Manager/OSFANLManager.swift index 7059f0f..18861bf 100644 --- a/src/ios/Manager/OSFANLManager.swift +++ b/src/ios/Manager/OSFANLManager.swift @@ -15,17 +15,17 @@ extension OSFANLManager: OSFANLManageable { } func createEventModel(for inputArgument: InputParameterData) throws -> OSFANLOutputModel { - guard let eventName = inputArgument[OSFANLInputDataFieldKey.event.rawValue], !eventName.isEmpty else { + guard let eventName = inputArgument[OSFANLInputDataFieldKey.event.rawValue] as? String, !eventName.isEmpty else { throw OSFANLError.missing(OSFANLInputDataFieldKey.event.rawValue) } var eventParameterArray: [InputParameterData]? - if let eventParameterString = inputArgument[OSFANLInputDataFieldKey.eventParameters.rawValue] { + if let eventParameterString = inputArgument[OSFANLInputDataFieldKey.eventParameters.rawValue] as? String { eventParameterArray = self.convert(jsonString: eventParameterString) } var itemArray: [InputItemData]? - if let itemString = inputArgument[OSFANLInputDataFieldKey.items.rawValue] { + if let itemString = inputArgument[OSFANLInputDataFieldKey.items.rawValue] as? String { itemArray = self.convert(jsonString: itemString) }