-
Notifications
You must be signed in to change notification settings - Fork 21
[V3] Add Networking to Send Analytics #75
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
Conversation
@@ -8,13 +8,16 @@ public class POPPopupBridge: NSObject, WKScriptMessageHandler { | |||
/// Exposed for testing | |||
var returnedWithURL: Bool = false | |||
|
|||
static var analyticsService: AnalyticsServiceable = AnalyticsService() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is static have we ensure it's being destroyed properly when this class is deinitalized? Just want to make sure we aren't keeping this around somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent observation, let me check. The reason i created this static property is because we need to send the started
event when this class is instantiated. I couldn't find a way to use dependency injection, and this approach allows me to conduct the necessary tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently this property does not get destroyed, once created, it persists throughout the application's lifecycle. As I mentioned earlier, the reason it is static is that it's the only way I found to conduct tests. I need to test the start
event sent in the public init
, which requires injecting a mock
of the AnalyticsService class. Any other ideas, or shall we continue with the static property?
The PopupBridge class never deinitializes properly. I tested this on the main
branch, and the same issue occurs. It seems we do have some retain cycles, but they are not due to the integration of the static property (once we manage to deinitialize PopupBridge, we will be able to see if the AnalyticsService class deinitializes correctly).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 yeah we should definitely address it not deinitializing or provide merchants more explicit instructions on destroying this class. If it's happening on main as well I don't want to make it a blocker, but can you make a ticket for us to dig into this a bit more post release?
Should we consider moving the started event out of the init and into the first thing that's called post init? Would that allow us to make this non static? Sounds like we'd still want to address the deinit of this class at a later time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ticket DTMOBILES-1487
Maybe i can think of another approach. We have two flows: PayPal (via PayPal -> Pay with Credit Card) and Venmo, where there isn't another method called after the init that we could use to send the 'start' event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it need to be static? Can it just be an instance property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "start" event is triggered in the public init, and there's no way to inject the mock AnalyticsService for testing. Any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take a look at the public init,
Self.analyticsService.sendAnalyticsEvent(PopupBridgeAnalytics.started, sessionID: sessionID) |
public init(webView: WKWebView, prefersEphemeralWebBrowserSession: Bool = true) {
self.webView = webView
super.init()
Self.analyticsService.sendAnalyticsEvent(PopupBridgeAnalytics.started, sessionID: sessionID)
configureWebView()
webAuthenticationSession.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession
returnBlock = { url in
guard let script = self.constructJavaScriptCompletionResult(returnURL: url) else {
return
}
self.injectWebView(webView: webView, withJavaScript: script)
return
}
}
@@ -8,13 +8,16 @@ public class POPPopupBridge: NSObject, WKScriptMessageHandler { | |||
/// Exposed for testing | |||
var returnedWithURL: Bool = false | |||
|
|||
static var analyticsService: AnalyticsServiceable = AnalyticsService() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 yeah we should definitely address it not deinitializing or provide merchants more explicit instructions on destroying this class. If it's happening on main as well I don't want to make it a blocker, but can you make a ticket for us to dig into this a bit more post release?
Should we consider moving the started event out of the init and into the first thing that's called post init? Would that allow us to make this non static? Sounds like we'd still want to address the deinit of this class at a later time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One small docstrings cleanup, but assuming we verified we are seeing these events in FPTI, this looks great to me! 🚀
Co-authored-by: Jax DesMarais-Leder <jdesmarais@paypal.com>
import Foundation | ||
|
||
enum NetworkError: Error { | ||
case invalidResponse |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on passing the statusCode so it can be logged?
case invalidResponse(statusCode: Int)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this way it remains as basic as possible, which is what's expected, since im using this in the NetworkClient, the scope of httpResponse is limited within the guard block, so i cannot directly use it in the throw statement outside of that block. If in the future we need to add more endpoints, I think that would add more value.
45CD0C2C2D64F08F0072C5A4 /* FPTIBatchData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2B2D64F0810072C5A4 /* FPTIBatchData.swift */; }; | ||
45CD0C2E2D664D140072C5A4 /* Date+MilisecondTimestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2D2D664CF50072C5A4 /* Date+MilisecondTimestamp.swift */; }; | ||
45CD0C302D6788A10072C5A4 /* Bundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2F2D67888F0072C5A4 /* Bundle+Extension.swift */; }; | ||
45CD0C322D6793FB0072C5A4 /* PopupBridgeAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C312D6793F90072C5A4 /* PopupBridgeAnalytics.swift */; }; | ||
45FBAF272D6F9CCF000D550B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF262D6F9CC6000D550B /* AnalyticsService.swift */; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add all of the new files that were specifically added for analytics to an Analytics
folder under Sources/PopupBridge
? nitpick, but would help navigation of the PopupBridge source files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated: c8fd4cf
// MARK: - Private Properties | ||
|
||
private let messageHandlerName = "POPPopupBridge" | ||
private let hostName = "popupbridgev1" | ||
private let hostName = "popupbridgev1" | ||
private let sessionID = UUID().uuidString.replacingOccurrences(of: "-", with: "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this sessionID creation live in AnalyticsService instead? Since it isn't required by any of the login in POPPopupBridge.swift
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, no, because the sessionID would always be the same, due to AnalyticsService being a static var.
If you have any suggestions for adding AnalyticsService that isn't a static var, I'd be happy to work on it. The reason it's a static variable is that in tests, I need to inject the mock of AnalyticsService. I can't inject it in the init, and the "start" event is triggered in the init :(
Sources/TestPlan.xctestplan
Outdated
@@ -0,0 +1,24 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this file be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeap, deleted: 5911546
func post(url: URL, body: FPTIBatchData) async throws | ||
} | ||
|
||
final class NetworkClient: Networkable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we know that PopupBridge doesn't do any other networking besides just sending a single POST for analytics, do we think it's worth having this as a separate layer from AnalyticsService? Thinking of the KISS principe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated: 7bc9147
// MARK: - Private Properties | ||
|
||
/// The FPTI URL to post all analytic events. | ||
private let url = URL(string: "https://api.paypal.com/v1/tracking/batch/events")! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we aren't actually batching, FPTI has a separate endpoint for single event upload /v1/tracking/events/
. The format is slightly different from /v1/tracking/batch/events/
. I'm not sure if there are performance benefits to using the single event API vs the multiple event API, but we could confirm in #help-fpti.
To see the structure of each, you can look at the API details in PPAAS (I also have it in a Postman collection)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's the endpoint used in PPCP, however, we would need a custom encode method. I did a POC with those changes and was thinking that it would be better to have the same implementation we have in BT, which is why I opted for this implementation. While working on BT v7, we chose not to use a custom encode method. Do you think we could use it for this repo?
Summary of changes
Checklist
Authors