Skip to content

MOB-20804 - updateProposition api with a callback to handle timeout#110

Merged
akhiljain1907 merged 25 commits intoadobe:dev-v5.0.2from
akhiljain1907:fix/updateProposition
Sep 19, 2024
Merged

MOB-20804 - updateProposition api with a callback to handle timeout#110
akhiljain1907 merged 25 commits intoadobe:dev-v5.0.2from
akhiljain1907:fix/updateProposition

Conversation

@akhiljain1907
Copy link
Copy Markdown
Contributor

@akhiljain1907 akhiljain1907 commented Aug 22, 2024

Description

This PR supports update proposition public api call with an optional completion handler invoked with list of successful decision scopes and errors received by edge (if any). In order to provide error details back we have also created a public class AEPOptimizeError and kept an additional field of AEPError in it which is mapped on the basis of error status received.

Related Issue

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@akhiljain1907 akhiljain1907 changed the title Updated updateProposition api with a callback to handle timeout. MOB-20804 - updateProposition api with a callback to handle timeout Aug 22, 2024
@@ -1,68 +0,0 @@
PODS:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be deleted? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored old podfile.lock from base branch.

/// - Parameter data: Additional free-form data to be sent in the personalization request.
@objc(updatePropositions:withXdm:andData:)
static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil) {
/// - Parameter completion: An optional callback to be used by consumer
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add what is returned in the callback?
- Parameter completion: Optional completion handler invoked with errors, if any

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil) {
/// - Parameter completion: An optional callback to be used by consumer
@objc(updatePropositions:withXdm:andData:completion:)
static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: ((Bool, Error?) -> Void)? = nil) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we can do away with the boolean value and simplify the completion handler to just _ completion: ((Error?) -> Void)?. If there's no error, we can return null. The boolean field seems a bit superfluous. Thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have changed this to keep list of successful decision scopes and AEPOptimize error as of now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update - we are now returning ([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) in completion

type: EventType.optimize,
source: EventSource.responseContent,
data: [
OptimizeConstants.EventDataKeys.COMPLETED_UPDATE_EVENT_ID: requestEventId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value isn't being read or used in the public API response callback. Can we omit it? The public API does not need to aware of this edge request event id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from here we are sending list of successful decision scopes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update - now we are sending propositionsInProgress: [DecisionScope: OptimizeProposition] from here

completion?(false, AEPError.callbackTimeout)
return
}
if let error = responseEvent.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR] as? AEPError {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you check if you can use getTypedData here instead of casting?

let error: AEPError = responseEvent.getTypedData(for: OptimizeConstants.EventDataKeys.RESPONSE_ERROR)

Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Please fix code formatting.
  2. Make changes to existing tests if needed and ensure all of them pass.
  3. In OptimizePublicAPITests, please add a test which has a completion handler passed in updatePropositions API call and verify its called in case of timeout.
  4. In OptimizeIntegrationTests, please add a test testUpdatePropositions_timeoutError with a completion handler passed in and simulating a network request timeout case.

@akhiljain1907 akhiljain1907 marked this pull request as draft August 28, 2024 06:19
@akhiljain1907 akhiljain1907 marked this pull request as ready for review August 30, 2024 07:51
@akhiljain1907
Copy link
Copy Markdown
Contributor Author

Hi @spoorthipujariadobe, I have made few changes based on your comments and added test cases as you suggested. Please review.

…changed default value of aepError to unexpected
source: EventSource.responseContent,
data: [
OptimizeConstants.EventDataKeys.RESPONSE_ERROR: error.rawValue
OptimizeConstants.EventDataKeys.RESPONSE_ERROR: error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If createErrorResponseEvent(_ error: AEPError) isn't being used anymore, can we remove it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we return AEPError in getProposition api, we are using this function. If we return AEPOptimizeError instead, we can remove this function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline to return AEPOptimizeError in getPropositions as well for API consistency

static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil) {
/// - Parameter completion: Optional completion handler invoked with list of successful decision scopes and errors, if any
@objc(updatePropositions:withXdm:andData:completion:)
static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: (([DecisionScope]?, AEPOptimizeError?) -> Void)? = nil) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we're sending back the propositions that were successfully fetched in the completion handler. Should this be
_ completion: (([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) -> Void)? =nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, we are now sending ([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) in completion

type: EventType.optimize,
source: EventSource.responseContent,
data: [
OptimizeConstants.EventDataKeys.DECISION_SCOPES: Array(self.propositionsInProgress.keys)
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here. Should we also be returning the propositionsInProgress.values which have the actual OptimizeProposition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we are now sending propositionInProgress in event data.

public let status: Int?
public let title: String?
public let detail: String?
public var aepError = AEPError.unexpected
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why we added AEPError here? Could you please share how customers are going to use this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In get proposition api, today we return ([DecisionScope: OptimizeProposition]?, Error?), where Error is of type AEPError. Considering the possibility customer has kept some check or performed any logic on their side on basis of AEPErrors we added this variable. We can discuss on sending back AEPOptimizeError in getProposition api as well instead of AEPError to improve on parity between api callback.

wait(for: [expectation], timeout: 12)
XCTAssertEqual(mockRuntime.dispatchedEvents.count, 2)

let dispatchedEvent = mockRuntime.dispatchedEvents.first
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add assertions for the first event as well to verify the type, source and maybe the propositions that are returned in that event in case of success?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment for all following tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job with the changes and test coverage! I just have a few small questions

Comment on lines +62 to +64
status: 408,
title: "Request Timeout",
detail: "Update proposition request resulted in a timeout.",
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add these values in OptimizeConstants?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +255 to +257
status: 408,
title: "Request Timeout",
detail: "Update proposition request resulted in a timeout.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here. Please add in OptimizeConstants and reuse wherever needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

// response event to provide success callback to updateProposition public api
let responseEventToSend = event.createResponseEvent(
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering we're also using this same event type and source in processGetPropositions do you think it makes sense to move this also to Event+Optimize.swift, similar to createErrorResponseEvent?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, since now we are returning ([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) in update proposition's api, we can create a common util function in Event+Optimize.swift. However, in update proposition callback we can have scenario where we have successful [DecisionScope: OptimizeProposition] and AEPOptimizeError both if one upstream gives error and other succeed. So I tried creating a common function which is like this -
createCallbackResponseEvent(_ data: [DecisionScope: OptimizeProposition], _ error: AEPOptimizeError?) -> Event and in case of get proposition i tried sending error as nil which failed some of the test cases. I am debugging that and once I am clear on that I will push it or will include this in next PR which will cover other edge error handling apart from timeout.

// update propositions
Optimize.updatePropositions(for: [decisionScope], withXdm: nil)
Optimize.updatePropositions(for: [decisionScope], withXdm: nil){ (scope,error) in
XCTAssertNotNil(scope)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you also add XCTAssertNil(error) for success case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

…sition]? instead of [DecisionScope]? in callback and code review changes
…ve AEPOptimizeError as argument and minor changes
Copy link
Copy Markdown
Contributor

@spoorthipujariadobe spoorthipujariadobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending deciding if we plan to return AEPError or AEPOptimizeError in all public APIs

@akhiljain1907 akhiljain1907 added the enhancement New feature or request label Sep 12, 2024
Copy link
Copy Markdown
Member

@sbenedicadb sbenedicadb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks ok other than it's not clear to me what the purpose is of the type property in OptimizeError.

let's either make it more clear what that property is for, or remove it if it's not in use.

@objc(AEPOptimizeError)
public class AEPOptimizeError: NSObject, Error {
typealias HTTPResponseCodes = OptimizeConstants.HTTPResponseCodes
public let type: String?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this type property being used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type property is received in edge error response content and we have kept it for that purpose. For timeout or any other invalid request case on optimize SDK side this will be nil.

@akhiljain1907
Copy link
Copy Markdown
Contributor Author

thanks a lot for reviewing @sbenedicadb, answering for purpose of keeping type field -
"We receive following fields from in Edge error response handler which are being sent from konductor itself -

  1. type - string - A URI reference (RFC3986) that identifies the problem type, following the format https://ns.adobe.com/aep/errors/.
  2. status - number - The HTTP status code generated by the server for this occurrence of the problem.
  3. title - string - A short, human-readable summary of the problem type
  4. detail - string - A short, human-readable description of the problem type
  5. report - object - A map of additional properties that aid in debugging such as the request ID or the org ID. In some cases, it might contain data specific to the error at hand, such as a list of validation errors.

As discussed with team, we have currently decided to send the type field in the error object and thus we have kept it in OptimizeError class."

@akhiljain1907 akhiljain1907 merged commit 47c1d3e into adobe:dev-v5.0.2 Sep 19, 2024
@sbenedicadb
Copy link
Copy Markdown
Member

Thanks @akhiljain1907 - this is super helpful information. it would be good to include this detail in the code itself so anyone maintaining it could get this context as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants