Open seeroush opened 6 months ago
Hey @seeroush thank's for reaching out.
The usage of the SDK does look correct, and the empty patch request is rather suspicious. Also, the fact that CSRF fetching as well as the ETag matching don't seem to work out of the box for you (it should be fetched by default for non-get requests) hint at multiple issues.
Are you certain the entry
variable actually contains anything when you create the batch request at runtime?
Hi @tomfrenken, yes. I validated that the values were non-empty via a debugger. I'm currently having to work around the issue by explicitly setting the payload like so:
updateRequest.requestConfig.payload = {
"PaymentTerms": 'CUSTOM',
"CashDiscountAmount": new BigNumber(5.00),
}
Do the handling of the out-of-the-box features depend on the destination being configured a certain way? One thing I should clarify is that I am hitting an on-premesis SAP instance and am not connecting through an SAP cloud proxy. Could that have something to do with the strange behavior around CSRF/ETag fetching?
Hmm, let's try a few things.
1) We read the etag
from the response.
The selection process for the appropriate etag
looks like this:
const eTag =
extractEtagFromHeader(response.headers) ||
this.extractODataEtag(response.data) ||
this.requestConfig.eTag;
We can see in the logs that the entity's .versionIdentifier
property is populated, which means the requestConfig's etag
property already contains this value.
If you don't set the if-match
header manually, what value does it contain instead?
I'm assuming the response's headers or response's data already contained a different header.
2) To fetch a CSRF token, we make up to two requests, looking like this:
Request: HEAD /resource/path/
Request Header: X-CSRF-Token=fetch
and
Request: HEAD /resource/path
Request Header: X-CSRF-Token=fetch
if this doesn't work, the target system is likely the cause, meaning it doesn't follow the usual default. To deviate from the default, we have documented how to change the SDK's default behavior here.
Thanks, Tom! The solution I currently landed with does look similar to what you've outlined. Because I'm doing batch operations, I had to manually build out the middleware as you suggested to extract both CSRF and etag tokens. The psuedocode looks something like:
// Fetch CSRF token and cookie via middleware on a GET request
const {token, cookies} = getCsrfToken();
// entries are returned with a single HTTP batch request
// etags are returned as a map<document_id, etag>. The etag identifiers are plucked from response.body.d.__metadata.etag
const {entries, etags} = await fetchViaBatch(myEntryIdentifiers);
// Build changeset request for all entries which includes setEditable, updateEntryValue, setReadOnly
const changesets = entries.map((entry) => getChangeSet(entry))
// Perform batch update request with all changesets
const batchRequest = batch(changesets);
const batchReqId = batchRequest.build().config.boundary;
const responses = await batchRequest.addCustomHeaders({
'x-csrf-token': resp.csrfToken,
// Not entirely sure why, but I had to manually fetch the boundary value and set the top-level header for it to work.
'content-type': `multipart/mixed;boundary=${batchReqId}`,
'cookie: `${resp.cookie};`,
}).execute({ destinationName: 'DESTINATION' });
While this works in practice for a single SAP instance, I'm trying to identify exceptions so that when we start interfacing with more than one SAP instance, the code will be as portable as possible. I suppose I'm just having a hard time understanding what conditions (destination configuration, request building, etc...) are ideal so that I don't need to manually fetch and build my requests. It sounds like the client is supposed to handle it, but I'm not sure what the exceptions/conditions are that requires a custom solution.
I will talk to the team to see if it's possible for us to use a cloud connector for our test SAP deployment.
The issues you are currently facing should be unrelated to the cloud connector.
Can you share for which service you've generated the client? That way I can generate it myself to further debug it.
Ideally the target system's endpoint returns the csrf token so you don't have to build your own middleware and create the csrf token & cookie headers.
I'm surprised you had to set 'content-type': multipart/mixed;boundary=${batchReqId}
, given that in the logs that header is already set
Content-Type: multipart/mixed; boundary=changeset_d7a5a14d-b3b4-4ed6-ae30-0c07b96eeaeb
The name of the service is FAC_FINANCIAL_DOCUMENT_SRV_01. I've attached the .edmx file (as a .txt) with the redacted domain URI (fake.domain.com). Let me know if that helps.
Alright, I've generated a client and tried to replicate the issue, however, for me, the correct payload was created.
My current assumption is, that the remote state has been updated in between your requests, which would lead to an empty payload, as the remote state already contains the change.
Can you verify what the remote state contains at runtime? For this, add a breakpoint for either one of the two:
entry.remoteState
entry["remoteState"]
here you can check if the entry already contained the desired change.
Alternatively, you can also try to use a PUT
request instead, as documented here.
This should definitely contain your change, as it doesn't depend on the remote state.
Thanks Tom! I'll look into the remoteState field to see what its contents are prior to the update batch request. I'm going to be out-of-office for the next week, so there'll be a bit of delay in the response. I appreciate the feedback and all your effort in helping out.
Hey @seeroush, were you able to identify the issue with @tomfrenken's suggestions?
I'm not sure if this is a bug, or possibly the way I am utilizing the generated OData v2 client. I am attempting to perform a batch request on a particular API, and noticed that the
PATCH
method is not providing any payload in the changeset body. From the documentation, I'd expect the assignment of the paymentTerms and cashDiscountAmount to be in the payload based on the following statement:If anyone could guide me on how to properly use the changeset/batch API in addition to updating an entity, it'd be appreciated. I also noticed that
etag
andcsrf
functionality of the client was not automatically handled, so I had to manually add the appropriate headers to get to this point. I kept that out of the paired-down sample below, but if CSRF handling should be automatic, guidance around how to get the auto-generated clients to handle it would be appreciated as well. The documentation led me to believe it's automatic.Here's a sample of how I'm using a combination of
batch
andchangeset
to perform the request.Dependencies:
Code:
I am getting 415 responses from the server, so I added some middleware to output the request body to console, and noticed that the
PATCH
request body is set to{}
when I would've expected it to be:Thanks in advance!