Create a report with the result of the transports imported using SAP CTMS

If you want to run your operations smoothly with SAP Integration Suite and avoid the wild west of manual export and imports in different environments you have few options to choose from top of my head:

  1. Implement CI/CD pipelines using SAP tools, 3rd party tools or Open Source tools developed by the community like FlashPipe or CPILint to automate governance.
  2. Use tools from SAP Partners like figaf to do the heavy lifting besides many other stuff. 
  3. Implement SAP Cloud Transport Management Services (SAP CTMS) to manage the transports which you can even control from Solution Manager (and probably also Cloud ALM)

I wanted to focus on the third scenario, but not how to do the initial implementation. Instead, I would like to explore the requirement of creating a small report/summary of what was moved to production during the last release window as not everyone will have access to SAP CTMS to check by themselves, as it was the case with the access to the STMS in the On-Prem systems.

The idea is to consume the available APIs from SAP CTMS to get the list of transports and their logs and send the information to the relevant audience via email in the less ugly format possible.

First step, as always these days, is to go to the SAP Business Accelerator Hub and find out what is available for SAP CTMS.

fjaviergar07_0-1738074504166.png

Unfortunately there is no OData APIs, only REST, but of course this will not stop us. Looking more into the details, I can imagine the initial scope of the APIs were to trigger the import of TRs externally as most of the CRUD operations are “POST” and “DELETE” and not to use it to build a report as we are trying to achieve. 

Anyways, we thankfully have few GET operations available:

  1. Get the list of Transport Requests
    fjaviergar07_3-1738075130901.png
  2. Get the logs for the Transport Requests

fjaviergar07_2-1738075073363.png

We can even do the initial testing directly from Business Accelerator Hub as soon as we know a couple of details:

  1. The NodeID from SAP CTMS. In our case we only want to get the report of the import to Production so we will need the ID of just that node.
  2. BTP Region and Subdomain where SAP CTMS is deployed.
  3. We need as well to create a Service key in BTP to get the Cliend ID and Client Secret.

The Try Out mechanism will help us to know what is the url we need to use when building the whole process in SAP Integration Suite.

But first, let’s dive in the two operations I mentioned above:

  1. Get the list of Transport Requests
    fjaviergar07_4-1738076037036.png

    NodeID -> Makes sense, we have it.

    Status -> Right, we want to know what happened with the TRs. We are not interested in the TRs that have not been imported (initial) or the ones that were already deleted, but I would rather prefer to not select any status but filter by date. However, what are the dates we can use?
           Start/End date and time of transport request creation -> It could have been several months ago.
           Start/End date and time of queue entry creation -> The TR is added to the Production Node  automatically when it is moved to the QAS environment, which could have been also weeks or months ago, when the development was released for testing.

    In summary, we don’t have the way to fetch only the Transport Requests that were imported at a certain point of time into the Production Node. We need to extract EVERYTHING we have in the node based on the status and use the next operation to know when it was imported.

    The double whammy is that I could not make a single call to get all the TRs for the different statuses I am interested. I tried different ways to add an OR clause but all of them failed, so at the end I had to do one call for each status and then gather all the results:
    fjaviergar07_5-1738077125022.png
    Each record will look like this:
    fjaviergar07_7-1738077460549.png

  2. Get the logs for the Transport Requests
    fjaviergar07_6-1738077296587.png

    Again we need to use the NodeID and the TransportID of each TransportRequest obtained before (XPATH: /*/*/*/*/id)

    When we fetch the logs of each TR we get lot of information for different actions:
    fjaviergar07_8-1738077633832.png

    We are only interested of what happened during the Import (Action Type = “I”) so we can check the status of this step only and when the action started:

    fjaviergar07_9-1738077898051.png

    We have all the information we need, but unfortunately for way too more TRs we wanted… and the problem is the list will increase and increase because we will have every release more “Succeeded” TRs.

If we kick the can down the road, we can discard the entries the TRs where the import happened before a certain threshold and only write the information for the ones of our last production release.

fjaviergar07_10-1738078169608.png

For the relevant entries we saved few details to build a csv: 

 

${property.transportRequestId},${property.descriptionFormatted},${property.ImportDate},${property.status}rn

 

We need to Gather all of them, add a Header:

 

TransportID,Description,ImportDate,Status
${in.body}

 

And finally, convert the CSV to HTML before sending it via email:

 

import com.sap.gateway.ip.core.customdev.util.Message;

def Message processData(Message message) {

// Get the CSV content from the message body
def csvContent = message.getBody(String).replaceAll(“succeeded”,”🟢”).replaceAll(“error”,”🔴”).replaceAll(“fatal”,”🔴”).replaceAll(“warning”,”⚠”)

// Initialize HTML content
def html = new StringBuilder()
html.append(“<html>n<head>n<title>BTP imports</title>n</head>n<body>n”)
html.append(“<table border=’1′ style=’border-collapse: collapse; width: 100%;’>n”)

// Convert CSV content to HTML table
csvContent.split(“n”).eachWithIndex { line, index ->
def cells = line.split(“,”) // Split by comma
html.append(“<tr>n”)
cells.each { cell ->
if (index == 0) {
// First row as table headers
html.append(“<th style=’padding: 8px; text-align: left;’>${cell.trim()}</th>n”)
} else {
// Other rows as table data
html.append(“<td style=’padding: 8px; text-align: left;’>${cell.trim()}</td>n”)
}
}
html.append(“</tr>n”)
}

html.append(“</table>n</body>n</html>”)

// Set the HTML content as the message body
message.setBody(html.toString())

return message
}

 

Remember to select the Text/HTML in the Mail adapter:

fjaviergar07_11-1738078575899.png

And the result is:

fjaviergar07_12-1738078766143.png

Conclusions

The result is bittersweet.

On one hand, the solution is solving the requirement and to be honest, the response from the SAP CTMS API is quite good. We have in our node already a little bit more than 1200 TRs in different status than initial or deleted. So we are doing 7 calls to get the TRs and another >1200 to get all the logs before putting everything together and send the email. The complete process is taking around 5 minutes.

On the other hand, this solution is not sustainable as we will have more and more TRs so the process will take longer and longer. Therefore I don’t encourage anyone to implement this without knowing the implications. 

I hope changes to these APIs or the release of new ones can allow us a better way of getting the information in a smarter and efficient way. I used the feedback option in the SAP Business Accelerator Hub to ask for it but I don’t know if that is going to move the needle in any way. If #SAP is reading this, please give us a wink!
Is there an influence program for BTP in general so we can submit these kind of requests?

And last but not least, I would like to ask the SAP Integration Community if you have had a more clever idea to solve this or a similar situation. I am here to learn so I am looking forward to reading your comments!

Cheers

Javier

 

 

 

 

 

Scroll to Top