Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pp 649 i os integrate the gini endpoint for amplitude user tracking #758

Open
wants to merge 18 commits into
base: Release-3.13.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f418a23
feat(GiniCaptureSDK): remove AmplitudeBaseEvent, AmplitudeEventOption…
mrkulik Dec 12, 2024
42bf33d
feat(GiniCaptureSDK): remove JSONCodingKeys, KeyedEncodingContainer, …
mrkulik Dec 12, 2024
b228c25
feat(GiniBankAPILibrary): Add Utilities, AmplitudeBaseEvent, Amplitud…
mrkulik Dec 12, 2024
cb3a430
feat(GiniBankAPILibrary): AnalyticsService, AnalyticsServiceProtocol
mrkulik Dec 12, 2024
2e2b809
feat(GiniBankAPILibrary): Add analyticsEvent to APIMethod
mrkulik Dec 12, 2024
c0750e8
feat(GiniBankAPILibrary): Add analyticsEvent to path and headers in A…
mrkulik Dec 12, 2024
6033e7c
feat(GiniBankAPILibrary): init AnalyticsService in BankAPI
mrkulik Dec 12, 2024
5c9f672
feat(GiniCaptureSDK): integrate AnalyticsService in GiniAnalyticsMana…
mrkulik Dec 12, 2024
558ca95
feat(GiniBankSDK): inject AnalyticsService in networking coordinator
mrkulik Dec 12, 2024
63bf61d
feat(GiniCaptureSDK): remove apiKey from GiniAnalyticsConfiguration, …
mrkulik Dec 13, 2024
d04e960
feat(GiniBankSDK): remove apiKey from GiniBankNetworkingScreenApiCoor…
mrkulik Dec 13, 2024
dcd7db5
feat(GiniBankAPILibrary): remove apiKey from ClientConfiguration, Amp…
mrkulik Dec 13, 2024
e84367d
feat(GiniCaptureSDK): remove apiURL for AmplitudeService
mrkulik Dec 13, 2024
d634873
feat(GiniCaptureSDK): AmplitudeService -> GiniAnalyticsService
mrkulik Dec 13, 2024
13de7d5
feat(GiniCaptureSDK): AmplitudeService -> GiniAnalyticsService in header
mrkulik Dec 13, 2024
8939900
doc(GiniCaptureSDK): update documentation for GiniAnalyticsService
mrkulik Dec 13, 2024
b5a3674
feat(GiniCaptureSDK): update events queue name
mrkulik Dec 13, 2024
0d58a76
feat(GiniBankAPILibrary): refactor APIResource
mrkulik Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ enum APIMethod: ResourceMethod {
case payment(id: String)
case logErrorEvent
case configurations
case analyticsEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,35 @@ struct APIResource<T: Decodable>: Resource {
return "/events/error"
case .configurations:
return "/configurations"
case .analyticsEvent:
return "/events/batch"
}
}

var defaultHeaders: HTTPHeaders {
let acceptKey = "Accept"
let contentTypeKey = "Content-Type"

let jsonAcceptValue = ContentType.content(version: apiVersion, subtype: nil, mimeSubtype: "json").value
let jsonContentTypeValue = ContentType.content(version: apiVersion, subtype: nil, mimeSubtype: "json").value
let amplitudeEventsValue = "application/vnd.gini.v1.events.amplitude"

switch method {
case .createDocument(_, _, let mimeSubType, let documentType):
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value,
"Content-Type": ContentType.content(version: apiVersion,
subtype: documentType?.name,
mimeSubtype: mimeSubType).value
]
let dynamicContentType = ContentType.content(version: apiVersion, subtype: documentType?.name, mimeSubtype: mimeSubType).value
return [acceptKey: jsonAcceptValue, contentTypeKey: dynamicContentType]

case .page, .pagePreview, .documentPage:
return [:]

case .paymentRequests:
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value]
return [acceptKey: jsonAcceptValue]

case .analyticsEvent:
return [acceptKey: ContentType.json.value, contentTypeKey: amplitudeEventsValue]

default:
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value,
"Content-Type": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value
]
return [acceptKey: jsonAcceptValue, contentTypeKey: jsonContentTypeValue]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import Foundation

/// The `AmplitudeBaseEvent` struct represents an event with various properties and implements encoding for serialization.
struct AmplitudeBaseEvent: Encodable, Equatable {
public struct AmplitudeBaseEvent: Encodable, Equatable {
var eventType: String
var eventProperties: [String: Any]?
var userProperties: [String: Any]?
var eventOptions: AmplitudeEventOptions

enum CodingKeys: String, CodingKey {
public enum CodingKeys: String, CodingKey {
case eventType = "event_type"
case eventProperties = "event_properties"
case userProperties = "user_properties"
Expand All @@ -36,7 +36,7 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
}

/// Initializes a new instance of the `AmplitudeBaseEvent` struct.
init(eventType: String,
public init(eventType: String,
eventProperties: [String: Any]? = nil,
userProperties: [String: Any]? = nil,
eventOptions: AmplitudeEventOptions) {
Expand All @@ -50,7 +50,7 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
///
/// - Parameter encoder: The encoder to write data to.
/// - Throws: An error if any values are invalid for the given encoder's format.
func encode(to encoder: Encoder) throws {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(eventType, forKey: .eventType)
try container.encodeIfPresent(eventProperties, forKey: .eventProperties)
Expand All @@ -69,7 +69,7 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
try container.encodeIfPresent(eventOptions.ip, forKey: .ip)
}

static func == (lhs: AmplitudeBaseEvent, rhs: AmplitudeBaseEvent) -> Bool {
public static func == (lhs: AmplitudeBaseEvent, rhs: AmplitudeBaseEvent) -> Bool {
return lhs.eventType == rhs.eventType && lhs.eventOptions.eventId == rhs.eventOptions.eventId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// The `AmplitudeEventOptions` struct holds common properties for events.
struct AmplitudeEventOptions {
public struct AmplitudeEventOptions {
var userId: String?
var time: Int64?
var sessionId: Int64?
Expand All @@ -22,7 +22,7 @@ struct AmplitudeEventOptions {
var deviceBrand: String?

/// Initializes a new instance of the `AmplitudeEventOptions` struct.
init(userId: String? = nil,
public init(userId: String? = nil,
deviceId: String? = nil,
time: Int64? = nil,
sessionId: Int64? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@

import Foundation
/**
A struct representing the payload for batching events to be sent to the Amplitude server.
A struct representing the payload for batching events to be sent to the server.

This struct conforms to the `Encodable` protocol to facilitate easy encoding
to JSON format. It includes the API key and an array of events to be uploaded.

- Parameters:
- apiKey: The API key for the Amplitude analytics platform.
- events: An array of `AmplitudeBaseEvent` objects to be included in the batch upload.
*/
struct AmplitudeEventsBatchPayload: Encodable {
let apiKey: String
public struct AmplitudeEventsBatchPayload: Encodable {
let events: [AmplitudeBaseEvent]

/**
Customizes the coding keys for the `AmplitudeEventsBatchPayload` struct to match the expected JSON format.

- apiKey: Encoded as "api_key" in the JSON payload.
- events: Encoded as "events" in the JSON payload.
*/
enum CodingKeys: String, CodingKey {
case apiKey = "api_key"
case events
}

public init(events: [AmplitudeBaseEvent]) {
self.events = events
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// AnalyticsService.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation


public final class AnalyticsService: AnalyticsServiceProtocol {
public func sendEventsPayload(payload: AmplitudeEventsBatchPayload, completion: @escaping CompletionResult<String>) {
self.sendEventsPayload(payload: payload, resourceHandler: sessionManager.data, completion: completion)
}

/// The session manager responsible for handling network requests.
let sessionManager: SessionManagerProtocol

/// The API domain to be used for fetching configurations.
public var apiDomain: APIDomain

/**
Initializes a new instance of `AnalyticsService`.

- Parameters:
- sessionManager: An object conforming to `SessionManagerProtocol` responsible for managing network sessions.
- apiDomain: The domain of the API to fetch configurations from. Defaults to `.default`.
*/
init(sessionManager: SessionManagerProtocol, apiDomain: APIDomain = .default) {
self.sessionManager = sessionManager
self.apiDomain = apiDomain
}
}

extension AnalyticsService {
func sendEventsPayload(payload: AmplitudeEventsBatchPayload ,resourceHandler: ResourceDataHandler<APIResource<String>>,
completion: @escaping CompletionResult<String>) {
let resource = APIResource<String>.init(method: .analyticsEvent,
apiDomain: apiDomain,
httpMethod: .post,
additionalHeaders: [:],
body: try? JSONEncoder().encode(payload))
sessionManager.data(resource: resource, completion: completion)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// AnalyticsServiceProtocol.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation


public protocol AnalyticsServiceProtocol: AnyObject {
func sendEventsPayload(payload: AmplitudeEventsBatchPayload, completion: @escaping CompletionResult<String>)
}

extension AnalyticsServiceProtocol {
func sendEventsPayload(payload: AmplitudeEventsBatchPayload,
resourceHandler: ResourceDataHandler<APIResource<String>>,
completion: @escaping CompletionResult<String>) {
// Default implementation is empty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,24 @@ public struct ClientConfiguration: Codable {

- parameter clientID: A unique identifier for the client.
- parameter userJourneyAnalyticsEnabled: A flag indicating whether user journey analytics is enabled.
- parameter amplitudeApiKey: An optional API key for Amplitude integration. Defaults to `nil`.
- parameter skontoEnabled: A flag indicating whether Skonto is enabled.
- parameter returnAssistantEnabled: A flag indicating whether the return assistant feature is enabled.
- parameter transactionDocsEnabled: A flag indicating whether TransactionDocs feature is enabled.
*/
public init(clientID: String,
userJourneyAnalyticsEnabled: Bool,
amplitudeApiKey: String? = nil,
skontoEnabled: Bool,
returnAssistantEnabled: Bool,
transactionDocsEnabled: Bool) {
self.clientID = clientID
self.userJourneyAnalyticsEnabled = userJourneyAnalyticsEnabled
self.amplitudeApiKey = amplitudeApiKey
self.skontoEnabled = skontoEnabled
self.returnAssistantEnabled = returnAssistantEnabled
self.transactionDocsEnabled = transactionDocsEnabled
}

public let clientID: String
public let userJourneyAnalyticsEnabled: Bool
public let amplitudeApiKey: String?
public let skontoEnabled: Bool
public let returnAssistantEnabled: Bool
public let transactionDocsEnabled: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ public final class GiniBankAPI {
private let docService: DocumentService!
private let payService: PaymentService?
private let configService: ClientConfigurationServiceProtocol?
private let analyticsService: AnalyticsServiceProtocol?

static var logLevel: LogLevel = .none

init<T: DocumentService>(documentService: T,
paymentService: PaymentService?,
configurationService: ClientConfigurationServiceProtocol?) {
configurationService: ClientConfigurationServiceProtocol?,
analyticsService: AnalyticsServiceProtocol?) {
self.docService = documentService
self.payService = paymentService
self.configService = configurationService
self.analyticsService = analyticsService
}

/**
Expand All @@ -47,6 +51,10 @@ public final class GiniBankAPI {
public func configurationService() -> ClientConfigurationServiceProtocol? {
return configService
}

public func analyticService() -> AnalyticsServiceProtocol? {
return analyticsService
}

/// Removes the user stored credentials. Recommended when logging a different user in your app.
public func removeStoredCredentials() throws {
Expand Down Expand Up @@ -114,10 +122,12 @@ extension GiniBankAPI {
let documentService = DefaultDocumentService(sessionManager: sessionManager, apiDomain: api)
let paymentService = PaymentService(sessionManager: sessionManager, apiDomain: api)
let configurationService = ClientConfigurationService(sessionManager: sessionManager, apiDomain: api)

return GiniBankAPI(documentService: documentService,
let analyticsService = AnalyticsService(sessionManager: sessionManager, apiDomain: api)

return GiniBankAPI(documentService: documentService,
paymentService: paymentService,
configurationService: configurationService)
configurationService: configurationService,
analyticsService: analyticsService)
}

private func createSessionManager() -> SessionManager {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
weak var resultsDelegate: GiniCaptureResultsDelegate?
let documentService: DocumentServiceProtocol
private var configurationService: ClientConfigurationServiceProtocol?
private var analyticsService: AnalyticsServiceProtocol?
var giniBankConfiguration = GiniBankConfiguration.shared

/// Internal coordinator for managing transaction documents, conforming to `TransactionDocsDataInternalProtocol`.
Expand All @@ -140,6 +141,7 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
lib: GiniBankAPI) {
documentService = DocumentService(lib: lib, metadata: documentMetadata)
configurationService = lib.configurationService()
analyticsService = lib.analyticService()
let captureConfiguration = configuration.captureConfiguration()
super.init(withDelegate: nil, giniConfiguration: captureConfiguration)

Expand All @@ -158,6 +160,7 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
lib: GiniBankAPI) {
documentService = DocumentService(lib: lib, metadata: documentMetadata)
configurationService = lib.configurationService()
analyticsService = lib.analyticService()
let captureConfiguration = configuration.captureConfiguration()
super.init(withDelegate: nil, giniConfiguration: captureConfiguration)

Expand Down Expand Up @@ -312,13 +315,12 @@ private extension GiniBankNetworkingScreenApiCoordinator {
private func initializeAnalytics(with configuration: ClientConfiguration) {
let analyticsEnabled = configuration.userJourneyAnalyticsEnabled
let analyticsConfiguration = GiniAnalyticsConfiguration(clientID: configuration.clientID,
userJourneyAnalyticsEnabled: analyticsEnabled,
amplitudeApiKey: configuration.amplitudeApiKey)
userJourneyAnalyticsEnabled: analyticsEnabled)

GiniAnalyticsManager.trackUserProperties([.returnAssistantEnabled: configuration.returnAssistantEnabled,
.returnReasonsEnabled: giniBankConfiguration.enableReturnReasons,
.bankSDKVersion: GiniBankSDKVersion])
GiniAnalyticsManager.initializeAnalytics(with: analyticsConfiguration)
GiniAnalyticsManager.initializeAnalytics(with: analyticsConfiguration, analyticsAPIService: analyticsService)
}

private func sendAnalyticsEventSDKClose() {
Expand Down
Loading