SAP / cloud-sdk-js

Use the SAP Cloud SDK for JavaScript / TypeScript to reduce development effort when building applications on SAP Business Technology Platform that communicate with SAP solutions and services such as SAP S/4HANA Cloud, SAP SuccessFactors, and many others.
Apache License 2.0
168 stars 57 forks source link

How to do deep insert function with Cloud SDK (Upsert / Batch) #407

Closed phangkw closed 4 years ago

phangkw commented 4 years ago

Hi Expert,

I have a requirement to create a Success Factor extension with SAP UI5 (frontend) + nodejs (middleware) + SF api (data model). 
After we have spoken to SAP expert, to ease all the necessary steps to perform "Principle Propogation" with beare token call from Success Factor -> Sap Cloud Platform (Cloud Foundry) -> Propagate to RESTFUL services (via api call) hosted in CF, the authorisation is called via Success Factor => SAP IAS server. 

What I have done is to download the OData definition (EDMX file) from API Hub and generated the functions via the npx generate-odata-client --inputDir service-specs --outputDir generated --forceOverwrite command. The system is generated correctly.

We are currently able to follow most of the tutorial from https://sap.github.io/cloud-sdk/docs/js/features/odata/overview for both "C" - Post, "R" - Get, "U" - Put and "D" - Delete accordingly. But when we tried to follow the example given in the blog for Batch Requests# ( retrieve requests and changesets ) , we are not able to follow as the batch and changeset function is not defined in our generated class. Can you kindly advice is there anywhere which we can do "UPSERT" or "BATCH" function using SDK ? and if you can provide some example, it will be great help.

Example of batch and changeset for businessPartnerAddress (sample) but this is not generated as part of the npx generate command.

updateAddresses(businessPartnerAddresses: BusinessPartnerAddress[]) :Promise<BusinessPartnerAddress[]> { const updateRequests = businessPartnerAddresses.map( address => BusinessPartnerAddress .requestBuilder() .update(address) );

const [updateChangesetResponse] = await batch(changeset(...updateRequests)) .execute(destination);

return (updateChangesetResponse as WriteResponses) .responses .map( response => response.as(BusinessPartnerAddress) ); }

artemkovalyov commented 4 years ago

Hi @phangkw,

I think Batch is not a part of the type-safe client, but a separate function you have to import. @FrankEssenberger, am I correct. Can we update our docs to reflect this necessity?

FrankEssenberger commented 4 years ago

Hello phangkw,

the batch requests are done differently the the normal OData request. Here is a detailed tutorial on how to use batch:

https://developers.sap.com/tutorials/cloudsdk-js-odata-batch-changeset.html

However, with the SuccessFactor API we had always some problems in the past, because upsert is a very common method there, but it is not an official OData or RESTfull keyword. There you only have the four CRUD as you mentioned. So currently we do not generate these methods for entities. Sometimes they are modeled as function imports in the edmx, then you can find them as function imports.

Perhaps you can share the edmx file (frank.essenberger@sap.com) and tell me which methods you want to invoke. Then I can have a look and see if they are generated or if it is possible that we add support for that.

If you can not find it in the function imports and we realize that a support of these methods is not possible easily I would suggest you use the untyped client:

https://github.com/SAP/cloud-sdk/blob/master/packages/core/src/http-client/http-client.ts

with the method: executeHttpRequest. With this you also get all the destination lookup, auth features of the SDK but can execute plain HTTP request.

Best Frank

phangkw commented 4 years ago

e had always some problems in the past, because upsert is a very common method there, but it is not an official OData or RESTfull keyword. There you only have the four CRUD as you mentioned. So currently we do not generate these methods for entities. Sometimes they are modeled as function imports in the edmx, then you can find them as function imports. Hi Frank,

This is the EDMX file which we are using. This is only 1 out of many entity we are trying to do so that we can perform an upsert / batch functions.

https://api.sap.com/api/ECEmploymentInformation/overview?name=ECEmploymentInformation&type=EDMX&action=download&artifactType=ODATA

or u can find it under the attachment. ECEmploymentInformation (3).zip

When we try to call the batch / chargeset, it is not available.

phangkw commented 4 years ago

If you can not find it in the function imports and we realize that a support of these methods is not possible easily I would suggest you use the untyped client:

https://github.com/SAP/cloud-sdk/blob/master/packages/core/src/http-client/http-client.ts

with the method: executeHttpRequest. With this you also get all the destination lookup, auth features of the SDK but can execute plain HTTP request.

Hi Frank, will you be able to provide one sample snippet code on how can we make use of the call to executeHttpRequest, so sorry that i'm not able to figure out how can we implement this to retrieve the response.

============================================================================== This was the sample snippet code i have extracted from api hub.

var data = null;

var xhr = new XMLHttpRequest(); xhr.withCredentials = false;

xhr.addEventListener("readystatechange", function () { if (this.readyState === this.DONE) { console.log(this.responseText); } });

//setting request method //API endpoint for API sandbox xhr.open("GET", "https://sandbox.api.sap.com/successfactors/odata/v2/EmpJobRelationships");

//API endpoint with optional query parameters //xhr.open("GET", "https://sandbox.api.sap.com/successfactors/odata/v2/EmpJobRelationships?$top=integer&$skip=integer"); //To view the complete list of query parameters, see its API definition.

//Available API Endpoints //https://api2.successfactors.eu/odata/v2 //https://apisalesdemo2.successfactors.eu/odata/v2 //https://api2preview.sapsf.eu/odata/v2 //https://api4.successfactors.com/odata/v2 //https://apisalesdemo4.successfactors.com/odata/v2 //https://api4preview.sapsf.com/odata/v2 //https://api5.successfactors.eu/odata/v2 //https://api8.successfactors.com/odata/v2 //https://apisalesdemo8.successfactors.com/odata/v2 //https://api8preview.sapsf.com/odata/v2 //https://api10.successfactors.com/odata/v2 //https://api10preview.sapsf.com/odata/v2 //https://api012.successfactors.eu/odata/v2 //https://apirot.successfactors.eu/odata/v2 //https://api12preview.sapsf.eu/odata/v2 //https://api15.sapsf.cn/odata/v2 //https://api16.sapsf.eu/odata/v2 //https://api17preview.sapsf.com/odata/v2 //https://api17.sapsf.com/odata/v2 //https://api18preview.sapsf.com/odata/v2 //https://api18.sapsf.com/odata/v2

//adding request headers xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Accept", "application/json"); //API Key for API Sandbox xhr.setRequestHeader("APIKey", "");

//Available Security Schemes for productive API Endpoints //Basic Authentication

//Basic Auth : provide username:password in Base64 encoded in Authorization header //xhr.setRequestHeader("Authorization", "Basic ");

//sending request xhr.send(data);

=========================================================================

FrankEssenberger commented 4 years ago

Hello phangkw ,

I will have a look at the edmx file as soon I find some time. Regarding the http client you would call it like this:

const destination = {
   destinationName: <yourDestinationName>,
   jwt: <yourJwtToken>
}//You can also give the destination object directly here. But the lookup is more convinient.
const config = {
     method: HttpMethod.GET,
     params: {
          a: 'a',
          b: 'b'
     }
};

const response = await executeHttpRequest(destination, config);

Best Frank

phangkw commented 4 years ago

Hello phangkw ,

I will have a look at the edmx file as soon I find some time. Regarding the http client you would call it like this:

const destination = {
   destinationName: <yourDestinationName>,
   jwt: <yourJwtToken>
}//You can also give the destination object directly here. But the lookup is more convinient.
const config = {
     method: HttpMethod.GET,
     params: {
          a: 'a',
          b: 'b'
     }
};

const response = await executeHttpRequest(destination, config);

Best Frank

=================================================================================

Hi Frank, thank you very much. Managed to perform the API call as per the explanation. Just added my codes as below just incase others people encountering the same issues as I struggle in the past 2 weeks. =).
<the code might not be the best but it works !>

================================================================================= //Adding the libraries from the sap cloud sdk/core. const { retrieveJwt, executeHttpRequest, HttpMethod } = require("@sap-cloud-sdk/core");

exports.= function (req, res) {

const destination = { destinationName: jwt: retrieveJwt(req) //just incase u need to pass by JWT token } //You can also give the destination object directly here. But the lookup is more convinient. const config = { method: HttpMethod.GET, url: '/odata/v2/getUsersByDynamicGroup', params: { // pass in the query parameters groupId: process.env.hradmin } };

//Used Promise instead of await / sync. it just work vice versa. return new Promise((resolve, reject)=>{

    executeHttpRequest(destination, config)
    //axios.get('https://api.github.com/users/mapbox')
    .then((response) => {
        resolve(response.data);
    });
});

} }

FrankEssenberger commented 4 years ago

Good that we have a working solution for the moment. We will also add some code example and information in our docu page: https://sap.github.io/cloud-sdk/.

FrankEssenberger commented 4 years ago

In order to close the issue I also looked at the client generated by the VDM. As I expected the upsert is not represented and support would be tricky. So in this case use the work around I mentioned. For batch I see in the BatchRequest.ts or typescript analogon. With this you can do batch request as described in the tutorial mentioned above with something like:

  const updateRequests = businessPartnerAddresses.map(address => BusinessPartnerAddress.requestBuilder().update(address));

  const [updateChangesetResponse] = await batch(changeset(...updateRequests)).execute({
    url: 'https://my.s4hana.ondemand.com/'
  });

Best Frank