diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index b98773df96..04cc63a784 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -27,7 +27,8 @@ import NetworkProtection extension Pixel { public enum Event { - + + case appInstall case appLaunch case refreshPressed case pullToRefresh @@ -908,6 +909,7 @@ extension Pixel.Event { public var name: String { switch self { + case .appInstall: return "m_install" case .appLaunch: return "ml" case .refreshPressed: return "m_r" case .pullToRefresh: return "m_pull-to-reload" diff --git a/Core/StatisticsLoader.swift b/Core/StatisticsLoader.swift index a8001e6077..7d60430add 100644 --- a/Core/StatisticsLoader.swift +++ b/Core/StatisticsLoader.swift @@ -35,15 +35,18 @@ public class StatisticsLoader { private let parser = AtbParser() private let atbPresenceFileMarker = BoolFileMarker(name: .isATBPresent) private let inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring + private let pixelFiring: PixelFiring.Type init(statisticsStore: StatisticsStore = StatisticsUserDefaults(), returnUserMeasurement: ReturnUserMeasurement = KeychainReturnUserMeasurement(), usageSegmentation: UsageSegmenting = UsageSegmentation(), - inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor()) { + inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor(), + pixelFiring: PixelFiring.Type = Pixel.self) { self.statisticsStore = statisticsStore self.returnUserMeasurement = returnUserMeasurement self.usageSegmentation = usageSegmentation self.inconsistencyMonitoring = inconsistencyMonitoring + self.pixelFiring = pixelFiring } public func load(completion: @escaping Completion = {}) { @@ -94,6 +97,7 @@ public class StatisticsLoader { completion() return } + self.fireInstallPixel() self.statisticsStore.installDate = Date() self.statisticsStore.atb = atb.version self.returnUserMeasurement.installCompletedWithATB(atb) @@ -102,6 +106,20 @@ public class StatisticsLoader { } } + private func fireInstallPixel() { + let formattedLocale = Locale.current.localeIdentifierAsJsonFormat + let isReinstall = String(statisticsStore.variant == VariantIOS.returningUser.name) + let parameters = [ + "locale": formattedLocale, + "reinstall": isReinstall + ] + pixelFiring.fire(.appInstall, withAdditionalParameters: parameters, includedParameters: [.appVersion], onComplete: { error in + if let error { + Logger.general.error("Install pixel failed with error: \(error.localizedDescription, privacy: .public)") + } + }) + } + private func createATBFileMarker() { atbPresenceFileMarker?.mark() } diff --git a/DuckDuckGoTests/StatisticsLoaderTests.swift b/DuckDuckGoTests/StatisticsLoaderTests.swift index f066097e99..8fa235203c 100644 --- a/DuckDuckGoTests/StatisticsLoaderTests.swift +++ b/DuckDuckGoTests/StatisticsLoaderTests.swift @@ -27,20 +27,26 @@ class StatisticsLoaderTests: XCTestCase { var mockStatisticsStore: StatisticsStore! var mockUsageSegmentation: MockUsageSegmentation! + var mockPixelFiring: PixelFiringMock.Type! var testee: StatisticsLoader! override func setUpWithError() throws { try super.setUpWithError() + PixelFiringMock.tearDown() + + mockPixelFiring = PixelFiringMock.self mockStatisticsStore = MockStatisticsStore() mockUsageSegmentation = MockUsageSegmentation() testee = StatisticsLoader(statisticsStore: mockStatisticsStore, usageSegmentation: mockUsageSegmentation, - inconsistencyMonitoring: MockStatisticsStoreInconsistencyMonitoring()) + inconsistencyMonitoring: MockStatisticsStoreInconsistencyMonitoring(), + pixelFiring: mockPixelFiring) } override func tearDown() { HTTPStubs.removeAllStubs() + PixelFiringMock.tearDown() super.tearDown() } @@ -270,6 +276,19 @@ class StatisticsLoaderTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } + func testWhenInstallStatisticsRequestedThenInstallPixelIsFired() { + loadSuccessfulExiStub() + + let testExpectation = expectation(description: "refresh complete") + testee.refreshAppRetentionAtb { + Thread.sleep(forTimeInterval: .seconds(0.1)) + testExpectation.fulfill() + } + + wait(for: [testExpectation], timeout: 5.0) + XCTAssertEqual(mockPixelFiring.lastPixelName, Pixel.Event.appInstall.name) + } + func loadSuccessfulAtbStub() { stub(condition: isHost(URL.atb.host!)) { _ in let path = OHPathForFile("MockFiles/atb.json", type(of: self))!