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 352 enables user journey analysis based on backend api #526

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
965a291
feat(GiniBankAPILibrary): Configuration Decodable model. Configuratio…
mrkulik Jun 3, 2024
1897eb4
feat(GiniBankAPILibrary): Add ConfigurationService to GiniBankAPI
mrkulik Jun 5, 2024
98a9779
feat(GiniBankAPILibrary): update Configuration model
mrkulik Jun 6, 2024
5bf337b
refactoring(GiniBankAPILibrary): remove gym domain
mrkulik Jun 6, 2024
c55510f
refactoring(GiniCaptureSDK): remove gym domain
mrkulik Jun 6, 2024
c2228a9
refactoring(GiniBankSDK): remove gym domain
mrkulik Jun 6, 2024
1ed4f25
Merge branch 'PP-349-Implement-user-journey-analysis-pipeline-in-iOS'…
mrkulik Jun 10, 2024
3307f3a
refactor(GiniBankAPILibrary): split Configuration protocol & service …
mrkulik Jun 10, 2024
9d25699
refactor(GiniBankAPILibrary): rename fetchConfiguration-> configurations
mrkulik Jun 10, 2024
8e56dff
refactor(GiniBankSDK): remove documentService static func init
mrkulik Jun 10, 2024
b167f82
feat(GiniBankSDK): fetchConfigurations request integration
mrkulik Jun 10, 2024
37dad40
feat(GiniCaptureSDK): add configuration service to GiniNetworkingScre…
mrkulik Jun 10, 2024
942ec28
refactor(GiniCaptureSDK): rename copyright
mrkulik Jun 10, 2024
5474f28
refactor(GiniCaptureSDK): GiniNetworkingScreenAPICoordinator refactoring
mrkulik Jun 10, 2024
60a777f
feat(GiniCaptureSDK): mark configuration service as optional for cust…
mrkulik Jun 10, 2024
c3beabe
refactor(GiniBankAPILibrary): remove setter from apiDomain in Configu…
mrkulik Jun 10, 2024
62ac217
feat(GiniBankSDK): inject configuration service to custom networking
mrkulik Jun 10, 2024
8e9a9b0
feat(GiniCaptureSDK): remove configuration service inject from custom…
mrkulik Jun 10, 2024
28b4921
feat(GiniBankAPILibrary): add amplitude key to Configuration
mrkulik Jun 11, 2024
26c4c8c
feat(GiniBankAPILibrary): remove extension from ConfigurationServiceP…
mrkulik Jun 11, 2024
88e2d13
feat(GiniCaptureSDK): add AnalyticsConfiguration and update Analytics…
mrkulik Jun 11, 2024
c9379e3
feat(GiniCaptureSDK): remove AnalyticsManager init
mrkulik Jun 11, 2024
75fb30d
feat(GiniBankSDK): add startSDK method to networking coordinator. Mov…
mrkulik Jun 11, 2024
2ca864f
feat(GiniBankSDKExample): add ConfigurationService dependency to cust…
mrkulik Jun 11, 2024
7353db3
refactoring(GiniBankSDK): refactor initializeAnalytics
mrkulik Jun 11, 2024
7d37658
refactoring(GiniBankSDK): refactor copyrights
mrkulik Jun 11, 2024
c60a552
refactoring(GiniBankSDK): make configuration service private in netwo…
mrkulik Jun 12, 2024
44aff78
feat(GiniCaptureSDK): integrate configuration tokens to analytics man…
mrkulik Jun 12, 2024
6fe4ac9
feat(GiniBankSDK): remove clientID super property track from coordinator
mrkulik Jun 12, 2024
8fa23f0
refactoring(GiniCaptureSDK): refactor super properties tracking
mrkulik Jun 12, 2024
50cc32b
refactoring(GiniBankAPILibrary): refactor GiniBankAPI init, mark conf…
mrkulik Jun 12, 2024
4a8d71d
feat(GiniCaptureSDK): remove AnalyticsManager amplitudeKey & mixPanel…
mrkulik Jun 12, 2024
ccaa83a
feat(GiniBankSDK): remove TODO and leave comment for error handling
mrkulik Jun 12, 2024
c0c1bbd
feat(GiniBankAPILibrary): add documentation for Configuration
mrkulik Jun 12, 2024
5f96738
feat(GiniBankAPILibrary): add documentation for ConfigurationService …
mrkulik Jun 12, 2024
c7800d5
refactoring(GiniBankAPILibrary): rename copyrights filename for Confi…
mrkulik Jun 12, 2024
f57072d
feat(GiniCaptureSDK): add documentation for AnalyticsConfiguration
mrkulik Jun 12, 2024
fdd7b01
Merge branch 'PP-349-Implement-user-journey-analysis-pipeline-in-iOS'…
mrkulik Jun 12, 2024
ff5c58c
feat(GiniCaptureSDK): add trackingAuthorized check before analytics init
mrkulik Jun 12, 2024
e90a57d
Merge branch 'PP-349-Implement-user-journey-analysis-pipeline-in-iOS'…
mrkulik Jun 13, 2024
54bed5a
refactoring(GiniBankAPILibrary): improve documentation for Configuration
mrkulik Jun 13, 2024
496cd40
refactoring(GiniBankAPILibrary): improve documentation for Configurat…
mrkulik Jun 13, 2024
ed082b3
refactoring(GiniBankAPILibrary): refactor GiniBankAPI init to multiline
mrkulik Jun 13, 2024
5c880ad
fix(GiniBankSDK): return correct method for screenCoordinator for cus…
mrkulik Jun 13, 2024
27b5f32
refactoring(GiniBankSDK): GiniBankNetworkingScreenApiCoordinator: ref…
mrkulik Jun 13, 2024
676cb6d
feat(GiniBankSDK): GiniBankNetworkingScreenApiCoordinator: add docume…
mrkulik Jun 13, 2024
f08d07b
refactoring(GiniCaptureSDK): remove firstSDKOpen flag from AnalyticsM…
mrkulik Jun 13, 2024
2921ace
refactoring(GiniBankSDK): GiniBank+Networking: remove firstSDKOpen flag
mrkulik Jun 13, 2024
6e16dcc
refactoring(GiniBankAPILibrary): update documentation for Configuration
mrkulik Jun 14, 2024
09aef63
refactor(GiniBankAPILibrary): rename Configuration - > ClientConfigur…
mrkulik Jun 14, 2024
23c4bf9
refactor(GiniBankSDK): rename Configuration - > ClientConfiguration
mrkulik Jun 14, 2024
c43c580
refactor(GiniBankAPILibrary): rename ConfigurationService - > ClientC…
mrkulik Jun 14, 2024
df7de36
refactor(GiniBankAPILibrary): rename ConfigurationServiceProtocol - >…
mrkulik Jun 14, 2024
2b7b9b5
refactor(GiniBankSDK): rename ConfigurationServiceProtocol - > Client…
mrkulik Jun 14, 2024
40e70a8
refactor(GiniCaptureSDK): rename ConfigurationServiceProtocol - > Cli…
mrkulik Jun 14, 2024
b9d2684
refactor(GiniBankAPILibrary): rename Configuration - > ClientConfigur…
mrkulik Jun 14, 2024
3722403
refactor(GiniBankAPILibrary): rename in docs for ClientConfigurationS…
mrkulik Jun 14, 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 @@ -32,4 +32,5 @@ enum APIMethod: ResourceMethod {
case resolvePaymentRequest(id: String)
case payment(id: String)
case logErrorEvent
case configurations
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ import Foundation
public enum APIDomain {
/// The default one, which points to https://pay-api.gini.net
case `default`
/// The GYM API, which points to https://gym.gini.net/
case gym(tokenSource: AlternativeTokenSource)
/// A custom domain with optional path and custom token source
case custom(domain: String, path: String? = nil, tokenSource: AlternativeTokenSource?)

var domainString: String {
switch self {
case .default: return "pay-api.gini.net"
case .gym: return "gym.gini.net"
case .custom(let domain, _, _): return domain
}
}
Expand Down Expand Up @@ -51,7 +48,7 @@ struct APIResource<T: Decodable>: Resource {

var apiVersion: Int {
switch domain {
case .default, .gym, .custom: return 2
case .default, .custom: return 2
}
}

Expand Down Expand Up @@ -122,6 +119,8 @@ struct APIResource<T: Decodable>: Resource {
return "/paymentRequests/\(id)/payment"
case .logErrorEvent:
return "/events/error"
case .configurations:
return "/configurations"
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// ClientConfiguration.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation

/**
A struct representing configuration settings.

This struct holds various configuration options that can be used to customize the behavior and features.
*/
public struct ClientConfiguration: Decodable {
/**
Creates a new `ClientConfiguration` instance.

- parameter clientID: A unique identifier for the client.
- parameter userJourneyAnalyticsEnabled: A flag indicating whether user journey analytics is enabled.
- parameter mixpanelToken: An optional token for Mixpanel integration. Defaults to `nil`.
- 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.
*/
public init(clientID: String,
userJourneyAnalyticsEnabled: Bool,
mixpanelToken: String? = nil,
amplitudeApiKey: String? = nil,
skontoEnabled: Bool,
returnAssistantEnabled: Bool) {
self.clientID = clientID
self.userJourneyAnalyticsEnabled = userJourneyAnalyticsEnabled
self.mixpanelToken = mixpanelToken
self.amplitudeApiKey = amplitudeApiKey
self.skontoEnabled = skontoEnabled
self.returnAssistantEnabled = returnAssistantEnabled
}

public let clientID: String
public let userJourneyAnalyticsEnabled: Bool
public let mixpanelToken: String?
public let amplitudeApiKey: String?
public let skontoEnabled: Bool
public let returnAssistantEnabled: Bool
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// ClientConfigurationService.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation

/**
A service responsible for fetching configuration settings.

This service implements the `ClientConfigurationServiceProtocol` and provides methods to retrieve configuration data from a specified API domain.
*/
public final class ClientConfigurationService: ClientConfigurationServiceProtocol {
/**
Fetches configuration settings.

- Parameter completion: A closure to be called upon completion of the fetch operation. The closure takes a `CompletionResult<Configuration>` as its parameter.

This method initiates the process of fetching configuration settings by utilizing the provided session manager to handle the network request.
*/
public func fetchConfigurations(completion: @escaping CompletionResult<ClientConfiguration>) {
self.fetchConfigurations(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 `ClientConfigurationService`.

- 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 ClientConfigurationService {
/**
A helper method to fetch configurations using a resource handler.

- Parameters:
- resourceHandler: A handler responsible for processing the API resource data.
- completion: A closure to be called upon completion of the fetch operation. The closure takes a `CompletionResult<Configuration>` as its parameter.

This method constructs an `APIResource` object with the required parameters and utilizes the resource handler to perform the network request. The result is then passed to the completion handler.
*/
func fetchConfigurations(resourceHandler: ResourceDataHandler<APIResource<ClientConfiguration>>,
completion: @escaping CompletionResult<ClientConfiguration>) {
let resource = APIResource<ClientConfiguration>(method: .configurations, apiDomain: apiDomain, httpMethod: .get)

resourceHandler(resource, { result in
switch result {
case let .success(configuration):
completion(.success(configuration))
case let .failure(error):
completion(.failure(error))
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// ClientConfigurationServiceProtocol.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation

/**
Protocol for client configuration service
*/
public protocol ClientConfigurationServiceProtocol: AnyObject {
/**
Fetches configurations from the server.

- parameter completion: A closure that handles the result of the configuration fetch operation.
*/
func fetchConfigurations(completion: @escaping CompletionResult<ClientConfiguration>)
}

extension ClientConfigurationServiceProtocol {
/**
Fetches configurations using the provided resource handler.

- parameter resourceHandler: The handler that processes the API resource data.
- parameter completion: A closure that handles the result of the configuration fetch operation.
*/
func fetchConfigurations(resourceHandler: ResourceDataHandler<APIResource<ClientConfiguration>>,
completion: @escaping CompletionResult<ClientConfiguration>) {
// Default implementation is empty
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ public final class GiniBankAPI {

private let docService: DocumentService!
private let payService: PaymentService?
private let configService: ClientConfigurationServiceProtocol?
static var logLevel: LogLevel = .none

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

/**
Expand All @@ -41,6 +44,10 @@ public final class GiniBankAPI {
return payService ?? PaymentService(sessionManager: SessionManager(userDomain: .default), apiDomain: .default)
}

public func configurationService() -> ClientConfigurationServiceProtocol? {
return configService
}

/// Removes the user stored credentials. Recommended when logging a different user in your app.
public func removeStoredCredentials() throws {
let keychainStore: KeyStore = KeychainStore()
Expand Down Expand Up @@ -103,25 +110,29 @@ extension GiniBankAPI {
GiniBankAPI.logLevel = logLevel

// Initialize GiniBankAPI
let sessionManager = createSessionManager()
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,
paymentService: paymentService,
configurationService: configurationService)
}

private func createSessionManager() -> SessionManager {
switch api {
case .default:
let sessionManager = SessionManager(userDomain: userApi, sessionDelegate: self.sessionDelegate)
return GiniBankAPI(documentService: DefaultDocumentService(sessionManager: sessionManager), paymentService: PaymentService(sessionManager: sessionManager, apiDomain: .default))
return SessionManager(userDomain: userApi, sessionDelegate: self.sessionDelegate)
case .custom(_, _, let tokenSource):
var sessionManager : SessionManager
if let tokenSource = tokenSource {
sessionManager = SessionManager(alternativeTokenSource: tokenSource, sessionDelegate: self.sessionDelegate)
return SessionManager(alternativeTokenSource: tokenSource, sessionDelegate: self.sessionDelegate)
} else {
sessionManager = SessionManager(userDomain: userApi, sessionDelegate: self.sessionDelegate)
return SessionManager(userDomain: userApi, sessionDelegate: self.sessionDelegate)
}
return GiniBankAPI(documentService: DefaultDocumentService(sessionManager: sessionManager, apiDomain: api), paymentService: PaymentService(sessionManager: sessionManager, apiDomain: api))
case let .gym(tokenSource):
let sessionManager = SessionManager(alternativeTokenSource: tokenSource, sessionDelegate: self.sessionDelegate)
return GiniBankAPI(documentService: DefaultDocumentService(sessionManager:
sessionManager), paymentService: PaymentService(sessionManager: sessionManager))
}
}

private func save(_ client: Client) {
do {
try KeychainStore().save(item: KeychainManagerItem(key: .clientId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//
// GiniPayBankNetworkingScreenApiCoordinator.swift
// GiniBank
// GiniBank
//
// Created by Nadya Karaban on 03.03.21.
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import Foundation
Expand Down Expand Up @@ -112,6 +112,7 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin

weak var resultsDelegate: GiniCaptureResultsDelegate?
let documentService: DocumentServiceProtocol
private var configurationService: ClientConfigurationServiceProtocol?
var giniBankConfiguration = GiniBankConfiguration.shared

public init(client: Client,
Expand All @@ -121,18 +122,16 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
api: APIDomain,
trackingDelegate: GiniCaptureTrackingDelegate?,
lib: GiniBankAPI) {
documentService = GiniBankNetworkingScreenApiCoordinator.documentService(with: lib,
documentMetadata: documentMetadata,
configuration: configuration,
for: api)
documentService = DocumentService(lib: lib, metadata: documentMetadata)
configurationService = lib.configurationService()
let captureConfiguration = configuration.captureConfiguration()
super.init(withDelegate: nil, giniConfiguration: captureConfiguration)

visionDelegate = self
GiniBank.setConfiguration(configuration)
giniBankConfiguration = configuration
giniBankConfiguration.documentService = documentService
self.trackAnalyticsProperties(configuration: configuration, client: client)
self.trackAnalyticsProperties(configuration: configuration)
self.resultsDelegate = resultsDelegate
self.trackingDelegate = trackingDelegate
}
Expand All @@ -141,10 +140,12 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
configuration: GiniBankConfiguration,
documentMetadata: Document.Metadata?,
trackingDelegate: GiniCaptureTrackingDelegate?,
captureNetworkService: GiniCaptureNetworkService) {
captureNetworkService: GiniCaptureNetworkService,
configurationService: ClientConfigurationServiceProtocol?) {

documentService = DocumentService(giniCaptureNetworkService: captureNetworkService,
metadata: documentMetadata)
self.configurationService = configurationService
let captureConfiguration = configuration.captureConfiguration()

super.init(withDelegate: nil,
Expand Down Expand Up @@ -178,16 +179,6 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
lib: lib)
}

private static func documentService(with lib: GiniBankAPI,
documentMetadata: Document.Metadata?,
configuration: GiniBankConfiguration,
for api: APIDomain) -> DocumentServiceProtocol {
switch api {
case .default, .gym, .custom:
return DocumentService(lib: lib, metadata: documentMetadata)
}
}

private func deliver(result: ExtractionResult, analysisDelegate: AnalysisDelegate) {
let hasExtractions = result.extractions.count > 0

Expand Down Expand Up @@ -220,6 +211,34 @@ open class GiniBankNetworkingScreenApiCoordinator: GiniScreenAPICoordinator, Gin
public func didPressEnterManually() {
self.resultsDelegate?.giniCaptureDidEnterManually()
}

/**
This method first attempts to fetch configuration settings using the `configurationService`.
If the configurations are successfully fetched, it initializes the analytics with the fetched configuration
on the main thread. Regardless of the result of fetching configurations, it then proceeds to start the
SDK with the provided documents.
*/
public func startSDK(withDocuments documents: [GiniCaptureDocument]?, animated: Bool = false) -> UIViewController {
configurationService?.fetchConfigurations(completion: { result in
switch result {
case .success(let configuration):
DispatchQueue.main.async {
self.initializeAnalytics(with: configuration)
}
case .failure(_):
break
}
})
return self.start(withDocuments: documents, animated: animated)
}

private func initializeAnalytics(with configuration: ClientConfiguration) {
let analyticsConfiguration = AnalyticsConfiguration(clientID: configuration.clientID,
userJourneyAnalyticsEnabled: configuration.userJourneyAnalyticsEnabled,
mixpanelToken: configuration.mixpanelToken,
amplitudeApiKey: configuration.amplitudeApiKey)
AnalyticsManager.initializeAnalytics(with: analyticsConfiguration)
}
}

extension GiniBankNetworkingScreenApiCoordinator {
Expand Down Expand Up @@ -330,13 +349,9 @@ extension GiniBankNetworkingScreenApiCoordinator {
})
}

private func trackAnalyticsProperties(configuration: GiniBankConfiguration, client: Client? = nil) {
private func trackAnalyticsProperties(configuration: GiniBankConfiguration) {
AnalyticsManager.trackUserProperties([.returnAssistantEnabled: configuration.returnAssistantEnabled,
.returnReasonsEnabled: configuration.enableReturnReasons])
// TODO: No clientID user property for custom networking init
if let client {
AnalyticsManager.registerSuperProperties([.giniClientID: client.id])
}
}
}

Expand Down
Loading
Loading