Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
DeepaliG
Explorer
While dealing with APIs in SAP API Management we come across the requirement to send alerts for API Failures. We can either go for Alert Notification Service available in SAP BTP or we can create an integration flow in SAP CPI which makes the process much easier and customized.

In this blog I will try to explain how one can Send SAP APIM Alerts via SAP CPI to MS Teams channel.

Integration Flow Design:


Below is the integration flow that needs to be developed in SAP CPI to meet the requirement. Please find the step wise explaination of each step below.

 


 

Step 1:


Set up below mentioned properties in first step to have the delta alerts only.

  1. 'sendToTeams' is a control parameter created to enable/disable the alerts when needed (used in Step 11).

  2. 'CurrentDateTime' property captures the TimeStamp when integration flow is run which can be defined using camel expression ${date:now:yyyy-MM-dd'T'00:00:00'Z'}

  3. 'lastSuccessfulRunDate' takes its value either from local varible defined in Step 3 or from the default value provided.




Step 2:


After Content Modifier, a looping process call is defined, which loops through the OData call until the condition "${property.SAP_APIM.Odata.hasMoreRecords} = 'true'" holds.

Condition Syntax: ${property.<receiver name>.<Channel Name>.hasMoreRecords}



 

Step 3:


A write variable 'LV_SAP_APIM_Alert' is defined locally which stores the TimeStamp 'CurrentDateTime' captured in the Step 1. The same write variable is called in Step 1 for property 'lastSuccessfulRunDate'.


 

Step 4:


Let's go through the Local Integration Process called by Step 2.

  1. API Portal - Analytics (CF) API is used to call the Analytics from API Portal to receive the logs. Basic/OAuth2 Client Credentials can be used for authentication purpose.Note: OData V4 adapter must be used to connect request reply with receiver.



 2. Please find the EDMX that can be imported manually if you are opting for OAuth2 Client Credentials.
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml">
<edmx:Include Alias="Capabilities" Namespace="Org.OData.Capabilities.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="AnalyticsService" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="ResultsUTC" EntityType="AnalyticsService.ResultsUTC"/>
<EntitySet Name="CustomMetrics" EntityType="AnalyticsService.CustomMetrics"/>
<EntitySet Name="Results" EntityType="AnalyticsService.ResultsParameters">
<NavigationPropertyBinding Path="Set/Parameters" Target="Results"/>
</EntitySet>
</EntityContainer>
<EntityType Name="ResultsUTC">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.String" MaxLength="40" Nullable="false"/>
<Property Name="ApiProxy" Type="Edm.String" MaxLength="1000"/>
<Property Name="ProxyBasepath" Type="Edm.String" MaxLength="1000"/>
<Property Name="RequestUrl" Type="Edm.String" MaxLength="1024"/>
<Property Name="RequestMethod" Type="Edm.String" MaxLength="1000"/>
<Property Name="ResponseCode" Type="Edm.Int32"/>
<Property Name="DeveloperName" Type="Edm.String" MaxLength="1000"/>
<Property Name="ApplicationName" Type="Edm.String" MaxLength="1000"/>
<Property Name="ProductName" Type="Edm.String" MaxLength="1000"/>
<Property Name="CacheHit" Type="Edm.Int64"/>
<Property Name="TargetHost" Type="Edm.String" MaxLength="1000"/>
<Property Name="TargetUrl" Type="Edm.String" MaxLength="1024"/>
<Property Name="PlatformName" Type="Edm.String" MaxLength="1000"/>
<Property Name="AgentsName" Type="Edm.String" MaxLength="1000"/>
<Property Name="DeviceType" Type="Edm.String" MaxLength="1000"/>
<Property Name="OsFamilyName" Type="Edm.String" MaxLength="1000"/>
<Property Name="SumResponseTime" Type="Edm.Double"/>
<Property Name="AvgResponseTime" Type="Edm.Double"/>
<Property Name="MaxResponseTime" Type="Edm.Double"/>
<Property Name="MinResponseTime" Type="Edm.Double"/>
<Property Name="SumErrors" Type="Edm.Int64"/>
<Property Name="AvgErrors" Type="Edm.Double"/>
<Property Name="SumTargetError" Type="Edm.Int64"/>
<Property Name="AvgTargetError" Type="Edm.Double"/>
<Property Name="SumPolicyError" Type="Edm.Int64"/>
<Property Name="AvgPolicyError" Type="Edm.Double"/>
<Property Name="SumTargetResponseTime" Type="Edm.Double"/>
<Property Name="AvgTargetResponseTime" Type="Edm.Double"/>
<Property Name="MaxTargetResponseTime" Type="Edm.Double"/>
<Property Name="MinTargetResponseTime" Type="Edm.Double"/>
<Property Name="SumRequestSize" Type="Edm.Int64"/>
<Property Name="AvgRequestSize" Type="Edm.Double"/>
<Property Name="MaxRequestSize" Type="Edm.Int64"/>
<Property Name="MinRequestSize" Type="Edm.Int64"/>
<Property Name="SumRequestProcessingLatency" Type="Edm.Double"/>
<Property Name="AvgRequestProcessingLatency" Type="Edm.Double"/>
<Property Name="MaxRequestProcessingLatency" Type="Edm.Double"/>
<Property Name="MinRequestProcessingLatency" Type="Edm.Double"/>
<Property Name="SumResponseProcessingLatency" Type="Edm.Double"/>
<Property Name="AvgResponseProcessingLatency" Type="Edm.Double"/>
<Property Name="MaxResponseProcessingLatency" Type="Edm.Double"/>
<Property Name="MinResponseProcessingLatency" Type="Edm.Double"/>
<Property Name="CreatedTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="testchart" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="apikey" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="contracttype" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="productid" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="Avgrequestheaders" Type="Edm.Int32"/>
<Property Name="Sumrequestheaders" Type="Edm.Int32"/>
<Property Name="Maxrequestheaders" Type="Edm.Int32"/>
<Property Name="Minrequestheaders" Type="Edm.Int32"/>
<Property Name="error_description" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="PolicyResponseTime" Type="Edm.Double"/>
<Property Name="CallCount" Type="Edm.Int64"/>
<Property Name="Year" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Month" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Day" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Week" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Hour" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Minute" Type="Edm.DateTimeOffset" Precision="7"/>
</EntityType>
<EntityType Name="CustomMetrics">
<Key>
<PropertyRef Name="id"/>
</Key>
<Property Name="id" Type="Edm.String" MaxLength="40" Nullable="false"/>
<Property Name="name" Type="Edm.String" MaxLength="1000"/>
<Property Name="type" Type="Edm.String" MaxLength="50"/>
<Property Name="metricsType" Type="Edm.String" MaxLength="15" DefaultValue="Not set"/>
<Property Name="markForDelete" Type="Edm.Boolean" DefaultValue="false"/>
<Property Name="markForDeleteDate" Type="Edm.String" MaxLength="50"/>
</EntityType>
<EntityType Name="ResultsParameters">
<Key>
<PropertyRef Name="TimeZone"/>
</Key>
<Property Name="TimeZone" Type="Edm.String" Nullable="false"/>
<NavigationProperty Name="Set" Type="Collection(AnalyticsService.ResultsType)" Partner="Parameters" ContainsTarget="true"/>
</EntityType>
<EntityType Name="ResultsType">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.String" MaxLength="40" Nullable="false"/>
<Property Name="ApiProxy" Type="Edm.String" MaxLength="1000"/>
<Property Name="ProxyBasepath" Type="Edm.String" MaxLength="1000"/>
<Property Name="RequestUrl" Type="Edm.String" MaxLength="1024"/>
<Property Name="RequestMethod" Type="Edm.String" MaxLength="1000"/>
<Property Name="ResponseCode" Type="Edm.Int32"/>
<Property Name="DeveloperName" Type="Edm.String" MaxLength="1000"/>
<Property Name="ApplicationName" Type="Edm.String" MaxLength="1000"/>
<Property Name="ProductName" Type="Edm.String" MaxLength="1000"/>
<Property Name="CacheHit" Type="Edm.Int64"/>
<Property Name="TargetHost" Type="Edm.String" MaxLength="1000"/>
<Property Name="TargetUrl" Type="Edm.String" MaxLength="1024"/>
<Property Name="PlatformName" Type="Edm.String" MaxLength="1000"/>
<Property Name="AgentsName" Type="Edm.String" MaxLength="1000"/>
<Property Name="DeviceType" Type="Edm.String" MaxLength="1000"/>
<Property Name="OsFamilyName" Type="Edm.String" MaxLength="1000"/>
<Property Name="CreatedTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="testchart" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="apikey" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="contracttype" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="productid" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="error_description" Type="Edm.String" DefaultValue="(not set)"/>
<Property Name="PolicyResponseTime" Type="Edm.Double"/>
<Property Name="ResponseTime" Type="Edm.Double"/>
<Property Name="TargetResponseTime" Type="Edm.Double"/>
<Property Name="RequestSize" Type="Edm.Double"/>
<Property Name="RequestProcessingLatency" Type="Edm.Double"/>
<Property Name="ResponseProcessingLatency" Type="Edm.Double"/>
<Property Name="TargetError" Type="Edm.Double"/>
<Property Name="Errors" Type="Edm.Double"/>
<Property Name="PolicyError" Type="Edm.Double"/>
<Property Name="CallCount" Type="Edm.Int64"/>
<Property Name="Year" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Month" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Day" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Week" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Hour" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="Minute" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="requestheaders" Type="Edm.Int32"/>
<NavigationProperty Name="Parameters" Type="AnalyticsService.ResultsParameters" Partner="Set"/>
</EntityType>
<Annotations Target="AnalyticsService.ResultsUTC/PolicyResponseTime">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Year">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Month">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Day">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Week">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Hour">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsUTC/Minute">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.EntityContainer/CustomMetrics">
<Annotation Term="Capabilities.InsertRestrictions">
<Record Type="Capabilities.InsertRestrictionsType">
<PropertyValue Property="Insertable" Bool="true"/>
</Record>
</Annotation>
<Annotation Term="Capabilities.UpdateRestrictions">
<Record Type="Capabilities.UpdateRestrictionsType">
<PropertyValue Property="Updatable" Bool="true"/>
</Record>
</Annotation>
<Annotation Term="Capabilities.DeleteRestrictions">
<Record Type="Capabilities.DeleteRestrictionsType">
<PropertyValue Property="Deletable" Bool="true"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="AnalyticsService.EntityContainer/Results">
<Annotation Term="Capabilities.NavigationRestrictions">
<Record Type="Capabilities.NavigationRestrictionsType">
<PropertyValue Property="RestrictedProperties">
<Collection>
<Record Type="Capabilities.NavigationPropertyRestriction">
<PropertyValue Property="NavigationProperty" NavigationPropertyPath="Set"/>
<PropertyValue Property="DeleteRestrictions">
<Record Type="Capabilities.DeleteRestrictionsType">
<PropertyValue Property="Deletable" Bool="false"/>
</Record>
</PropertyValue>
<PropertyValue Property="InsertRestrictions">
<Record Type="Capabilities.InsertRestrictionsType">
<PropertyValue Property="Insertable" Bool="false"/>
</Record>
</PropertyValue>
<PropertyValue Property="UpdateRestrictions">
<Record Type="Capabilities.UpdateRestrictionsType">
<PropertyValue Property="Updatable" Bool="false"/>
</Record>
</PropertyValue>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
<Annotations Target="AnalyticsService.ResultsParameters/TimeZone">
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/Mandatory"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/CreatedTime">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/PolicyResponseTime">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Year">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Month">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Day">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Week">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Hour">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
<Annotations Target="AnalyticsService.ResultsType/Minute">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

3. Query Options can be set as per the requirements.



 

Step 5:


Below written script can be used to remove '&' special character received in the response.
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message)
{
def body_xml= message.getBody(java.lang.String);
def input_xml=body_xml.replaceAll("&apos;","");
message.setBody(input_xml);
return message;
}

 

Step 6:


General Splitter is used to split each record of the response.


 

Step 7:


Capture the ID from the response in a property 'id' which can be used as a unique identifier to filter any duplicate record if received.


 

Step 8:


Idempotent process call is defined to Skip the duplicate records received. 'id' received in the response is unique for each message in API Portal.

Note: Though we have already applied lastSuccessfulRunDate concept for Delta load, 'Created Date'(used in the query filter) for each message is not precise up to minutes/seconds. Hence it is important to remove any duplicates if received using idempotent process call.


 

Step 9:


Inside the Local Integration Process, called by Step 8, an XSLT Mapping is defined to create the HTML body of the alert. Please find the used XSLT.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="html"
doctype-public="XSLT-compat"
omit-xml-declaration="yes"
encoding="UTF-8"
indent="yes"/>
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Api Proxy</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/ApiProxy"/>
</td>
</tr>
<tr>
<th>Response Code</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/ResponseCode"/>
</td>
</tr>
<tr>
<th>Time</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/Minute"/>
</td>
</tr>
<tr>
<th>Request Method</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/RequestMethod"/>
</td>
</tr>
<tr>
<th>Request URL</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/RequestUrl"/>
</td>
</tr>
<tr>
<th>Error Description</th>
<td>
<xsl:value-of select="ResultsUTC/ResultsUTC/Error_Description"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:transform>

 

Step 10:


To post the errors on the teams channel, create the header named 'Content-Type' with value 'application/json'.


Body for the same can be defined in the Message body as shown below.
{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "c70900",
"summary": "Production : SAP APIM Errors",
"sections": [
{
"activityTitle": "SAP APIM Erros",
"facts": [
{
"name": "Date and Time(UTC)",
"value": "${date:now:yyyy-MM-dd'T'hh:mm:ss'Z'}"
},
{
"name": "",
"value": '${in.body}'
},
{
"name": "Note:",
"value": "SAP APIM Errors"
}
],
"markdown": true
}
]
}

 

Step 11:


Router is used to either enable or disable the alerts to Teams channel. If downtime is known and one doesn't want the alerts to be sent, one can simply set the property as 'N' to have them disabled.


 

Step 12:


Steps to be configured in MS Teams:

  1. Go to MS Teams and click on 'Join or create team' if not already created.

  2. After creating the team as needed, Click on the options corresponding to that team and select add channel. (To be done only if channel is not created already)

  3. After creating the channel, Click on more options and choose 'connectors'

  4. Choose 'Incoming Webhook' and click Configure

  5. Provide a Suitable name and click on 'Create'

  6. Copy the Webhook URL generated and save.


The same webhook URL can be placed in HTTP Address as shown below:


 

Example Alert Generated in the channel:


 

Conclusion:


This is how SAP CPI Integration can be used to send SAP APIM Alerts to MS Teams. Alert can be enhanced by adding error description using blog.

 

Hope this blog helps! 😊 

Best Regards,

Deepali
Consultant, Capgemini India
8 Comments
mastanvalim
Participant
Hi Deepali,

nice blog, keep it up 🙂

 

Regards,

Mastan
raihan_siddiqui
Explorer
Great way for the team to get notified about technical details!

Thanks for sharing!
Jwan_
Explorer
0 Kudos
Hello Deepali,

After I have had problmes with the iFlow that you have developed (It was always asking for the access token). I built in a request to the token service url, where then the bearer token gets saved in a property and then delivered as a header to access the /api/1.0/AnalyticsService api.


Unfortunately, now I have the problem that the access is getting forbidden with the error 403

DeepaliG
Explorer
0 Kudos
Hi Jwan,

Thank you for reaching out!

You need to create the credentials of type OAuth2 client credentials as shown below and it will automatically take care of access token. You don't need to store the Access token in any header.

 


 

Hope this helps!

Best Regards,

Deepali
Jwan_
Explorer
0 Kudos
Hello dear Deepali,

 

thank you for your response. This is the error I am getting:

 


Best regards
Jwan
DeepaliG
Explorer
0 Kudos
Hi Jwan,

Did you try fetching access token via postman? Were you able to do that?

 

BR/Deepali
Jwan_
Explorer
0 Kudos
Hello Deepali,

 

yes, I get successfully the access token via the service token url with the same credentials I use in OAuth adapter in CPI


 



BR
Jwan

KhusalShah
Discoverer
0 Kudos
Hello Deepali,

 

Could you please help explain how you got the hostname for API-M Analytics for Cloud Foundry enviornment? Is it specific to tenant or particular region? and which Oauth Client Credentials are you using? I am trying the same from postman to fetch the metadata but unable to do so. I am using the process integration runtime instance with plan as api but unable to do so?

 

Thanks in Advance.
Labels in this area