diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift index 2872710..4dc0cd6 100644 --- a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -7,6 +7,7 @@ import FirebaseAnalytics // MARK: - Protocol protocol AnalyticsManagerProtocol { + func logScreen( name: String, path: String, @@ -23,75 +24,12 @@ protocol AnalyticsManagerProtocol { func setEnable(_ enable: Bool) } -// MARK: - Matomo - -class MatomoAnalyticsManager: AnalyticsManagerProtocol { - - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \(url)") - debugPrint("[Matomo service] Site ID: \(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen( - name: String, - path: String, - params: [String: Any]? - ) { - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } - - func setEnable(_ enable: Bool) { - tracker.isOptedOut = !enable - } -} - // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + func logScreen( name: String, path: String, @@ -158,16 +96,16 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol { parameters: parameters ) } + func setEnable(_ enable: Bool) { Analytics.setAnalyticsCollectionEnabled(enable) } } - // MARK: - Traker Type enum TrackerType: CaseIterable { - case matomo + case firebase } @@ -191,7 +129,7 @@ class AnalyticsManager { } } - // MARK: - Methods + // MARK: - Enable Methods private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in @@ -211,14 +149,12 @@ class AnalyticsManager { setAnalytics(enable: false, analytics) } - func configure(siteId: String, url: String) { - managers[TrackerType.matomo] = MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + func configure() { managers[TrackerType.firebase] = FirebaseAnalyticsManager() } + // MARK: - Private Log Methods + private func logScreen( name: String, path: String, @@ -255,8 +191,8 @@ class AnalyticsManager { // MARK: - section_one - func logScreenS1DefOne(title: String) { - logScreen( + static func logScreenS1DefOne(title: String) { + AnalyticsManager.shared.logScreen( name: "s1 def one \(title)", path: "s1_def_one/\(title)", params: nil @@ -283,8 +219,8 @@ class AnalyticsManager { // MARK: - section_two - func logScreenS2DefOne() { - logScreen( + static func logScreenS2DefOne() { + AnalyticsManager.shared.logScreen( name: "s2 def one", path: "s2_def_one/", params: nil diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index 3de2c99..536a461 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -54,7 +54,7 @@ echo "\n-------------------------\n" # Analytics swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ - --target "firebase matomo" \ + --target "firebase" \ --extension-output-path "./Tags/Generated" \ --extension-name "Analytics" \ --extension-suffix "GenAllScript" \ diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index 0cff0cf..8e775cd 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -5,11 +5,14 @@ // Created by Loris Perret on 08/12/2023. // +// CPD-OFF + import CoreVideo import Foundation import ToolCore // Disabled cause it's a pain to handle in generated string +// swiftlint:disable type_body_length enum AnalyticsGenerator { @@ -89,48 +92,49 @@ enum AnalyticsGenerator { ) -> String { """ // Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion) - + \(Self.getImport(targets: targets)) - + \(Self.getAnalyticsProtocol(targets: targets)) - - \(Self.getTrackerTypeEnum()) - + + \(Self.getTrackerTypeEnum(targets: targets)) + // MARK: - Manager class AnalyticsManager { - + static var shared = AnalyticsManager() - + private init() {} // MARK: - Properties - + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - + \(Self.getEnabledContent()) - + \(Self.getAnalyticsProperties(targets: targets)) - + \(Self.getPrivateLogFunction()) """ } - - private static func getTrackerTypeEnum() -> String { + + private static func getTrackerTypeEnum(targets: [TrackerType]) -> String { var result: [String] = [] - TrackerType.allCases.forEach { type in + targets.forEach { type in result.append(" case \(type)") } - + return """ // MARK: - Traker Type - + enum TrackerType: CaseIterable { + \(result.joined(separator: "\n")) } """ } - + private static func getEnabledContent() -> String { """ private var isEnabled: Bool { @@ -140,9 +144,9 @@ enum AnalyticsGenerator { true } } - - // MARK: - Methods - + + // MARK: - Enable Methods + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in if analytics.contains(where: { type in @@ -152,11 +156,11 @@ enum AnalyticsGenerator { } } } - + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: true, analytics) } - + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: false, analytics) } @@ -171,6 +175,7 @@ enum AnalyticsGenerator { if targets.contains(TrackerType.matomo) { result.append("import MatomoTracker") } + if targets.contains(TrackerType.firebase) { result.append("import FirebaseAnalytics") } @@ -180,13 +185,15 @@ enum AnalyticsGenerator { private static func getPrivateLogFunction() -> String { """ - private func logScreen( + // MARK: - Private Log Methods + + private func logScreen( name: String, path: String, params: [String: Any]? ) { guard isEnabled else { return } - + managers.values.forEach { manager in manager.logScreen( name: name, @@ -203,7 +210,7 @@ enum AnalyticsGenerator { params: [String: Any]? ) { guard isEnabled else { return } - + managers.values.forEach { manager in manager.logEvent( name: name, @@ -235,6 +242,7 @@ enum AnalyticsGenerator { ) """) } + if targets.contains(TrackerType.firebase) { content.append(" managers[TrackerType.firebase] = FirebaseAnalyticsManager()") } @@ -252,22 +260,22 @@ enum AnalyticsGenerator { // MARK: - Protocol protocol AnalyticsManagerProtocol { + func logScreen( name: String, path: String, params: [String: Any]? ) - + func logEvent( name: String, action: String, category: String, params: [String: Any]? ) - + func setEnable(_ enable: Bool) } - """ var result: [String] = [proto] @@ -280,7 +288,7 @@ enum AnalyticsGenerator { result.append(FirebaseGenerator.service) } - return result.joined(separator: "\n") + return result.joined(separator: "\n\n") } private static func getProperties( @@ -319,3 +327,5 @@ enum AnalyticsGenerator { """ } } + +// CPD-ON diff --git a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift index 8d2d22d..3e8ff0f 100644 --- a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift @@ -13,11 +13,11 @@ enum FirebaseGenerator { static var service: String { [ - FirebaseGenerator.header, - FirebaseGenerator.logScreen, - FirebaseGenerator.logEvent, - FirebaseGenerator.enable, - FirebaseGenerator.footer + Self.header, + Self.logScreen, + Self.logEvent, + Self.enable, + Self.footer ] .joined(separator: "\n") } @@ -29,6 +29,9 @@ enum FirebaseGenerator { // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + """ } @@ -42,11 +45,11 @@ enum FirebaseGenerator { var parameters = [ AnalyticsParameterScreenName: name as NSObject ] - + if path.isEmpty == false { parameters["path"] = path + "/iOS" as NSObject } - + if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in @@ -54,11 +57,11 @@ enum FirebaseGenerator { }) { continue } - + parameters[newKey] = newValue as? NSObject } } - + Analytics.logEvent( AnalyticsEventScreenView, parameters: parameters @@ -79,15 +82,15 @@ enum FirebaseGenerator { var parameters: [String:NSObject] = [ AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject ] - + if category.isEmpty == false { parameters["AnalyticsParameterItemCategory"] = category as NSObject } - + if action.isEmpty == false { parameters["action"] = action as NSObject } - + if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in @@ -105,9 +108,10 @@ enum FirebaseGenerator { parameters: parameters ) } + """ } - + private static var enable: String { """ func setEnable(_ enable: Bool) { @@ -115,11 +119,10 @@ enum FirebaseGenerator { } """ } - + private static var footer: String { """ } - """ } } diff --git a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift index 0217328..dd8c9bc 100644 --- a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift @@ -5,18 +5,20 @@ // Created by Loris Perret on 05/12/2023. // +// CPD-OFF + import Foundation enum MatomoGenerator { static var service: String { [ - MatomoGenerator.header, - MatomoGenerator.setup, - MatomoGenerator.logScreen, - MatomoGenerator.logEvent, - MatomoGenerator.enable, - MatomoGenerator.footer + Self.header, + Self.setup, + Self.logScreen, + Self.logEvent, + Self.enable, + Self.footer ] .joined(separator: "\n") } @@ -100,10 +102,10 @@ enum MatomoGenerator { url: nil ) } - + """ } - + private static var enable: String { """ func setEnable(_ enable: Bool) { @@ -115,7 +117,8 @@ enum MatomoGenerator { private static var footer: String { """ } - """ } } + +// CPD-ON diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift index c84bf1c..1946cbf 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift @@ -52,20 +52,22 @@ class AnalyticsDefinition { private func getParameters() -> String { var result: String - + let paramsString = parameters.compactMap { parameter -> String? in guard parameter.value.isEmpty else { return nil } - + let defaultValue: String switch parameter.type { case .bool: defaultValue = "\(parameter.defaultValue.lowercased())" + case .int, .double: defaultValue = "\(parameter.defaultValue)" + case .string: defaultValue = "\"\(parameter.defaultValue)\"" } - + let defaultValueString = parameter.defaultValue.isEmpty ? "" : " = \(defaultValue)" return "\(parameter.name): \(parameter.type.rawValue)\(defaultValueString)" } @@ -90,9 +92,13 @@ class AnalyticsDefinition { for rep in parameter.replaceIn { switch rep { case "name": name = name.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "path": path = path.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "action": action = action.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + default: if let param = parameters.first(where: { $0.name == rep }), param.value.isEmpty == false { param.value = param.value.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") @@ -117,8 +123,10 @@ class AnalyticsDefinition { switch param.type { case .bool: params.append("\"\(param.name)\": \(param.value.lowercased())") + case .int, .double: params.append("\"\(param.name)\": \(param.value)") + case .string: params.append("\"\(param.name)\": \"\(param.value)\"") } diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift index 42dd8b5..1accfa2 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift @@ -18,7 +18,7 @@ class AnalyticsParameter { var replaceIn: [String] = [] // MARK: - Init - + init(name: String, type: ParameterType, value: String, defaultValue: String) { self.name = name self.type = type diff --git a/Sources/ResgenSwift/Analytics/Model/ParameterType.swift b/Sources/ResgenSwift/Analytics/Model/ParameterType.swift index 1f60ef9..6cf8519 100644 --- a/Sources/ResgenSwift/Analytics/Model/ParameterType.swift +++ b/Sources/ResgenSwift/Analytics/Model/ParameterType.swift @@ -8,6 +8,7 @@ import Foundation enum ParameterType: String { + case string = "String" case int = "Int" case double = "Double" diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 64a5cec..6b2460d 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -70,7 +70,7 @@ class AnalyticsFileParser { private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { func verify(value: String?, for type: ParameterType) { guard let value, value.isEmpty == false else { return } - + switch type { case .int: if Int(value) == nil { @@ -78,23 +78,26 @@ class AnalyticsFileParser { print(error.description) Analytics.exit(withError: error) } + case .bool: if Bool(value.lowercased()) == nil { let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)") print(error.description) Analytics.exit(withError: error) } + case .double: if Double(value) == nil { let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)") print(error.description) Analytics.exit(withError: error) } + case .string: break } } - + return parameters.map { dtoParameter in // Type let type = dtoParameter.type.uppercasedFirst() @@ -104,13 +107,13 @@ class AnalyticsFileParser { print(error.description) Analytics.exit(withError: error) } - + if dtoParameter.value != nil, dtoParameter.replaceIn != nil { let error = AnalyticsError.invalidParameter("you can't set 'value' and 'replaceIn' for \(dtoParameter.name)") print(error.description) Analytics.exit(withError: error) } - + verify(value: dtoParameter.value, for: typeEnum) verify(value: dtoParameter.defaultValue, for: typeEnum) @@ -147,7 +150,7 @@ class AnalyticsFileParser { } if let parameters { - definition.parameters = AnalyticsFileParser.getParameters(from: parameters) + definition.parameters = Self.getParameters(from: parameters) } return definition @@ -215,7 +218,7 @@ class AnalyticsFileParser { if let category = event.category { definition.category = category } - + if let action = event.action { definition.action = action } diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift index 4ed4bbe..cf824d1 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift @@ -62,7 +62,8 @@ final class AnalyticsDefinitionTests: XCTestCase { func logScreenDefinitionName() { logScreen( name: "Ecran un", - path: "ecran_un/" + path: "ecran_un/", + params: nil ) } """ @@ -84,7 +85,7 @@ final class AnalyticsDefinitionTests: XCTestCase { name: "Ecran un", action: "", category: "", - params: [:] + params: nil ) } """ @@ -103,9 +104,10 @@ final class AnalyticsDefinitionTests: XCTestCase { // Expect let expectScreen = """ static func logScreenDefinitionName() { - logScreen( + AnalyticsManager.shared.logScreen( name: "Ecran un", - path: "ecran_un/" + path: "ecran_un/", + params: nil ) } """ @@ -123,11 +125,11 @@ final class AnalyticsDefinitionTests: XCTestCase { // Expect let expectEvent = """ static func logEventDefinitionName() { - logEvent( + AnalyticsManager.shared.logEvent( name: "Ecran un", action: "", category: "", - params: [:] + params: nil ) } """ diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift index 5b6e987..57f0016 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift @@ -1,6 +1,6 @@ // // AnalyticsGeneratorTests.swift -// +// // // Created by Thibaut Schmitt on 06/09/2022. // @@ -14,7 +14,7 @@ import ToolCore @testable import ResgenSwift final class AnalyticsGeneratorTests: XCTestCase { - + private func getAnalyticsDefinition( id: String, path: String = "", @@ -31,7 +31,183 @@ final class AnalyticsGeneratorTests: XCTestCase { definition.category = category return definition } - + + private func protocolString() -> String { + """ + // MARK: - Protocol + + protocol AnalyticsManagerProtocol { + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) + + func setEnable(_ enable: Bool) + } + """ + } + + private func firebaseString() -> String { + """ + // MARK: - Firebase + + class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { + var parameters = [ + AnalyticsParameterScreenName: name as NSObject + ] + + if path.isEmpty == false { + parameters["path"] = path + "/iOS" as NSObject + } + + if let supplementaryParameters = params { + for (newKey, newValue) in supplementaryParameters { + if parameters.contains(where: { (key: String, value: NSObject) in + key == newKey + }) { + continue + } + + parameters[newKey] = newValue as? NSObject + } + } + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) + } + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:NSObject] = [ + AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject + ] + + if category.isEmpty == false { + parameters["AnalyticsParameterItemCategory"] = category as NSObject + } + + if action.isEmpty == false { + parameters["action"] = action as NSObject + } + + if let supplementaryParameters = params { + for (newKey, newValue) in supplementaryParameters { + if parameters.contains(where: { (key: String, value: NSObject) in + key == newKey + }) { + continue + } + + parameters[newKey] = newValue as? NSObject + } + } + + Analytics.logEvent( + AnalyticsEventSelectContent, + parameters: parameters + ) + } + + func setEnable(_ enable: Bool) { + Analytics.setAnalyticsCollectionEnabled(enable) + } + } + """ + } + + private func matomoString() -> String { + """ + // MARK: - Matomo + + class MatomoAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Properties + + private var tracker: MatomoTracker + + // MARK: - Init + + init(siteId: String, url: String) { + debugPrint("[Matomo service] Server URL: \\(url)") + debugPrint("[Matomo service] Site ID: \\(siteId)") + tracker = MatomoTracker( + siteId: siteId, + baseURL: URL(string: url)! + ) + + #if DEBUG + tracker.dispatchInterval = 5 + #endif + + #if DEBUG + tracker.logger = DefaultLogger(minLevel: .verbose) + #endif + + debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") + debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") + } + + // MARK: - Methods + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { + guard let trackerUrl = tracker.contentBase?.absoluteString else { return } + + let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") + tracker.track( + view: [name], + url: urlString + ) + } + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + tracker.track( + eventWithCategory: category, + action: action, + name: name, + number: nil, + url: nil + ) + } + + func setEnable(_ enable: Bool) { + tracker.isOptedOut = !enable + } + } + """ + } + func testGeneratedExtensionContentFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -39,19 +215,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.firebase], @@ -65,64 +241,18 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import FirebaseAnalytics - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(firebaseString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + // MARK: - Traker Type - // MARK: - Firebase + enum TrackerType: CaseIterable { - class FirebaseAnalyticsManager: AnalyticsManagerProtocol { - - func logScreen(name: String, path: String) { - var parameters = [ - AnalyticsParameterScreenName: name as NSObject - ] - - Analytics.logEvent( - AnalyticsEventScreenView, - parameters: parameters - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - var parameters: [String:NSObject] = [ - "action": action as NSObject, - "category": category as NSObject, - ] - - if let supplementaryParameters = params { - for (newKey, newValue) in supplementaryParameters { - if parameters.contains(where: { (key: String, value: NSObject) in - key == newKey - }) { - continue - } - - parameters[newKey] = newValue as? NSObject - } - } - - Analytics.logEvent( - name.replacingOccurrences(of: [" "], with: "_"), - parameters: parameters - ) - } + case firebase } // MARK: - Manager @@ -131,27 +261,59 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure() { - managers.append(FirebaseAnalyticsManager()) + managers[TrackerType.firebase] = FirebaseAnalyticsManager() } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - managers.forEach { manager in - manager.logScreen(name: name, path: path) + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -163,7 +325,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -178,7 +340,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "" + path: "", + params: nil ) } @@ -187,7 +350,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "", category: "", - params: [:] + params: nil ) } @@ -196,19 +359,20 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "" + path: "", + params: nil ) } } - + """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } - + func testGeneratedExtensionContentMatomo() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -216,19 +380,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo], @@ -241,80 +405,18 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import MatomoTracker - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(matomoString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + // MARK: - Traker Type - // MARK: - Matomo + enum TrackerType: CaseIterable { - class MatomoAnalyticsManager: AnalyticsManagerProtocol { - - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \\(url)") - debugPrint("[Matomo service] Site ID: \\(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen(name: String, path: String) { - guard !tracker.isOptedOut else { return } - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - guard !tracker.isOptedOut else { return } - - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } + case matomo } // MARK: - Manager @@ -323,32 +425,62 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure(siteId: String, url: String) { - managers.append( - MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + managers[TrackerType.matomo] = MatomoAnalyticsManager( + siteId: siteId, + url: url ) } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - managers.forEach { manager in - manager.logScreen(name: name, path: path) + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -360,7 +492,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -375,7 +507,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "s1_def_one/" + path: "s1_def_one/", + params: nil ) } @@ -384,7 +517,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "test", category: "test", - params: [:] + params: nil ) } @@ -393,19 +526,20 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "s2_def_one/" + path: "s2_def_one/", + params: nil ) } } """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } - + func testGeneratedExtensionContentMatomoAndFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -413,19 +547,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo, TrackerType.firebase], @@ -439,125 +573,22 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import MatomoTracker import FirebaseAnalytics - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(matomoString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + \(firebaseString()) - // MARK: - Matomo + // MARK: - Traker Type - class MatomoAnalyticsManager: AnalyticsManagerProtocol { + enum TrackerType: CaseIterable { - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \\(url)") - debugPrint("[Matomo service] Site ID: \\(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen(name: String, path: String) { - guard !tracker.isOptedOut else { return } - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - guard !tracker.isOptedOut else { return } - - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } - } - - // MARK: - Firebase - - class FirebaseAnalyticsManager: AnalyticsManagerProtocol { - func logScreen(name: String, path: String) { - var parameters = [ - AnalyticsParameterScreenName: name as NSObject - ] - - Analytics.logEvent( - AnalyticsEventScreenView, - parameters: parameters - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - var parameters: [String:NSObject] = [ - "action": action as NSObject, - "category": category as NSObject, - ] - - if let supplementaryParameters = params { - for (newKey, newValue) in supplementaryParameters { - if parameters.contains(where: { (key: String, value: NSObject) in - key == newKey - }) { - continue - } - - parameters[newKey] = newValue as? NSObject - } - } - - Analytics.logEvent( - name.replacingOccurrences(of: [" "], with: "_"), - parameters: parameters - ) - } + case matomo + case firebase } // MARK: - Manager @@ -566,33 +597,63 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure(siteId: String, url: String) { - managers.append( - MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + managers[TrackerType.matomo] = MatomoAnalyticsManager( + siteId: siteId, + url: url ) - managers.append(FirebaseAnalyticsManager()) + managers[TrackerType.firebase] = FirebaseAnalyticsManager() } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - - managers.forEach { manager in - manager.logScreen(name: name, path: path) + + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -604,7 +665,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -619,7 +680,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "s1_def_one/" + path: "s1_def_one/", + params: nil ) } @@ -628,7 +690,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "test", category: "test", - params: [:] + params: nil ) } @@ -637,13 +699,14 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "s2_def_one/" + path: "s2_def_one/", + params: nil ) } } """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) }