CX Works

A single portal for curated, field-tested and SAP-verified expertise for your SAP C/4HANA suite. Whether it's a new implementation, adding new features, or getting additional value from an existing deployment, get it here, at CX Works.

External Campaigns in SAP Marketing Cloud Using Khoros (3/4)

A how-to on executing external campaigns in SAP Marketing Cloud using Khoros | Part 3: SAP Cloud Platform Integration Configurations

This is the third article of this series to explain how external campaign can be created in SAP Marketing Cloud for the social media management platform - Khoros. In this article, SAP Cloud Platform Integration (CPI) configurations will be explained. To better understand the external campaign use case using SAP Marketing Cloud and Khoros, please visit Part 1 of this series. The functional testing steps will be discussed in the final article in this series.


The scenario described in this article was built using SAP Marketing Cloud 1811 release, SAP Cloud Platform Integration(CPI) build number 2.46.16 and Khoros Conversations V2 API.

Table of Contents

SAP Cloud Platform Integration Configurations

Since SAP Cloud Platform integration (CPI) will be used as middleware for external campaign execution in Khoros using SAP Marketing Cloud, the below steps need to be followed in SAP CPI:

  • Log on to your SAP Cloud Platform cockpit to create a technical user on the SAP Cloud Platform cockpit.
  • Log on to SAP Cloud Platform Integration and navigate to Design.
  • Create a new Integration Package.
  • Create, design and configure a new iFlow.
  • Deploy the iFlow.

In the following section, we will focus on the custom integration flows (iFlow) which are required for this use case to execute external campaigns in SAP Marketing Cloud using Khoros. We recommend to visit the following link to better understand the basic concepts of SAP CPI: https://help.sap.com/viewer/product/CLOUD_INTEGRATION/Cloud/en-US.

Initially, two iFlows need to be developed to read the campaign parameters and to assign the Khoros plans to SAP Marketing Cloud campaign as an external campaign.

Integration Flow to Read Campaign Parameters

This integration flow has been created to read the campaign parameters from Khoros and to set the campaign parameter values to the SAP Marketing Cloud entity set, CampaignParameterSet. Since this iFlow end-point was configured in the communication arrangement in SAP Marketing Cloud and created an external campaign with category Khoros Campaign - Test, this iFlow will be triggered.



In this section, we will elaborate further on the design and configuration of each step of this integration flow.

Step 1: Create a sender HTTPS request from SAP Marketing Cloud to SAP CPI. 



The address of the integration flow should be in the format /<your_service>/<your_entity>. To request campaign parameters from Khoros, the path should end with CampaignParameterSet entity set.


Step 2: Add the authorization content modifier.



A parameter Authorization is created at the message header and the oAuth token received from Khoros (see the Khoros Configurations article) is provided as the value.


Step 3: Create an HTTP request to Khoros API.



This call is made to the Khoros API to read all the initiatives.


Step 4: Add the Root content modifier.



The Root is added to the message body of the JSON response from Khoros as it is needed to convert the JSON message to the XML format.


Step 5: Add the JSON to the XML converter.



Since the Message Mapping expects an XML formatted file, the Khoros JSON response needs to be converted to XML.


Step 6: Create the Initiative_To_Campaign_Param message mapping.



The response of the Khoros Initiative API needs to be mapped with the SAP Marketing Cloud CampaignParameterSet entity set. Due to the differences in the message structures between source and target structures, a sample mapping has been done. Details of this message mapping could be checked in the iFlow content. Also, the XSD files which are required for both source and target structures can be found in the iFlow content.


Step 7: Add the XML to the JSON converter.



Finally, the format of the message content needs to be converted to JSON as SAP Marketing Cloud expects the response in JSON format.

Integration Flow to Assign Khoros Plans as External Campaigns in SAP Marketing Cloud

This integration flow has been created to read the plans from Khoros to the chosen initiatives in SAP Marketing Cloud. Fetched plans from Khoros will be set to the SAP Marketing Cloud entity set CampaignSet. One of these fetched plans could be linked with SAP Marketing Cloud campaigns. As this iFlow end-point has been configured in the communication arrangement in SAP Marketing Cloud for value help for campaign assignment, at the time of selecting the value help of campaign ID of an external campaign with category Khoros Campaign - Test, this iFlow will be triggered.



In this section, we will elaborate further on the design and configuration of each step of this integration flow.

Step 1: Create a sender HTTPS request from SAP Marketing Cloud to SAP CPI. 



The address of the integration flow should be in the format /<your_service>/<your_entity>. To request plans from Khoros, the path should end with CampaignSet entity set.


Step 2: Add the InitiativeID content modifier.



A parameter InitiativeID is created at the message header which will hold the value of the selected initiative in SAP Marketing Cloud.


Step 3: Parse the InitiativeID in the Groovy script file.


Parse Initiative
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
    //Body 
       def body = message.getBody();
        //Headers 
       def map = message.getHeaders();
       def value = map.get("CamelHttpQuery");
       message.setHeader("CamelHttpQuery", "");
       String result = java.net.URLDecoder.decode(value, "UTF-8");  
       def textRegex = "\\(([^)]+)\\)";
       def textMatcher = ( result =~ textRegex );
       def values = textMatcher[1][1].split(' eq ');
       def value1 = values[1].replaceAll("'","");
       message.setHeader("InitiativeID", value1);
      return message;
}

The selected initiative value will be passed to CPI in the CamelHttpQuery parameter. Note: It is required to parse the query string to get the value of the selected initiative.


Step 4: Add the authorization content modifier.



A parameter Authorization is created at the message header and the oAuth token received from Khoros (see Khoros Configurations article) is provided as the value.


Step 5: Create a HTTP request to Khoros's API.



This call is made to the Khoros API to read all the plans corresponding to the selected initiative in SAP Marketing Cloud.


Step 6: Add the Root content modifier.


The Root is added to the message body of the JSON response from Khoros as it is required to convert the JSON message to XML format.


Step 7: Add the JSON to the XML converter.


Since the Message Mapping expects an XML formatted file, the Khoros JSON response must be converted to XML.


Step 8: Create the Map_Plan_to_campaign message mapping.


The response of the Khoros Plans API needs to be mapped with the SAP Marketing Cloud CampaignSet entity set. Due to the differences in the message structures between source and target structures, a sample mapping has been done. Details of this message mapping could be checked in the iFlow content. Also, the XSD files which are required for both source and target structures can be found in the iFlow content.


Step 9: Add the Groovy script to adjust the campaign ID.


Adjust Campaign ID
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import groovy.xml.*;
def Message processData(Message message) {
    //Body 
       def body = message.getBody(java.lang.String) as String;
       def xml = new XmlParser().parseText( body );
       xml.'**'.findAll{it.name() == 'ServerCampaignId'}.each
       {
         String updatedvalue = it.text().replaceAll("-","").toString();
         it.value = updatedvalue;
       }
       message.setBody(groovy.xml.XmlUtil.serialize(xml));
       return message;
}

This is required to adjust the campaign ID by removing '-' from the ID value.


Step 10: Add the XML to the JSON converter.



Finally, the format of the message content needs to be converted to JSON as SAP Marketing Cloud expects the response in JSON format.


Integration Flow to Read Marketing Success Data

Another integration flow has been created to retrieve the campaign success data from Khoros. SAP Marketing Cloud makes a periodic request for success data. In this case, SAP Marketing Cloud calls the GetEntitySet method of the MarketingSuccessSet entity set. The Khoros system will asynchronously respond with a ReportID. If a ReportID is received by SAP Marketing Cloud, a second GET request will be sent with a ReportID to read the campaign success data from Khoros. This process repeats every four hours.


In this section, we will elaborate further on the design and configuration of each step of this integration flow.

Step 1: Create a sender HTTPS request from SAP Marketing Cloud to SAP CPI. 



The corresponding configurations are maintained:



The address of the integration flow should be in the format /<your_service>/<your_entity>. To request success data from Khoros, the path should end with MarketingSuccessSet entity set.


Step 2: Add the required parameters in the Header table of the Content Modifier.



All the required parameters are maintained:



These parameters will be passed to SAP CPI in CamelHttpQuery parameter. It is required to parse the query string to get the value of the selected initiative.


Step 3: Parse the parameters in the Groovy script file.


The Groovy script code is written to parse the parameter value which is coming from SAP Marketing Cloud:


Parse Parameters
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
    //Body 
       def body = message.getBody();
        //Headers 
       def map = message.getHeaders();
       def value = map.get("CamelHttpQuery");
       message.setHeader("CamelHttpQuery", "");
       String result = java.net.URLDecoder.decode(value, "UTF-8");  
       def textRegex = "\\(([^)]+)\\)";
       def textMatcher = ( result =~ textRegex );
       def values = textMatcher[0][1].split(' eq ');
       def value2 = values[1].replaceAll("'","");  
       message.setHeader("CampaignID", value2);
       if(result.contains("ReportId")){
       def sub = { it.split("ReportId eq ")[1] };
       def report_id = sub(result);
       def str = report_id.split('&');
       def value3 = str[0].replaceAll("'",""); 
       def value4 = value3.trim();
       message.setHeader("ReportID", value4);
       }
       def values1 = textMatcher[1][1].split(' eq ');
       def value4 = values1[2].replaceAll("'",""); 
       message.setHeader("InitiativeID", value4);
      return message;
}


Based on a value of the ReportID parameter, a Router element in the flow is used for routing messages.


Case 1: If the ReportID is not populated, the flow will go to Route 2 to get the report ID for the POST exports based on the defined fields required from the Khoros system.


Step 1.1: In this step, the Content Modifier sets a payload in the Message Body for the following API call. The response to this call includes an ExportID value which is stored as a ReportID in SAP Marketing Cloud. To understand more about how we can build the required message body in this step, please check the standard Khoros API document.



Below is the message body used for our use case:

{
"query": {
"start": "2018-01-01",
"stop": "2019-12-31",
"fields": [
283,232,282,48,51,52,53,54,134,133,136,135,132,141,142,143,144
]
}
}


Step 1.2: In this step, there will be an HTTP call to the Khoros's system to receive the corresponding ExportID (based on the message body set from the previous step).



Step 1.3: Add the Root content modifier.



The Root is added to the message body of the JSON response from Khoros as it is required to convert the JSON message to XML format.


Step 1.4: Add the JSON to the XML converter.



Since the Message Mapping expects an XML formatted file, the Khoros JSON response must be converted to XML.


Step 1.5: Add the campaign ID in the XML by Groovy script file.



With the existing message body, a campaign ID is also incorporated in the XML body so that it can be used to send the ReportID to SAP Marketing Cloud.


Set Campaign ID
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
     //Headers 
     def map = message.getHeaders();
     def value = map.get("CampaignID");
     def toAdd = "<CampaignID>"+value+"</CampaignID>";
    //Body 
    def body = message.getBody();
    def inBody = body.toString();
    def root = new XmlSlurper(false, true).parse(body);
    def fragmentToAdd = new XmlSlurper(false, true).parseText(toAdd);
    root.appendNode(fragmentToAdd);
    String outxml = groovy.xml.XmlUtil.serialize(root);
    message.setBody(outxml);
    return message;
}


Step 1.6: Create the ReportID_to_External_jobID message mapping.




The message consisting of the ReportID and the campaign ID needs to be mapped with SAP Marketing Cloud MarketingSuccessSet entity set. Due to the differences in the message structures between source and target structures, a sample mapping has been done. Details of this message mapping could be checked in the iFlow content. Also, XSD files which are required for both source and target structures can be found in the iFlow content.


Step 1.7: Add the XML to the JSON converter.



Finally, the format of the message content needs to be converted to JSON as SAP Marketing Cloud expects the response in JSON format.


Case 2: If the ReportID is populated, the flow will go to Route 3 in order to get campaign success data.

Once a ReportID is received and stored, a second GET request including a ReportID will be sent. In this flow, the campaign success data will be fetched corresponding with the stored ReportID.



Step 2.1: Create a  HTTP request to Khoros's API.



This call is made to the Khoros API to read the plans corresponding to the selected initiative in SAP Marketing Cloud. These plans are equal to the campaigns in SAP Marketing Cloud.


Step 2.2: Add the Root content modifier.



The Root is added to the message body of the JSON response from Khoros as it is required to convert the JSON message to XML format.


Step 2.3: Add the JSON to the XML converter.



Since the Message Mapping expects XML formatted file, the Khoros JSON response must be converted to XML.


Step 2.4: Add the  Groovy Script to adjust the campaign ID.



The Groovy script AdjustCamp.groovy is implemented to remove the ‘-’ from the campaign ID which is received from Khoros. Also, the header parameter ‘CampaignName’ was set with the plan name received from Khoros.


Adjust Campaign ID
import groovy.xml.*
import groovy.json.*
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
       def body = message.getBody(java.lang.String) as String;
       def mProp = message.getHeaders();
       def id_campaign = mProp.get('CampaignID');
       def nm_campaign = '';
       def xml = new XmlParser().parseText( body );
       xml.'**'.findAll{it.name() == 'id'}.each
       {
         String updatedvalue = it.text().replaceAll("-","").toString();
         it.value = updatedvalue;
         /*
         if (updatedvalue == id_campaign) {
             message.setHeader("CampaignName",it.name);
         }
         */
       }
       xml.data.each{a->
            if (a.id.text() == id_campaign) {
                message.setHeader("CampaignName",a.name.text());    
            }
        }
       message.setBody(groovy.xml.XmlUtil.serialize(xml));
       return message;
}


Step 2.5:  Filter the XML data based on the campaign ID.



The following XPath expression data[id/text() = $CampaignID] is used to select a node for a CampaignID from the XML document. It is used to filter the relevant XML nodes with Plan data received from Khoros based on the campaign ID received from SAP Marketing Cloud.


Step 2.6: Add the  Groovy script to generate the URL.



In this step of the message processing, the Groovy script creates a URL to download a file containing the campaign performance data from Amazon Web Services S3.


Prepare URL
import groovy.xml.*
import groovy.json.*
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
    def body = message.getBody(java.lang.String)
    def messageLog = messageLogFactory.getMessageLog(message);
    def mHead = message.getHeaders();
	def exportid = mHead.get("ReportId");
	def idValue = exportid.toString();
    message.setHeader("export_url", "https://api.spredfast.com/v2/analytics/export/" + exportid + "/status");
    def root = new XmlSlurper().parseText(body);
    def field1 = root.id.text();
    message.setHeader("url_plan_name", field1);
    if(messageLog != null){
        //def mProp = message.getProperties();
	    messageLog.addAttachmentAsString("idValue", idValue, "text/plain");
	    messageLog.addAttachmentAsString("export_url", exportid.getClass().toString() , "text/plain");
    }
    message.setBody("");
    return message;
}


Step 2.7: Add the looping process call.

In this step, a local integration process is called to retrieve the status of the file that has been generated by Khoros from step 1.2. This local process will execute in a loop until the retrieved status of the file is “Completed” or the number of interaction cycles is greater or equal to 1000.



Step 2.7.1: The local integration process consists of two steps:

  • A request call is made to Amazon S2 Service in order to get the status of the file. 

  • The response from Amazon S3 is parsed by the Groovy script. If the status is "Completed", the script will parse the response to get the URL of the file.

    Parse URL
    import com.sap.gateway.ip.core.customdev.util.Message;
    import java.util.HashMap;
    import java.net.*
    import groovy.json.*
    static def parseQueryString(String string) {
        string.split('&').collectEntries{ param ->
            param.split('=', 2).collect{ URLDecoder.decode(it, 'UTF-8') }
        }
    }
    static def parseUri(String uri) {
        def parsedUri
        try {
            parsedUri = new URI(uri)
            if (parsedUri.scheme == 'mailto') {
                def schemeSpecificPartList = parsedUri.schemeSpecificPart.split('\\?', 2)
                def tempMailMap = parseQueryString(schemeSpecificPartList[1])
                parsedUri.metaClass.mailMap = [
                        recipient: schemeSpecificPartList[0],
                        cc: tempMailMap.find{it.key.toLowerCase() == 'cc'}.value,
                        bcc: tempMailMap.find{it.key.toLowerCase() == 'bcc'}.value,
                        subject: tempMailMap.find{it.key.toLowerCase() == 'subject'}.value,
                        body: tempMailMap.find{it.key.toLowerCase() == 'body'}.value
                ]
            }
            if (parsedUri.fragment?.contains('?')) { // handle both fragment and query string
                parsedUri.metaClass.rawQuery = parsedUri.rawFragment.split('\\?')[1]
                parsedUri.metaClass.query = parsedUri.fragment.split('\\?')[1]
                parsedUri.metaClass.rawFragment = parsedUri.rawFragment.split('\\?')[0]
                parsedUri.metaClass.fragment = parsedUri.fragment.split('\\?')[0]
            }
            if (parsedUri.rawQuery) {
                parsedUri.metaClass.queryMap = parseQueryString(parsedUri.rawQuery)
            } else {
                parsedUri.metaClass.queryMap = null
            }
            if (parsedUri.queryMap) {
                parsedUri.queryMap.keySet().each { key ->
                    def value = parsedUri.queryMap[key]
                    if (value.startsWith('http') || value.startsWith('/')) {
                        parsedUri.queryMap[key] = parseUri(value)
                    }
                }
            }
        } catch (e) {
            //assert false, "Parsing of URI failed: $uri\n$e"
        }
        parsedUri
    }
    def Message processData(Message message) {
        def messageLog = messageLogFactory.getMessageLog(message);
        def body = message.getBody(java.lang.String) as String;
        def jsonSlurper = new JsonSlurper();
        def jsonDataObject = jsonSlurper.parseText(body);
        //def messageLog = messageLogFactory.getMessageLog(message);
        def s_status_id = jsonDataObject.status;
        message.setHeader("status_id",s_status_id);
        message.setHeader("url_address","");
        if (s_status_id == "complete"){
            if(messageLog != null){
                messageLog.addAttachmentAsString("test 1", "1", "text/plain");
            }
            def s_url = jsonDataObject.url;
            def parsedUri = parseUri(s_url)
            def tmp_path = parsedUri.path
            if(messageLog != null){
                messageLog.addAttachmentAsString("tmp_path", tmp_path, "text/plain");
            }
            def t_path = tmp_path.replaceAll('\\+',"%2B")
            message.setHeader("url_path",t_path);
            if(messageLog != null){
                messageLog.addAttachmentAsString("t_path", t_path, "text/plain");
            }
            def t_host = parsedUri.host
            message.setHeader("url_host",t_host);
            def t_url = 'https://' + t_host + t_path
            message.setHeader("url_address",t_url);
            if(messageLog != null){
                messageLog.addAttachmentAsString("test 2", "2", "text/plain");
            }
            def tmp_query = parsedUri.getRawQuery();
            def s_query = tmp_query.replaceAll('\\+',"%2B")
            message.setHeader("url_query", s_query);
            if(messageLog != null){
                messageLog.addAttachmentAsString("getRawQuery", tmp_query, "text/plain");
                messageLog.addAttachmentAsString("url_query", s_query, "text/plain");
            }
        }
        return message;
    }
    

Step 2.8: Add the Groovy Script to read the data from Amazon S3.

 The following script makes a call to Amazon S3 for the campaign success data file. When the response code is 200, the script writes the response payload into the message body. 



Success Data Call
import groovy.json.*
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.*;
static def parseQueryString(String string) {
    string.split('&').collectEntries{ param ->
        param.split('=', 2).collect{ URLDecoder.decode(it, 'UTF-8') }
    }
}
static def parseUri(String uri) {
    def parsedUri
    try {
        parsedUri = new URI(uri)
        if (parsedUri.scheme == 'mailto') {
            def schemeSpecificPartList = parsedUri.schemeSpecificPart.split('\\?', 2)
            def tempMailMap = parseQueryString(schemeSpecificPartList[1])
            parsedUri.metaClass.mailMap = [
                    recipient: schemeSpecificPartList[0],
                    cc: tempMailMap.find{it.key.toLowerCase() == 'cc'}.value,
                    bcc: tempMailMap.find{it.key.toLowerCase() == 'bcc'}.value,
                    subject: tempMailMap.find{it.key.toLowerCase() == 'subject'}.value,
                    body: tempMailMap.find{it.key.toLowerCase() == 'body'}.value
            ]
        }
        if (parsedUri.fragment?.contains('?')) { // handle both fragment and query string
            parsedUri.metaClass.rawQuery = parsedUri.rawFragment.split('\\?')[1]
            parsedUri.metaClass.query = parsedUri.fragment.split('\\?')[1]
            parsedUri.metaClass.rawFragment = parsedUri.rawFragment.split('\\?')[0]
            parsedUri.metaClass.fragment = parsedUri.fragment.split('\\?')[0]
        }
        if (parsedUri.rawQuery) {
            parsedUri.metaClass.queryMap = parseQueryString(parsedUri.rawQuery)
        } else {
            parsedUri.metaClass.queryMap = null
        }
        if (parsedUri.queryMap) {
            parsedUri.queryMap.keySet().each { key ->
                def value = parsedUri.queryMap[key]
                if (value.startsWith('http') || value.startsWith('/')) {
                    parsedUri.queryMap[key] = parseUri(value)
                }
            }
        }
    } catch (e) {
        //assert false, "Parsing of URI failed: $uri\n$e"
    }
    parsedUri
}
def Message processData(Message message) {
    def body = message.getBody(java.lang.String)
    def mProp = message.getHeaders();
    JsonSlurper slurper = new JsonSlurper()
    def jsonDataObject = slurper.parseText(body);
    //def messageLog = messageLogFactory.getMessageLog(message);
    def s_status_id = jsonDataObject.status;
    message.setHeader("status_id",s_status_id);
    message.setHeader("url_address","");
	def messageLog = messageLogFactory.getMessageLog(message);
    messageLog.addAttachmentAsString("s_status_id", s_status_id , "text/plain");
	if (s_status_id == 'complete') {
        def s_url = jsonDataObject.url;
        def parsedUri = parseUri(s_url)
        def tmp_path = parsedUri.path
        def t_path = tmp_path.replaceAll('\\+',"%2B")
        message.setHeader("url_path",t_path);
        def t_host = parsedUri.host
        message.setHeader("url_host",t_host);
        def t_url = 'https://' + t_host + t_path
        message.setHeader("url_address",t_url);
        if(messageLog != null){
            messageLog.addAttachmentAsString("url_address", t_url, "text/plain");
        }
        def tmp_query = parsedUri.getRawQuery();
        def s_query = tmp_query.replaceAll('\\+',"%2B")
        message.setHeader("url_query", s_query);
        if(messageLog != null){
            messageLog.addAttachmentAsString("url_query", s_query, "text/plain");
            messageLog.addAttachmentAsString("full_url", t_url + "?" + s_query, "text/plain");
        }
        URL url = new URL(t_url + "?" + s_query);
        HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
        if (httpConnection.getResponseCode() == 200) {
            BufferedReader inp = new BufferedReader(new InputStreamReader(httpConnection.getInputStream(),"UTF-8"));
            String inputLine = '';
            def s_inputLine = ''
            //"Line 1" + System.getProperty("line.separator") + "Line 2"
            while ((inputLine = inp.readLine()) != null) {
                //s_inputLine += inputLine
                s_inputLine +=  inputLine + System.getProperty("line.separator");
            }
            messageLog.addAttachmentAsString("response code :", httpConnection.responseCode.toString() ,   "text/plain");
            messageLog.addAttachmentAsString("csv content  :", s_inputLine,   "text/plain");
            message.setBody(s_inputLine);
            inp.close();                    
            } else {
                messageLog.addAttachmentAsString("An error occurred: " + httpConnection.responseCode.toString()  + " "  + httpConnection.responseMessage, "text/plain");
            }
    } else {
        messageLog.addAttachmentAsString("s_status", "incomplete", "text/plain");
    }
    //message.setBody("");
    return message;  
}


Step 2.9: Convert the CSV data to XML.

The campaign success data retrieved in CSV format is converted into XML format. Because Khoros provides campaign success data for all the plans and not for a specific campaign, we need to filter out data for the required campaign name in XML format.



The XSD file post_engagement_1 is created based on the data structure of the campaign success data received from Khoros.


Step 2.10: Filter the campaign success data based on the Marketing campaign.



Khoros provides campaign success data for all the plans and not for a specific campaign. So, it is required to filter out data for the required campaign name in XML format.


Step 2.11: Add the Root content modifier.



The Root is added to the message body of the XML payload so that it could be converted to JSON format properly before sending the data to SAP Marketing Cloud.


Step 2.12: Add the Groovy script to aggregate the campaign success data.


 


Khoros will provide separate lines of data for each activity on a social medial channel for a Khoros Plan. Therefore, it is required to aggregate the success data from different channels based on the marketing campaign and the social media channel. (Note: Currently only LinkedIn and Twitter are maintained in the script. For other social media channels, similar code snippets need to be added. Also, only Post engagements and number of Posts are maintained in the script. It will need to be enhanced to incorporate other KPIs like Comments, Likes and more.). So, in this step, all Post engagements are being aggregated on a campaign and channel level.


Success Data Aggregation
import groovy.xml.*
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
    def body = message.getBody(java.lang.String) as String;
    def messageLog = messageLogFactory.getMessageLog(message);
    if(messageLog != null){
        messageLog.setStringProperty("Logging#1", "Printing Payload As Attachment")
        messageLog.addAttachmentAsString("ResponsePayload:", body, "text/plain");
        def worklogs = new XmlSlurper().parseText(body);
        def stringWriter = new StringWriter()
        def planBuilder = new MarkupBuilder(stringWriter)
        def t11=0;
        def t12=0;
        def mProp = message.getHeaders();
        def s_camp_id = mProp.get("CampaignID");
        def s_camp_name = mProp.get("CampaignName");
        //def with_kids=node.findAll {it.userId.unique()}
        def btNumbers = worklogs.row.collect{it.Channel+it.Plan}
        def countMap = btNumbers.unique(false).collectEntries{
            btNumber->[btNumber, btNumbers.count(btNumber)]
        }
        planBuilder.
        "root" {
            countMap.each {k,v ->
                worklogs.row.each {
                    node ->
                        if (k == (node.Channel+node.Plan)) {
                             le = node.Post_Engagement.text()
                            //t11 = t11 + st1.toInteger() 
                            t11 = t11 + Float.parseFloat(le)
                            le = node.Post_Engagement_Rate.text()
                            t12 = t12 + Float.parseFloat(le)
                            //t12 = t12 + le.toInteger()
                            channel = node.Channel
                            if(node.Channel == "Twitter"){
                                channel = "TW";
                            }
                            if(node.Channel == "Linkedin"){
                                channel = "LINKD";
                            }
                            plan = node.Plan
                        }
                }
                rowout {
                    //key(k)
                    key(k)
                    Plan(s_camp_id)
                    Channel(channel)
                    Posts(v)
                    PostEngagement(t11)
                    PostEngagementRate(t12)
                }
                t11 = 0
                t12 = 0
            }
        }
        def xml1 = stringWriter.toString()
        message.setBody(xml1);      
    }
    return message;
}      


Step 2.13: Create the message mapping for the Marketing campaign success data.



After the Post engagements data aggregation, the flow will execute a transformation message based on the following message mapping:



The following fields were mapped from source to target structure:


Source Structure Target Structure
Root Root
Rowout  Results
Plan ServerCampaignID
Channel CommunicationMedium
Posts DeliveredMessages
PostEngagement  PostEngagements


Step 2.14: Add the XML to the JSON converter.



The last step is to convert the aggregated campaign success data into JSON format so that it can be fed to the entity set MarketingSuccessSet of SAP Marketing Cloud.

Note that the Groovy script 3 has been implemented to log information which is an optional step. In a production scenario, we recommend to remove this logging step.






Conclusion

In this article, you learned how to build the integration flows for the external campaign scenario between SAP Marketing Cloud and Khoros. In Part 4 , you will learn about the steps to test the end-to-end use case.