const sfsfSrv = await cds.connect.to('sfsf')
// Option 1: Query Notation
const response = await sfsfSrv.run(SELECT`teamMembersSize`.from`User`.where`userId = ${req.user.id}`)
console.log("option 1: " + response[0].teamMembersSize)
// Option 2: HTTP method-style
const response2 = await sfsfSrv.get(`/User('${req.user.id}')/teamMembersSize/$value`)
console.log("option 2: " + response2)
Tag: SuccessFactors
[CAP] SAPUI5 Filter using FilterOperator.Contains with oData V2
In my SAPUI5 Freesstyle frontend I created a search field above a list. In the searchfield handler I’m creating a filter with the provided query.
const sQuery = oEvent.getParameter("query");
new Filter("firstName", FilterOperator.Contains, sQuery);
Afterwards I’m binding the filter to my list to trigger the binding refresh. But when debugging the backend handler I noticed the following…
In my CAP on Read handler, the filter gets converted into a V4 compatible filter expression:
oData V4: $filter=contains(firstName,'Max')
As I’m forwarding the request to an external V2 oData API (SuccessFactors) this would not work, as for V2 the following filter syntax is needed:
oData V2: $filter=substringof('Max',firstName) eq true
As I could not find any solution to this problem, I manually passed my filter as custom property to my CAP Service and did a manual select.
Adding the custom property in the frontend in my searchfield handler:
onSearch: function (oEvent) {
if (oEvent.getParameters().refreshButtonPressed) {
this.onRefresh();
return;
}
let oBindingInfo = this._oList.getBindingInfo("items");
if (!oBindingInfo.parameters) oBindingInfo.parameters = {};
if (!oBindingInfo.parameters.custom) oBindingInfo.parameters.custom = {};
if (oEvent.getParameter("query")) {
oBindingInfo.parameters.custom.filter = "%" + oEvent.getParameter("query") + "%";
} else {
oBindingInfo.parameters.custom.filter = undefined
}
this._oList.bindItems(oBindingInfo);
}
My CAP handler with the filter handling:
const { Object } = srv.entities
const SF_Srv = await cds.connect.to('SF')
srv.on('READ', Object, async req => {
if (!req._queryOptions.filter) {
// share request context with the external service
return SF_Srv.tx(req).run(req.query);
} else {
//if filter provided, build manually a select statement using a where condition
let input = req._queryOptions.filter;
const tx = SF_Srv.transaction(req);
return await tx.run(
SELECT
.from(Object)
.where`firstName like ${input} or lastName like ${input}`)
}
})
As alternative you could also add the where condition directly to the query object:
const { Object } = srv.entities
const SF_Srv = await cds.connect.to('SF')
srv.on('READ', Object, async req => {
if (req._query.filter) {
//if filter provided, build manually a select statement using a where condition
let { query } = req
let input = req._queryOptions.filter
if (!query.SELECT.where) query.SELECT["where"] = []
query.SELECT.where.push(
{ ref: ['firstName'] }, 'like', { val: input }, "or",
{ ref: ['lastName'] }, 'like', { val: input }, "or",
{ ref: ['object'] }, 'like', { val: input })
}
// share request context with the external service
return SF_Srv.tx(req).run(req.query)
})
[ABAP] Parse SF Entity Key from URI
METHOD get_entitykey_from_uri.
" Pattern: Entity\((.*)\),
DATA(pattern) = iv_entity && `\((.*)\)`.
TRY.
DATA(matcher) = cl_abap_matcher=>create( pattern = pattern
text = iv_uri
ignore_case = abap_true ).
CATCH cx_sy_invalid_regex.
ENDTRY.
TRY.
DATA(lt_matches) = matcher->find_all( ).
ASSIGN lt_matches[ 1 ] TO FIELD-SYMBOL(<s>).
rv_entity_key = substring( val = iv_uri off = <s>-offset len = <s>-length ).
CATCH cx_sy_no_current_match.
ENDTRY.
ENDMETHOD.
[ABAP] JSON to ABAP with dereferencing
Recently I had to fetch an attachment from SuccessFactors. The API returns a huge JSON. Here only a few lines of that json.
{
"d": {
"__metadata": {
"uri": "https://apihostname.successfactors.com:443/odata/v2/Attachment(1000)",
"type": "SFOData.Attachment"
},
"attachmentId": "1000",
"fileName": "Testdokument.pdf",
"mimeType": "application/pdf",
"moduleCategory": "HRIS_ATTACHMENT",
"userId": "10000555",
"fileExtension": "pdf",
"fileContent": "JVBERi0xLjcKjp2jtMXW5/gKMiAwIG9iag0KWy9JQ0NCYXNlZCAzIDAgUl0NCmVuZG9iag0KMyAw\r\nIG9iag0KPDwNCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlIA0KL0xlbmd0aCAyNTk2IA0KL04gMyANCj4+\r\nDQpzdHJlYW0NCnicnZZ3VFPZFofPvTe9UJIQipTQa2hSAkgNvUiRLioxCRBKwJAAIjZEVHBEUZGm\r\nCDIo4ICjQ5GxIoqFAVGx6wQZRNRxcBQblklkrRnfvHnvzZvfH/d+a5+9z91n733WugCQ/IMFwkxY\r\nCYAMoVgU4efFiI2LZ2AHAQzwAANsAOBws7NCFvhGApkCfNiMbJkT+Be9ug4g+fsq0z+MwQD/n5S5\r\nWSIxAFCYjOfy+NlcGRfJOD1XnCW3T8mYtjRNzjBKziJZgjJWk3PyLFt89pllDznzMoQ8GctzzuJl\r\n8OTcJ+ONORK+jJFgGRfnCPi5Mr4mY4N0SYZAxm/ksRl8TjYAKJLcLuZzU2RsLWOSKDKCLeN5AOB......"
...
}
}
Parsing the JSON to ABAP
SAP provides two classes /ui2/cl_json
and /ui2/cl_data_access
to work with JSONs and there are also many cool tools on https://dotabap.org/ for that.
Because it’s just a simple JSON I tried to avoid importing another class to the system. So I stayed with SAPs classes.
First deserialize the JSON with /ui2/cl_json
.
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_responce "the repsonse contains the json string
CHANGING data = lr_data ).
I found three similiar ways to use the class /ui2/cl_data_access
to get the relevant data out of the JSON.
1. Using the class construktor and the value( )
method for earch property.
DATA: lv_filecontent_base64 TYPE string,
lv_filename TYPE string,
lv_documentcategory TYPE string.
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filename }| )->value( IMPORTING ev_data = lv_filename).
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_doc_cat }| )->value( IMPORTING ev_data = lv_documentcategory ).
2. Create an instance as first step and pass the component. Reuse the instance with the value( )
method.
DATA: lv_filecontent_base64 TYPE string,
lv_filename TYPE string,
lv_documentcategory TYPE string.
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
lo_data_access->at( |{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
lo_data_access->at( |{ gc_filename }| )->value( IMPORTING ev_data = lv_filename ).
lo_data_access->at( |{ gc_doc_cat }| )->value( IMPORTING ev_data = lv_documentcategory ).
3. Use the ref( )
method to get a reference of a property value. Because it returns a generic data type (and you cannot dereference generic references) you have to use CAST
combined with dereferencing (->*
) to get the concret value.
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
DATA(lv_filename) = CAST string( lo_data_access->at( gc_filename )->ref( ) )->*.
DATA(lv_documentcategory) = CAST string( lo_data_access->at( gc_doc_cat )->ref( ) )->*.
In the end I stayed with version 3 because I prefere using inline declarations. The complete result looked like this:
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_response "the repsonse contains the json string
CHANGING data = lr_data ).
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
DATA(lv_filename) = CAST string( lo_data_access->at( gc_filename )->ref( ) )->*.
DATA(lv_documentcategory) = CAST string( lo_data_access->at( gc_doc_cat )->ref( ) )->*.
" Decode BASE64 PDF to XString
DATA(lv_decodedx) = cl_http_utility=>if_http_utility~decode_x_base64( lv_filecontent_base64 ).
" XString to binary
DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( lv_decodedx ).
" Download file
cl_gui_frontend_services=>gui_download( EXPORTING bin_filesize = xstrlen( lv_decodedx )
filename = lv_filename
filetype = 'BIN'
show_transfer_status = ' '
CHANGING data_tab = lt_data ).
When /ui2/cl_data_access
is not available (SAP_UI has to be at least 7.51, see note 2526405), you can do it the oldschool way.
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_response "the repsonse contains the json string
CHANGING data = lr_data ).
ASSIGN lr_data->* TO FIELD-SYMBOL(<fs_d>).
ASSIGN COMPONENT 'D' OF STRUCTURE <fs_d> TO FIELD-SYMBOL(<fs_components>).
ASSIGN <fs_components>->* TO FIELD-SYMBOL(<fs_fields>).
ASSIGN COMPONENT gc_doc_cat OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<documentcategory>).
ASSIGN COMPONENT gc_filename OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filename>).
ASSIGN COMPONENT gc_filecontent OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filecontent>).
ASSIGN <documentcategory>->* TO FIELD-SYMBOL(<documentcategory_value>).
ASSIGN <filename>->* TO FIELD-SYMBOL(<filename_value>).
ASSIGN <filecontent>->* TO FIELD-SYMBOL(<filecontent_value>).
"...
[SuccessFactors] Fetch Entity Metadata
https://launchpad.support.sap.com/#/notes/2668887
https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/505856f7d9814f76a8894ec4f0d9e16e.html
# To fetch the entire metadata of an instance
https://<hostname>.successfactors.com/odata/v2/?$metadata
# Fetch the metadata of specific entity
https://<hostname>.successfactors.com/odata/v2/PerPersonRelationship/$metadata
# Fetch metata of specific entity including navigation properties
https://<hostname>.successfactors.com/odata/v2/Entity('PerPersonRelationship')/?$metadata