Skip to Content
Technical Articles

Integrating Qualtrics with application on SAP Cloud Platform using Node.js

Hi fellow SCNers,

In this blog I will demonstrate how seamlessly we can integrate Qualtrics XM Platform with SAP Cloud Platform (SCP) application using Node.js and SAP Cloud Application Programming (CAP) Model.

Before the blog, I would like to thank my team mates along with whom I realized the same.

Thanks Mohak Bhatia and Aman Khanna.

Use Case –

Below solution intends to pull experience data from the Qualtrics platform and then write same in the HANA container available in the SCP Application. We have used as an example deals/opportunities business object. Once the experience data is available in HANA, it can be readily consumed by other SAP products (Such consumption is not in scope of this blog but can be presented later 😊).

I will break this in following steps –

  1. Qualtrics survey which captures the experience data.
  2. Creating the SCP Application which has the database, the OData Service and the API trigger code.

In detail,

  1. Qualtrics survey which captures the experience data.
  • Login to the Qualtrics XM Platform and create a survey choosing the CoreXM option or choose from any template.
  • Sample survey can look like –
  • Note the survey id encircled at the top.

2.Creating the SCP App which has the database, the OData Service and the API triggercode.

  • Open WebIDE and create an MTA Application. This demo is based on the development done using Cloud Application Programming Model – https://github.com/SAP-samples/cloud-cap-walkthroughs.
  • Final folder structure would be –
  • Go to DB module and create the database table –Code
    namespace sap.dealmakers;
    
    entity BidOutcomes {
        key bidId                                : UUID;
            reqUnderstoodRate                    : Integer;
            fitmentVsRequiredFeatureRatio        : Integer;
            identifiedServicesAndDeliveryOptions : String;
            solutionValidated                    : String;
            riskRedressalDone                    : String;
            clarificationAddressed               : String;
            pocRequested                         : String;
            bidChecklistFollowed                 : String;
            referencesPastProposals              : String;
            isCompetitorProductExpiring          : String;
            dealSize                             : String;
            discountSlabOffered                  : Integer;
            cLevelExecInMeeting                  : String; 
    }
    ​
  • Enable Web IDE extensions to allow working with the Database explorer and building of DB module as below –
  • Save and build the DB module –
  • Configure the Cloud foundry region so that the database artifacts are created appropriately –
  • Maintain below project settings for Cloud Foundry to avoid the error – Error –
  • Post successful build we can see tables for various entities in the HDI container. Choose the DB module and open the HDI container – Table structure –
  • Now we will create ODATA Service – Code –
    using sap.dealmakers from '../db/hdb';
    
    service DealsService {
        entity BidOutcomes as projection on dealmakers.BidOutcomes;
    }
    ​
  • Now, we need to create the Node.js module which will be used to fetch data from Qualtrics XM Platform and write same to the SCP HANA module of current application. Β Code –
    /*eslint no-console: 0*/
    // Get all the libraries required for processing
    const axios = require('axios');
    var fs = require('fs');
    var url = require('url');
    var http = require('http');
    
    var AdmZip = require('adm-zip');
    
    var request = require('request');
    
    // Pass your survey id
    var surveyid = 'SV_eDIL1bRzKlebXXX'
    
    // Pass your API token from your qualtrics account
    var headers = {
    	'X-API-TOKEN': 'dBCclMcOWOYCMgquJtcsplJIw59W6jy2lcjXXXXX',
    	'Content-Type': 'application/json'
    };
    
    // Declare the data format for processing
    var dataString = '{"format": "json" }';
    
    // Prepare the URL for the XM platform
    var options = {
    	url: 'https://xxxxx.qualtrics.com/API/v3/surveys/' + surveyid + '/export-responses',
    	method: 'POST',
    	headers: headers,
    	body: dataString
    };
    
    // Trigger call to fetch the data from Qualtrics platform
    function callback(error, response, body) {
    	if (!error && response.statusCode == 200) {
    
    		var options2 = {
    			url: 'https://xxxxx.qualtrics.com/API/v3/surveys/' + surveyid + '/export-responses/' + JSON.parse(response.body).result.progressId,
    			headers: headers
    		};
    
    		var intervalComplete = false;
    
    		function callback2(error, response, body2) {
    
    			if (!error && !intervalComplete && response.statusCode === 200 && JSON.parse(response.body).result.status === "complete") {
    
    				clearInterval(intervalId);
    				intervalComplete = true;
    
    				var options3 = {
    					url: 'https://xxxxx.qualtrics.com/API/v3/surveys/' + surveyid + '/export-responses/' + JSON.parse(response.body).result.fileId +
    						'/file',
    					headers: headers,
    					encoding: null
    				};
    
    				function callback3(error, response, body) {
    
    					if (!error && response.statusCode == 200) {
    // Qualtrics exports the survey response in ZIP format
    						if (response.headers['content-type'] == 'application/zip') {
    							var zip = new AdmZip(body);
    							var zipEntries = zip.getEntries();
    							var json = JSON.parse(zip.readAsText(zipEntries[0]));
    							var oData = {};
    //Unzip the response and parse the results in format which is mapped with the DB table structure							
    							json.responses.forEach(resp => {
    								console.log("--- Response is ----");
    								oData = {
    									"dealStatus": resp.labels.QID11,
    									"cLevelExecInMeeting": resp.labels.QID1,
    									"numberOfMeetings": resp.values.QID13_TEXT,
    									"competitorsInvolvedInBidding": resp.labels.QID3 ,
    								    "demoRequested": resp.labels.QID6,
    									"demoDelivered": resp.labels.QID14 ,
    									"cLevelExecInDemo": resp.values.QID15,
    									"demoFeedbackRating": resp.values.QID16_1,
    									"wasFollowupAfterDemoRequested": resp.labels.QID8,
    									"discountSlabOffered": resp.values.QID17_TEXT,
    									"sapSolutionsOffered": resp.values.QID18_TEXT,
    							        "reqUnderstoodRate": resp.values.QID20_1,
    									"fitmentVsRequiredFeatureRatio": resp.values.QID22_TEXT,
    								    "identifiedServicesAndDeliveryOptions": resp.labels.QID23,
    									"solutionValidated": resp.labels.QID24,
    									"riskRedressalDone": resp.labels.QID25,
    									"clarificationAddressed": resp.labels.QID26,
    									"bidChecklistFollowed": resp.labels.QID29,
    									"isCompetitorProductExpiring": resp.labels.QID30,
    									"referencesPastProposals": resp.labels.QID31
    								};
    								
    								console.log("--- POSTING Odata ----", oData);
    // Prepare fully qualified URL for the ODATA service which will hit the table and write the data in the HANA container								
    								axios.post('https://xxxxx.cfapps.eu10.hana.ondemand.com/deals/BidOutcomes', oData)
    									.then((res) => {
    										console.log(`statusCode: ${res.statusCode}`);
    									})
    									.catch((error) => {
    										console.error(error);
    									});
    							});
    
    							console.log('CALLBACK4');
    						}
    					}
    				}
    				// Download the survey responses .zip
    				request(options3, callback3);
    			}
    		}
    
    		var intervalId = setInterval(function () {
    			// Trigger to get progress update
    			console.log("triggering progress update call and waiting...");
    			request(options2, callback2);
    		}, 500);
    
    	}
    	else{
    		response.write("Failed!!");
    	}
    }
    // Create Export and get ProgressID  
    request(options, callback);
    ​
  • Final output would be, HANA container table updated with Qualtrics experience data –

Happy to have your feedback and questions.

Thanks for reading.

5 Comments
You must be Logged on to comment or reply to a post.
      • Hi Ankit ,

         

        Thanks for the blog. We are using a similar scenario where we want to bring data from Qualtrics to SAP HANA table , but the issue we are facing is that if create a table with the given data structure using the current questions , over time the questions might change for the survey then How do we dynamically create the new table structure and pull the data to HANA tables

        • Hi Poonam,

          We did not explore the possibility of making dynamic mapping between question columns and HANA table fields. As per my understanding this is quite difficult because Qualtrics has its inbuilt feature rich data visualization mechanism and further, I did not find any such API at Qualtrics API reference library https://api.qualtrics.com/reference

          I treat it as analogous to an ALV in classical SAP GUI. If a new field is added in ALV corresponding has to be added in the DDIC as well.

          I welcome thoughts from other experts.

          Thanks and Regards.

  • Hi Poonam,

    You might want to think about using the Document store feature in Hana and store the responses as JSON. This way you may not be constrained by a fixed schema’s inability to store an evolving survey question set. Think ‘schemaless’ for your persistence architecture,