Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[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' }]
                })

[SAPUI5] Place fields horizontally opposite each other

Because I always forget how to place a field at the beginning of a line and one opposite at the end, I create this post…

The easiest way to do this is using a Flex Box and the property justifyContent like in this sample: https://sapui5.hana.ondemand.com/#/entity/sap.m.FlexBox/sample/sap.m.sample.FlexBoxOpposingAlignment

<FlexBox alignItems="Center" width="100%" justifyContent="SpaceBetween">
    <Text text="Text1" textAlign="Begin"/>
    <Text text="Text2" textAlign="End"/>
</FlexBox>

Another way is a using a Toolbar and a ToolbarSpacer. But of course a Toolbar should only be used, when it makes sense to use a Toolbar.

<Toolbar>
    <Text text="Text1" textAlign="Begin"/>
    <ToolbarSpacer/>
    <Text text="Text2" textAlign="End"/>
</Toolbar>

[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

https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations#local-environment-variable

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

[ABAP] Create ToC for a given transport, release it and download it as ZIP

*&---------------------------------------------------------------------*
*& Report Z_ZIP_TOC
*&---------------------------------------------------------------------*
*& For the givin transport, this report creates a transport of copies (ToC),
*& releases it, and then download the ToC as ZIP file to your filesystem.
*& The given request can be in sate unreleased!
*&---------------------------------------------------------------------*
REPORT Z_ZIP_TOC.

INITIALIZATION.

  SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
  PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
  PARAMETERS p_ttext  TYPE as4text.
  SELECTION-SCREEN END OF BLOCK bl01.

  SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
  PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
  PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
  SELECTION-SCREEN END OF BLOCK bl02.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_trkorr.
  CALL FUNCTION 'TR_F4_REQUESTS'
    EXPORTING
      iv_trkorr_pattern   = p_trkorr
    IMPORTING
      ev_selected_request = p_trkorr.

START-OF-SELECTION.

  " Read description of provided transport
  DATA ls_request TYPE trwbo_request.
  CALL FUNCTION 'TR_READ_REQUEST'
    EXPORTING
      iv_read_e07t     = 'X'
      iv_trkorr        = p_trkorr
    CHANGING
      cs_request       = ls_request
    EXCEPTIONS
      error_occured    = 1
      no_authorization = 2
      OTHERS           = 3.
  IF sy-subrc <> 0.
    MESSAGE 'Could not read Transport description' TYPE 'E'.
  ENDIF.


  " Read all objects for provided transport
  DATA: lt_objects TYPE  tr_objects,
        lt_keys    TYPE  tr_keys.
  CALL FUNCTION 'TR_GET_OBJECTS_OF_REQ_AN_TASKS'
    EXPORTING
      is_request_header      = VALUE trwbo_request_header( trkorr = p_trkorr )
      iv_condense_objectlist = 'X'
    IMPORTING
      et_objects             = lt_objects
      et_keys                = lt_keys
    EXCEPTIONS
      invalid_input          = 1
      OTHERS                 = 2.
  IF sy-subrc <> 0.
    MESSAGE 'Could not read Transport objects' TYPE 'E'.
  ENDIF.


  " Create new ToC
  DATA ls_request_header TYPE trwbo_request_header.
  CALL FUNCTION 'TR_INSERT_REQUEST_WITH_TASKS'
    EXPORTING
      iv_type           = 'T'
      iv_text           = COND as4text( WHEN p_ttext IS INITIAL THEN ls_request-h-as4text
                                                                ELSE p_ttext )
      iv_owner          = sy-uname
      iv_target         = 'DUM'
    IMPORTING
      es_request_header = ls_request_header
    EXCEPTIONS
      insert_failed     = 1
      enqueue_failed    = 2
      OTHERS            = 3.
  IF sy-subrc <> 0.
    MESSAGE 'Transport creation failed' TYPE 'E'.
  ENDIF.
  DATA(lv_trkorr_toc) = ls_request_header-trkorr.


  " Add all object to the new ToC
  CALL FUNCTION 'TRINT_APPEND_COMM'
    EXPORTING
      wi_exclusive       = 'X'
      wi_sel_e071        = 'X'
      wi_sel_e071k       = 'X'
      wi_trkorr          = lv_trkorr_toc
    TABLES
      wt_e071            = lt_objects
      wt_e071k           = lt_keys
    EXCEPTIONS
      e071k_append_error = 1
      e071_append_error  = 2
      trkorr_empty       = 3
      OTHERS             = 4.
  IF sy-subrc <> 0.
    MESSAGE 'Could not append objects to ToC' TYPE 'E'.
  ENDIF.


  " Release ToC Transport
  DATA lt_messages TYPE ctsgerrmsgs.
  CALL FUNCTION 'TRINT_RELEASE_REQUEST'
    EXPORTING
      iv_trkorr                   = lv_trkorr_toc
*     IV_DIALOG                   = 'X'
*     IV_AS_BACKGROUND_JOB        = ' '
*     IV_SUCCESS_MESSAGE          = 'X'
*     IV_WITHOUT_OBJECTS_CHECK    = ' '
*     IV_CALLED_BY_ADT            = ' '
*     IV_CALLED_BY_PERFORCE       = ' '
*     IV_WITHOUT_DOCU             = ' '
      iv_without_locking          = 'X'
*     IV_DISPLAY_EXPORT_LOG       = 'X'
*     IV_IGNORE_WARNINGS          = ' '
*     IV_SIMULATION               = ' '
    IMPORTING
      et_messages                 = lt_messages
    EXCEPTIONS
      cts_initialization_failure  = 1
      enqueue_failed              = 2
      no_authorization            = 3
      invalid_request             = 4
      request_already_released    = 5
      repeat_too_early            = 6
      object_lock_error           = 7
      object_check_error          = 8
      docu_missing                = 9
      db_access_error             = 10
      action_aborted_by_user      = 11
      export_failed               = 12
      execute_objects_check       = 13
      release_in_bg_mode          = 14
      release_in_bg_mode_w_objchk = 15
      error_in_export_methods     = 16
      object_lang_error           = 17
      OTHERS                      = 18.
  IF sy-subrc <> 0.
    MESSAGE 'Could not release ToC' TYPE 'E'.
    cl_demo_output=>display( lt_messages ).
  ENDIF.


  " Download released ToC as ZIP
  DATA lv_xcontent_k TYPE xstring.
  DATA lv_xcontent_r TYPE xstring.

  DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.
  DATA(lv_transdir_r) = |{ p_sapdir }data/R{    lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.


  TRY.

      " K
      OPEN  DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_k INTO lv_xcontent_k.
      CLOSE DATASET lv_transdir_k.

      " R
      OPEN  DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_r INTO lv_xcontent_r.
      CLOSE DATASET lv_transdir_r.


      " Add to ZIP
      DATA(lo_zipper) = NEW cl_abap_zip( ).

      lo_zipper->add( name    = |K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
                      content = lv_xcontent_k ).

      lo_zipper->add( name    = |R{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
                      content = lv_xcontent_r ).


      " Download ZIP
      DATA(lv_xzip) = lo_zipper->save( ).

      " Convert to raw data
      DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).

      " Set zip filename
      DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ lv_trkorr_toc }|
                                                          ELSE |{ p_ttext              }_{ lv_trkorr_toc }| ).

      " Replace every character that is not [a-zA-Z0-9_] with '_'.
      REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.

      cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
                                                        filetype = 'BIN'
                                              CHANGING  data_tab = lt_data ).

    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'E'.
  ENDTRY.

  MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.

[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: herehere 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

[ABAP] ALV traffic lights

TYPES: BEGIN OF ty_log,
         status TYPE icon-id,
       END OF ty_log.

DATA gt_log TYPE TABLE OF ty_log.

gt_log = VALUE #( ( status = icon_red_light    )
                  ( status = icon_yellow_light )
                  ( status = icon_green_light  ) ).

TRY.
    cl_salv_table=>factory( IMPORTING r_salv_table = DATA(alv_table)
                            CHANGING  t_table      = gt_log ).

    alv_table->display( ).

  CATCH cx_salv_msg.
ENDTRY.

icon_red, icon_yellow etc. will be automatically loaded from the TYPE-POOL: icon during runtime.

You can check all available icons via the table icon.

SELECT SINGLE id INTO @DATA(icon_red) FROM icon WHERE name = 'ICON_RED_LIGHT'.