Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Error cases is handled and proper ping referral code is added for the…
Browse files Browse the repository at this point in the history
…se cases
  • Loading branch information
soner-yuksel committed Jan 2, 2024
1 parent 1407472 commit b54736e
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 41 deletions.
34 changes: 22 additions & 12 deletions Sources/Growth/URP/AdAttributionReportData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,38 @@
import os.log
import Foundation

public enum SerializationError: Error {
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}

public enum SearchAdError: Error {
case invalidCampaignTokenData
case invalidGroupsReportData
case failedCampaignTokenFetch
case failedCampaignTokenLookup
case missingReportsKeywordParameter
case failedReportsKeywordLookup
case successfulCampaignFailedKeywordLookup(AdAttributionData)
}

public struct AdAttributionData {
// A value of true returns if a user clicks an Apple Search Ads impression up to 30 days before your app download.
// If the API can’t find a matching attribution record, the attribution value is false.
public let attribution: Bool
let attribution: Bool
// The identifier of the organization that owns the campaign.
// organizationId is the same as your account in the Apple Search Ads UI.
public let organizationId: Int?
let organizationId: Int?
// The type of conversion is either Download or Redownload.
public let conversionType: String?
let conversionType: String?
// The unique identifier for the campaign.
public let campaignId: Int
let campaignId: Int
// The country or region for the campaign.
public let countryOrRegion: String?
let countryOrRegion: String?
// The ad group if for the campaign which will be used for feature link
public let adGroupId: Int?
let adGroupId: Int?
// The keyword id for the campaign which will be used for feature link
public let keywordId: Int?
let keywordId: Int?

init(attribution: Bool,
organizationId: Int? = nil,
Expand Down Expand Up @@ -83,20 +93,20 @@ public struct AdAttributionData {
}

public struct AdGroupReportData {
public struct KeywordReportResponse: Codable {
struct KeywordReportResponse: Codable {
let row: [KeywordRow]
}

public struct KeywordRow: Codable {
struct KeywordRow: Codable {
let metadata: KeywordMetadata
}

public struct KeywordMetadata: Codable {
struct KeywordMetadata: Codable {
let keyword: String
let keywordId: Int
}

public let productKeyword: String
let productKeyword: String

init(data: Data, keywordId: Int) throws {
do {
Expand Down
39 changes: 22 additions & 17 deletions Sources/Growth/URP/AttributionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import Preferences
import Combine
import Shared

public class AttributionManager {
public enum FeatureLinkageType {
case undefined, vpn, playlist
}

public enum FeatureLinkageError: Error {
case executionTimeout
}
public enum FeatureLinkageType {
case undefined, vpn, playlist
}

public enum FeatureLinkageError: Error {
case executionTimeout(AdAttributionData)
}

public class AttributionManager {

private let dau: DAU
private let urp: UserReferralProgram
Expand Down Expand Up @@ -44,14 +45,13 @@ public class AttributionManager {
@MainActor public func handleSearchAdsInstallAttribution() async throws {
do {
let attributionData = try await urp.adCampaignLookup()
let refCode = generateReferralCode(attributionData: attributionData)
setupReferralCodeAndPingServer(refCode: refCode)
generateReferralCodeAndPingServer(with: attributionData)
} catch {
throw error
}
}

@MainActor public func handleAdsReportingFeatureLinkage() async throws -> String {
@MainActor public func handleAdsReportingFeatureLinkage() async throws -> (keyword: String, attributionData: AdAttributionData) {
// This function should run multiple tasks first adCampaignLookup
// and adReportsKeywordLookup depending on adCampaignLookup result.
// There is maximum threshold of 5 sec for all the tasks to be completed
Expand All @@ -61,13 +61,14 @@ public class AttributionManager {
let start = DispatchTime.now() // Start time for time tracking

do {
let attributionData = try await urp.adCampaignLookup()
// Ad campaign Lookup should be performed with no retry as first step
let attributionData = try await urp.adCampaignLookup(isRetryEnabled: false)

let elapsedTime = Double(DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
let remainingTime = 5.0 - elapsedTime

guard remainingTime > 0 else {
throw FeatureLinkageError.executionTimeout
throw FeatureLinkageError.executionTimeout(attributionData)
}

let task2Timeout = DispatchTime.now() + .seconds(Int(remainingTime))
Expand All @@ -78,16 +79,15 @@ public class AttributionManager {
let keyword = try await self.urp.adReportsKeywordLookup(attributionData: attributionData)
continuation.resume(returning: keyword)
} catch {
continuation.resume(throwing: error)
continuation.resume(throwing: SearchAdError.successfulCampaignFailedKeywordLookup(attributionData))
}
}

DispatchQueue.global().asyncAfter(deadline: task2Timeout) {
continuation.resume(throwing: FeatureLinkageError.executionTimeout)
continuation.resume(throwing: FeatureLinkageError.executionTimeout(attributionData))
}
}

return keywordResult
return (keywordResult, attributionData)
} catch {
throw error
}
Expand All @@ -104,6 +104,11 @@ public class AttributionManager {
dau.sendPingToServer()
}

public func generateReferralCodeAndPingServer(with attributionData: AdAttributionData) {
let refCode = generateReferralCode(attributionData: attributionData)
setupReferralCodeAndPingServer(refCode: refCode)
}

private func performProgramReferralLookup(refCode: String?, completion: @escaping (URL?) -> Void) {
urp.referralLookup(refCode: refCode) { referralCode, offerUrl in
Preferences.URP.referralLookupOutstanding.value = false
Expand Down
4 changes: 2 additions & 2 deletions Sources/Growth/URP/UrpService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct UrpService {
return adAttributionData
}

throw SerializationError.invalid("Invalid Data type from response", "")
throw SearchAdError.invalidCampaignTokenData
} catch {
throw error
}
Expand All @@ -123,7 +123,7 @@ struct UrpService {
return adGroupsReportData.productKeyword
}

throw SerializationError.invalid("Invalid Data type from response", "")
throw SearchAdError.invalidGroupsReportData
} catch {
throw error
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/Growth/URP/UserReferralProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,20 +122,20 @@ public class UserReferralProgram {

do {
return try await service.adCampaignTokenLookupQueue(adAttributionToken: adAttributionToken)

} catch {
Logger.module.info("Could not retrieve ad campaign attibution from ad services")
throw error
throw SearchAdError.invalidCampaignTokenData
}
} catch {
Logger.module.info("Couldnt fetch attribute tokens with error: \(error)")
throw error
throw SearchAdError.failedCampaignTokenFetch
}
}

@MainActor func adReportsKeywordLookup(attributionData: AdAttributionData) async throws -> String {
guard let adGroupId = attributionData.adGroupId, let keywordId = attributionData.keywordId else {
throw SerializationError.invalid("adGroupId or keywordId is nil", "")
Logger.module.info("Could not retrieve ad campaign attibution from ad services")
throw SearchAdError.missingReportsKeywordParameter
}

do {
Expand All @@ -146,7 +146,7 @@ public class UserReferralProgram {

} catch {
Logger.module.info("Could not retrieve ad groups reports using ad services")
throw error
throw SearchAdError.failedReportsKeywordLookup
}
}

Expand Down
34 changes: 29 additions & 5 deletions Sources/Onboarding/Welcome/WelcomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -389,17 +389,27 @@ public class WelcomeViewController: UIViewController {
Task { @MainActor in
do {
// Handle API calls and send linkage type
// let featureType = try await controller.attributionManager.handleAdsReportingFeatureLinkage()
let featureType = try await controller.attributionManager.handleAdsReportingFeatureLinkage()
// controller.attributionManager.adFeatureLinkage = featureType!

controller.calloutView.isLoading = false
self?.close()
} catch FeatureLinkageError.executionTimeout(let attributionData) {
// Time out occurred while executing ad reports lookup
// Ad Campaign Lookup is successful so dau server should be pinged
// attribution data referral code
await self?.pingServerWithGeneratedReferralCode(
using: attributionData, controller: controller)
} catch SearchAdError.successfulCampaignFailedKeywordLookup(let attributionData) {
// Error occurred while executing ad reports lookup
// Ad Campaign Lookup is successful so dau server should be pinged
// attribution data referral code
await self?.pingServerWithGeneratedReferralCode(
using: attributionData, controller: controller)
} catch {
// Sending default organic install code for dau
// Error occurred before getting successful
// attributuion data, generic code should be pinged
controller.attributionManager.setupReferralCodeAndPingServer()

controller.calloutView.isLoading = false
self?.close()
}
}
}
Expand All @@ -413,6 +423,20 @@ public class WelcomeViewController: UIViewController {
Preferences.Onboarding.p3aOnboardingShown.value = true
}
}

private func pingServerWithGeneratedReferralCode(using attributionData: AdAttributionData, controller: WelcomeViewController) async {
Task {
await withCheckedContinuation { continuation in
DispatchQueue.global().async {
controller.attributionManager.generateReferralCodeAndPingServer(with: attributionData)
continuation.resume()
}
}
}

controller.calloutView.isLoading = false
close()
}

private func onSetDefaultBrowser() {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
Expand Down

0 comments on commit b54736e

Please sign in to comment.