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
Labels in this area