Skip to content

Commit

Permalink
feat(GiniCaptureSDK): Add error screen events
Browse files Browse the repository at this point in the history
- events added: `screen_shown`, `close_tapped`, `enter_manually_tapped`, `retake_images_tapped`
- fix tests

PP-392
  • Loading branch information
ValentinaIancu-Gini committed Apr 17, 2024
1 parent ae3e1e3 commit 0bae068
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
case requestCancelled
case tooManyRequests(response: HTTPURLResponse? = nil, data: Data? = nil)
case unauthorized(response: HTTPURLResponse? = nil, data: Data? = nil)
case maintenance
case outage
case server
case maintenance(errorCode: Int)
case outage(errorCode: Int)
case server(errorCode: Int)
case unknown(response: HTTPURLResponse? = nil, data: Data? = nil)
case noInternetConnection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,11 @@ private extension SessionManager {
case 429:
completion(.failure(.tooManyRequests(response: response, data: data)))
case 503:
completion(.failure(.maintenance))
completion(.failure(.maintenance(errorCode: statusCode)))
case 500:
completion(.failure(.outage))
completion(.failure(.outage(errorCode: statusCode)))
case 501, 502, 504...599:
completion(.failure(.server))
completion(.failure(.server(errorCode: statusCode)))
default:
completion(.failure(.unknown(response: response, data: data)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ class ErrorScreenViewController: UIViewController {

- returns: A view controller instance allowing the user to take a picture or pick a document.
*/
public init(
giniConfiguration: GiniConfiguration,
type: ErrorType,
documentType: GiniCaptureDocumentType,
viewModel: BottomButtonsViewModel
) {
public init(giniConfiguration: GiniConfiguration,
type: ErrorType,
documentType: GiniCaptureDocumentType,
viewModel: BottomButtonsViewModel) {
self.giniConfiguration = giniConfiguration
self.viewModel = viewModel
self.errorType = type
Expand All @@ -91,6 +89,26 @@ class ErrorScreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()

sendAnalyticsScreenShown()
}

private func sendAnalyticsScreenShown() {
var eventProperties = [AnalyticsProperty(key: .documentType,
value: AnalyticsMapper.documentTypeAnalytics(from: documentType))]

let errorAnalytics = errorType.errorAnalytics()
eventProperties.append(AnalyticsProperty(key: .errorType, value: errorAnalytics.type))
if let code = errorAnalytics.code {
eventProperties.append(AnalyticsProperty(key: .errorCode, value: code))
}

if let reason = errorAnalytics.reason {
eventProperties.append(AnalyticsProperty(key: .errorMessage, value: reason))
}

AnalyticsManager.trackScreenShown(screenName: .error,
properties: eventProperties)
}

func setupView() {
Expand Down Expand Up @@ -134,19 +152,17 @@ class ErrorScreenViewController: UIViewController {
}

private func configureButtons() {
buttonsView.enterButton.addTarget(
viewModel,
action: #selector(viewModel.didPressEnterManually),
for: .touchUpInside)
buttonsView.retakeButton.addTarget(
viewModel,
action: #selector(viewModel.didPressRetake),
for: .touchUpInside)
buttonsView.enterButton.addTarget(self,
action: #selector(didPressEnterManually),
for: .touchUpInside)
buttonsView.retakeButton.addTarget(self,
action: #selector(didPressRetake),
for: .touchUpInside)
}

private func configureCustomTopNavigationBar() {
let cancelButton = GiniBarButton(ofType: .cancel)
cancelButton.addAction(viewModel, #selector(viewModel.didPressCancel))
cancelButton.addAction(self, #selector(didPressCancel))

if giniConfiguration.bottomNavigationBarEnabled {
navigationItem.rightBarButtonItem = cancelButton.barButton
Expand All @@ -157,6 +173,21 @@ class ErrorScreenViewController: UIViewController {
}
}

@objc func didPressEnterManually() {
AnalyticsManager.track(event: .enterManuallyTapped, screenName: .error)
viewModel.didPressEnterManually()
}

@objc func didPressRetake() {
AnalyticsManager.track(event: .backToCameraTapped, screenName: .error)
viewModel.didPressRetake()
}

@objc func didPressCancel() {
AnalyticsManager.track(event: .closeTapped, screenName: .error)
viewModel.didPressCancel()
}

private func getButtonsMinHeight(numberOfButtons: Int) -> CGFloat {
if numberOfButtons == 1 {
return Constants.singleButtonHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import GiniBankAPILibrary
- authentication: Error related to authentication.
- unexpected: Unexpected error that is not covered by the other cases.
- maintenance: Error returned when the system is under maintenance.
- outage: Error indicating that the service is unavailable due to outage.
*/

@objc public enum ErrorType: Int {
Expand All @@ -28,6 +29,9 @@ import GiniBankAPILibrary
case maintenance
case outage

// Dictionary to store ErrorAnalytics for each case
private static var errorAnalyticsDictionary: [ErrorType: ErrorAnalytics] = [:]

/**
Initializes a new instance of the `ErrorType` enum based on the given `GiniError`.

Expand Down Expand Up @@ -55,6 +59,11 @@ import GiniBankAPILibrary
default:
self = .unexpected
}

// Generate error analytics using AnalyticsMapper
let errorAnalytics = AnalyticsMapper.errorAnalytics(from: error)
// Store error analytics in the dictionary
ErrorType.errorAnalyticsDictionary[self] = errorAnalytics
}

func iconName() -> String {
Expand Down Expand Up @@ -139,4 +148,15 @@ import GiniBankAPILibrary
comment: "Outage error")
}
}

/**
Get the error analytics for the current `ErrorType`.

- Returns: An `ErrorAnalytics` object representing the analytics for the error.
*/
func errorAnalytics() -> ErrorAnalytics {
let unknownError = ErrorAnalytics(type: "", code: nil,
reason: "Error analytics not found for \(self)")
return ErrorType.errorAnalyticsDictionary[self] ?? unknownError
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ extension GiniScreenAPICoordinator: AnalysisDelegate {
}

self.trackingDelegate?.onAnalysisScreenEvent(event: Event(type: .error))
let viewController = ErrorScreenViewController(
giniConfiguration: giniConfiguration,
type: errorType,
documentType: pages.type ?? .pdf,
viewModel: viewModel)
let viewController = ErrorScreenViewController(giniConfiguration: giniConfiguration,
type: errorType,
documentType: pages.type ?? .pdf,
viewModel: viewModel,
errorAnalytics: ErrorAnalytics(type: ""))

screenAPINavigationController.pushViewController(viewController, animated: animated)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ enum AnalyticsEvent: String {
// MARK: - No Results and Error
case enterManuallyTapped = "enter_manually_tapped"
case retakeImagesTapped = "retake_images_tapped"
case backToCameraTapped = "back_to_camera_tapped"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// AnalyticsMapper.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import Foundation
import GiniBankAPILibrary

class AnalyticsMapper {

static func documentTypeAnalytics(from noResultType: NoResultScreenViewController.NoResultType) -> String {
switch noResultType {
case .pdf:
return "pdf"
case .image:
return "image"
case .qrCode:
return "qrCode"
default:
return "unknown"
}
}

static func documentTypeAnalytics(from documentType: GiniCaptureDocumentType) -> String {
switch documentType {
case .pdf:
return "pdf"
case .image:
return "image"
case .qrcode:
return "qrCode"
}
}

static func errorAnalytics(from error: GiniError) -> ErrorAnalytics {
switch error {
case .badRequest(let response, _):
return ErrorAnalytics(type: "bad_request", code: response?.statusCode, reason: error.message)
case .notAcceptable(let response, _):
return ErrorAnalytics(type: "not_acceptable", code: response?.statusCode, reason: error.message)
case .notFound(let response, _):
return ErrorAnalytics(type: "not_found", code: response?.statusCode, reason: error.message)
case .noResponse:
return ErrorAnalytics(type: "no_reponse", reason: error.message)
case .parseError(_, let response, _):
return ErrorAnalytics(type: "bad_request", code: response?.statusCode, reason: error.message)
case .requestCancelled:
return ErrorAnalytics(type: "request_cancelled", reason: error.message)
case .tooManyRequests(let response, _):
return ErrorAnalytics(type: "too_many_requests", code: response?.statusCode, reason: error.message)
case .unauthorized(let response, _):
return ErrorAnalytics(type: "unauthorized", code: response?.statusCode, reason: error.message)
case .maintenance(let errorCode):
return ErrorAnalytics(type: "maintenance", code: errorCode, reason: error.message)
case .outage(let errorCode):
return ErrorAnalytics(type: "outage", code: errorCode, reason: error.message)
case .server(let errorCode):
return ErrorAnalytics(type: "server", code: errorCode, reason: error.message)
case .unknown(let response, _):
return ErrorAnalytics(type: "unknown", code: response?.statusCode, reason: error.message)
case .noInternetConnection:
return ErrorAnalytics(type: "no_internet")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,7 @@ enum AnalyticsPropertyKey: String {
case ibanDetectionLayerVisible = "iban_detection_layer_visible"

case errorMessage = "error_message"
case documentType = "document_type"
case errorCode = "error_code"
case errorType = "error_type"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation

enum AnalyticsScreen: String {
Expand All @@ -13,4 +12,5 @@ enum AnalyticsScreen: String {
case review
case analysis
case noResults = "no_results"
case error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ErrorAnalytics.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import Foundation

public struct ErrorAnalytics {
let type: String
let code: Int?
let reason: String?

init(type: String, code: Int? = nil, reason: String? = nil) {
self.type = type
self.code = code
self.reason = reason
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ final class GiniScreenAPICoordinatorTests: XCTestCase {

let rootViewController = coordinator.start(withDocuments: capturedImages)
_ = rootViewController.view
let errorType = ErrorType(error: .server)
let errorType = ErrorType(error: .server(errorCode: 502))
coordinator.displayError(errorType: errorType, animated: false)
let screenNavigator = rootViewController.children.first as? UINavigationController
let errorScreen = screenNavigator?.viewControllers.last as? ErrorScreenViewController
Expand All @@ -193,7 +193,7 @@ final class GiniScreenAPICoordinatorTests: XCTestCase {

let rootViewController = coordinator.start(withDocuments: capturedImages)
_ = rootViewController.view
let errorType = ErrorType(error: .maintenance)
let errorType = ErrorType(error: .maintenance(errorCode: 503))
coordinator.displayError(errorType: errorType, animated: false)
let screenNavigator = rootViewController.children.first as? UINavigationController
let errorScreen = screenNavigator?.viewControllers.last as? ErrorScreenViewController
Expand All @@ -212,7 +212,7 @@ final class GiniScreenAPICoordinatorTests: XCTestCase {

let rootViewController = coordinator.start(withDocuments: capturedImages)
_ = rootViewController.view
let errorType = ErrorType(error: .outage)
let errorType = ErrorType(error: .outage(errorCode: 500))
coordinator.displayError(errorType: errorType, animated: false)
let screenNavigator = rootViewController.children.first as? UINavigationController
let errorScreen = screenNavigator?.viewControllers.last as? ErrorScreenViewController
Expand Down

0 comments on commit 0bae068

Please sign in to comment.