const { executeHttpRequest } = require('@sap-cloud-sdk/http-client')
const FormData = require('form-data')
try {
//Create payload
const form = new FormData()
form.append('file', fileContent, {
contentType: 'application/pdf'
filename: 'test.pdf'
})
//Create headers
const headers = {
...form.getHeaders(),
'Content-Length': form.getLengthSync(),
}
//Send to Destination
const response = await executeHttpRequest(
{ destinationName: 'TESTINATION' },
{
method: 'POST',
url: 'myApiPath',
headers: headers,
data: form,
responseType: 'arraybuffer' // if you need the response data as buffer to prevent UTF-8 encoding
}
)
console.log({response})
} catch (error) {
console.error(error.message)
}
Tag: CAP
[CAP] Fiori Elements – Add section with PDFViewer
I have a CAP Service that provides a PDF file that I needed to display in a Fiori Elements frontend using the sap.m.PDFViewer. The viewer should be placed in a section on the object page after navigating from the ListReport main page.
My CAP Service has the following annotations to provide the PDF.
entity pdfFiles : cuid, managed {
content : LargeBinary @stream @Core.MediaType: mediaType @Core.ContentDisposition.Filename: fileName @Core.ContentDisposition.Type: 'inline';
mediaType : String @Core.IsMediaType: true;
fileName : String @mandatory;
}
Add a custom section to your view following this example: https://sapui5.hana.ondemand.com/test-resources/sap/fe/core/fpmExplorer/index.html#/customElements/customSectionContent
Two steps are necessary.
1. Add a new section via the manifest. The template path should match your app namespace.
"ObjectPage": {
"type": "Component",
"id": "ObjectPage",
"name": "sap.fe.templates.ObjectPage",
"viewLevel": 1,
"options": {
"settings": {
"editableHeaderContent": false,
"entitySet": "pdfFiles",
"content": {
"body": {
"sections" : {
"myCustomSection": {
"template": "sap.fe.core.fpmExplorer.customSectionContent.CustomSection",
"title": "{i18n>pdfSection}",
"position": {
"placement": "After",
"anchor": "StandardSection"
}
}
}
}
}
}
}
}
2. Add the section content by defining a new fragment in the file CustomSection.fragment.xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:l="sap.ui.layout" xmlns:macro="sap.fe.macros">
<ScrollContainer
height="100%"
width="100%"
horizontal="true"
vertical="true">
<FlexBox direction="Column" renderType="Div" class="sapUiSmallMargin">
<PDFViewer source="{content}" title="{fileName}" height="1200px">
<layoutData>
<FlexItemData growFactor="1" />
</layoutData>
</PDFViewer>
</FlexBox>
</ScrollContainer>
</core:FragmentDefinition>
On the ObjectPage you will now have a new section containing the PDFViewer.
[CAP] Fiori Elements – Display managed fields as value help
data-model.cds
using {
managed
} from '@sap/cds/common';
entity managedEntity: managed {
key ID : UUID;
field : String;
}
annotations.cds
using myService as service from '../../srv/myService';
annotate service.managedEntity with @(
Capabilities.SearchRestrictions: {Searchable: false},
UI.PresentationVariant : {
SortOrder : [{
Property : createdAt,
Descending: true
}],
Visualizations: ['@UI.LineItem']
},
UI.HeaderInfo : {
TypeName : '{i18n>myEntity}',
TypeNamePlural: '{i18n>myEntities}',
},
UI.SelectionFields : [
createdAt,
createdBy
],
UI.LineItem : [
]
) {
createdAt @UI.HiddenFilter : false;
createdBy @UI.HiddenFilter : false;
};
[CAP] CQL Expand Composition / Deep read
In the documentation they always use a star in string literals like this o => o.`*`
to select all fields, but when running this it always failed. However, after some tests I found that this format works with brackets o => o.('*')
https://cap.cloud.sap/docs/guides/providing-services/#–-deep-read
entity myEntity: cuid, managed {
field1 : String;
comp : Composition of one myComposition;
}
aspect myComposition: cuid, managed {
myCompField: String;
}
const result = await SELECT.from(myEntity)
.columns(e => {
e('*')
e.comp(c => c.myCompField) //expand composition, but only select 'myCompField'
})
Update 14.09.2023: is now fixed (#)
[CAP] CQL Deep Update
entity myEntity: cuid, managed {
field1 : String;
comp : Composition of one myComposition;
}
aspect myComposition: cuid, managed {
myCompField: String;
}
const { myEntity } = cds.entities
await UPDATE(myEntity)
.where({
ID: '0732eae8-8858-4917-8865-e4fa2c40xxxx'
})
.set({
comp : [{ myCompField: 'my string data' }]
})
[CAP] UploadSet http test file
How to create an uploadSet in combination with a CAP backend: https://blogs.sap.com/2021/08/18/my-journey-towards-using-ui5-uploadset-with-cap-backend/
You can test the uploadSet CAP backend part using these http calls:
### Create file
# @name file
POST http://localhost:4004/v2/admin/Files
Authorization: Basic admin:
Content-Type: application/json
{
"mediaType": "image/png",
"fileName": "picture.png",
"size": 1000
}
### Fill Variable from Response
@ID = {{file.response.body.$.d.ID}}
### Upload Binary PNG content
PUT http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:
Content-Type: image/png
< ./picture.png
### Get uploaded png
GET http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:
The picture.png file must be in the same folder as the http test file.
[CAP] Dynamically set destination in package.json for an external connection
"cds": {
"requires": {
"sfsf": {
"kind": "odata-v2",
"credentials": {
"destination": "<set during runtime>",
"path": "/odata/v2",
"requestTimeout": 18000000
}
}
}
},
/*
* Handover query to some external SF OData Service to fecth the requested data
*/
srv.on("READ", Whatever, async req => {
const sf_api_def = cds.env.requires['sfsf'] //defined in package.json
sf_api_def.credentials.destination = "myDestinationName" //set your Destination name, could come from a customizing table
const sfsfSrv = await cds.connect.to(sf_api_def)
return await sfsfSrv.run(req.query)
})
[CAP] Providing destinations locally using .env
The provided example
destinations="[{\"name\": \"TESTINATION\", \"url\": \"http://url.hana.ondemand.com\", \"username\": \"DUMMY_USER\", \"password\": \"EXAMPLE_PASSWORD\"}]"
didn’t work for me. I had to change it to the following format:
destinations=[{"name": "TESTINATION", "url": "http://url.hana.ondemand.com", "username": "DUMMY_USER", "password": "EXAMPLE_PASSWORD"}]
You can check the current value using:
process.env.destinations
[CAP] Use Destinations from Subscriber Account
While working on this topic, there are currently two chapters in the CAP docs regarding this topic:
And some SCN posts: here, here and here
But all this didn’t help me to complete the task. Got the solution which finally worked for me from this SAP Sample Susaas (two snippets: here and here).
server.js
const cds = require("@sap/cds")
cds.on('served', async () => {
const { 'cds.xt.SaasProvisioningService': provisioning } = cds.services
// Add provisioning logic only if multitenancy is there..
if (provisioning) {
let tenantProvisioning = require('./provisioning')
provisioning.prepend(tenantProvisioning)
} else {
console.log(">>> There is no service, therefore does not serve multitenancy!")
}
})
module.exports = cds.server
provisioning.js
const xsenv = require('@sap/xsenv')
xsenv.loadEnv()
module.exports = (service) => {
service.on('dependencies', async (req, next) => {
let dependencies = await next()
const services = xsenv.getServices({
registry: { tag: 'SaaS' },
destination: { tag: 'destination' }
})
dependencies.push({ xsappname: services.destination.xsappname }) //adds the subscriber destination as dependency
console.log(">>> SaaS Dependencies:", JSON.stringify(dependencies))
return dependencies
})
}
It injects the destination dependency by manually reading it using xsenv package and returning it in a dependencies callback handler.
[BTP] Create sap-successfactors-extensibility service providing a technicalUser
Prerequisite, you have registered an SAP SuccessFactors system in your Global Account (see here). Creating the sap-successfactors-extensibility service can be done via command line:
#Created the service instance
#An HTTP destination on a subaccount level with the same name as the service instance name is automatically generated
cf create-service sap-successfactors-extensibility api-access myInstanceName -c '{"systemName": "SFCPART000000","technicalUser": "sfadmin"}'
#Bind the instance to an application
cf bind-service myApp-srv myInstanceName
Find an explanation of the parameters here: https://help.sap.com/docs/btp/sap-business-technology-platform/authentication-type-json-file
This service instance will result in creating:
- a separate OAuth2 client application on SFSF side (can find in SF in Manage OAuth2 Client Applications)
- a separate destination definition on a BTP sub-account level
The technicalUser parameter can be specified only during creation. There is no possibility to provide it afterwards using cf update-service
. It may be possible to manually update the technicalUser in the destination, which got automatically created. But I did not test this yet.
Of course, the same service creation can also be done via mta.yaml.
resources:
#####################################################################################################################
# SuccessFactors Extensibility Service
#####################################################################################################################
- name: myInstanceName
type: org.cloudfoundry.managed-service
#type: org.cloudfoundry.existing-service
parameters:
service: sap-successfactors-extensibility
service-plan: api-access
config:
systemName: SFCPART000000 # <-- Provide your system name
technicalUser: sfadmin
For initial deployment, you need the line type: org.cloudfoundry.managed-service
. For all further deployments, you have to comment that line out and comment in the next line type: org.cloudfoundry.existing-service
. Else you will receive an error. Read more about that behavior here:https://github.com/SAP-samples/successfactors-extension-calculate-employee-seniority/issues/2