Skip to Content
Technical Articles
Author's profile photo Ferry Djaja

Tracking Coronavirus COVID-19 Near Real Time with SAP HANA XSA

In this blog, I am going to walk through how you can create yourself a simple app to track the COVID-19 status in near real time with SAP HANA XS Advanced and display it in a web server or with SAP Analytics Cloud using custom widget.

Let’s get started to create the app in SAP HANA XSA.

Create HANA XSA app

  • Logon to SAP HANA XSA Web IDE and create a new project from template.
  • Select SAP Cloud Platform Business Application. Click Next.
  • Enter the project name covid19. You can give any name you like. Click Next.
  • On this section, just click Next to continue.
  • We will not include Service (at this moment) and Database. Also unchecked the “Unable user authentication (UAA”) with the intention that anyone can access the app without credentials. Click Next to continue.
  • Click Finish to complete the setup.

Create Node.JS Module

  • Right click on the project covid19 and select Node.js Module.
  • Enter the module name srv. You can give any name you like. Click Next.
  • Unchecked “Enable XSJS support” and click Next to continue.
  • Click Finish to complete.
  • You should see the srv folder created. Open the package.json.
  • We need to add additional libraries like csvtojson and geojson.
  • Insert the following lines under dependencies:
    "dependencies": {
        "@sap/cds": "^3.10.0",
        "express": "^4.17.1",
        "@sap/xssec": "^2.1.17",
        "@sap/xsenv": "^2.0.0",
        "hdb": "^0.17.0",
        "@sap/hdbext": "^6.0.0",
        "@sap/hana-client": "^2.4.139",
        "@sap/textbundle": "latest",
        "@sap/logging": "^5.0.1",
        "@sap/audit-logging": "^3.0.0",
        "nodemailer": "^6.2.1",
        "passport": "~0.4.0",
        "async": "^3.0.1",
        "ws": "^7.0.0",
        "accept-language-parser": "latest",
        "node-xlsx": "^0.15.0",
        "node-zip": "~1.1.1",
        "xmldoc": "~1.1.2",
        "winston": "^3.2.1",
        "body-parser": "^1.19.0",
        "elementtree": "latest",
        "then-request": "latest",
        "compression": "~1.7",
        "helmet": "^3.18.0",
        "request": "^2.81.0",
        "csvtojson": "^2.0.10",
        "geojson": "^0.5.0"
    }​

  • Replace server.js with this content:
    /*eslint no-console: 0, no-unused-vars: 0, no-undef:0, no-process-exit:0*/
    /*eslint-env node, es6 */
    "use strict";
    const port = process.env.PORT || 3000;
    const server = require("http").createServer();
    
    const cds = require("@sap/cds");
    //Initialize Express App for XSA UAA and HDBEXT Middleware
    const xsenv = require("@sap/xsenv");
    const passport = require("passport");
    const xssec = require("@sap/xssec");
    const xsHDBConn = require("@sap/hdbext");
    const express = require("express");
    global.__base = __dirname + "/";
    
    //logging
    var logging = require("@sap/logging");
    var appContext = logging.createAppContext();
    
    //Initialize Express App for XS UAA and HDBEXT Middleware
    var app = express();
    
    //Compression
    app.use(require("compression")({
      threshold: "1b"
    }));
    
    //Helmet for Security Policy Headers
    const helmet = require("helmet");
    // ...
    app.use(helmet());
    app.use(helmet.contentSecurityPolicy({
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "sapui5.hana.ondemand.com"],
        scriptSrc: ["'self'", "sapui5.hana.ondemand.com"]
      }
    }));
    // Sets "Referrer-Policy: no-referrer".
    app.use(helmet.referrerPolicy({ policy: "no-referrer" }));
    
    app.use(logging.middleware({
    	appContext: appContext,
    	logNetwork: true
    }));
    
    var http = require("http");
    
    app.use(function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "REPLACE_WITH_YOUR_SAP_ANALYTICS_CLOUD_DOMAIN); // update to match the domain you will make the request from
        res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        next();
    });
    
    app.get("/node", (req, res) => {
    	res.send("OK");
    });
    
    //Setup Additonal Node.js Routes
    require("./router")(app, server);
    
    //Start the Server 
    server.on("request", app);
    server.listen(port, function () {
    	console.info(`HTTP Server: ${server.address().port}`);
    });
    
    ​

    At line 50, replace “REPLACE_WITH_YOUR_SAP_ANALYTICS_CLOUD_DOMAIN” with your localhost or SAP Analytics Cloud domain. This is to resolve the CORS issue.

  • Create a folder router inside srv folder. Also create a folder routes inside router folder.
  • Create index.js in router folder with this content:
    /*eslint-env node, es6 */
    "use strict";
    
    module.exports = (app, server) => {
    	app.use("/node", require("./routes/myNode")());
    };​
  • Create myNode.js in routes folder with this content:
    /*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, newcap:0*/
    /*eslint-env node, es6 */
    "use strict";
    const express = require("express");
    const async = require("async");
    const https = require('https');
    const csv = require('csvtojson')
    const GeoJSON = require('geojson')
    
    function formatDate(d) {
    	//get the month
    	var month = d.getMonth();
    	//get the day
    	//convert day to string
    	var day = d.getDate().toString() - 1;
    	//get the year
    	var year = d.getFullYear();
    
    	//pull the last two digits of the year
    	year = year.toString().substr(-2);
    
    	//increment month by 1 since it is 0 indexed
    	//converts month to a string
    	month = (month + 1).toString();
    
    	//if month is 1-9 pad right with a 0 for two digits
    	if (month.length === 1) {
    		month = month;
    	}
    
    	//if day is between 1-9 pad right with a 0 for two digits
    	if (day.length === 1) {
    		day = day;
    	}
    
    	//return the string "MMddyy"
    	return month + '/' + day + '/' + year;
    }
    
    function readHeader() {
    	return new Promise(resolve => {
    		const result = [];
    		https.get(
    			'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Confirmed.csv',
    			(resp) => {
    				let data = '';
    
    				// A chunk of data has been recieved.
    				resp.on('data', (chunk) => {
    					data += chunk;
    				});
    
    				// The whole response has been received. Print out the result.
    				resp.on('end', () => {
    					csv({
    							noheader: false,
    							output: "json"
    						})
    						.fromString(data)
    						.then((csvRow) => {
    							const result = [];
    							var d = new Date();
    							var date = formatDate(d);
    							console.log(date);
    							for (var i = 0; i < csvRow.length; i++) {
    								result.push({
    									'Province/State': csvRow[i]['Province/State'],
    									'Country/Region': csvRow[i]['Country/Region'],
    									'Lat': csvRow[i]['Lat'],
    									'Long': csvRow[i]['Long']
    								})
    							}
    							resolve(result);
    						})
    				});
    
    			}).on("error", (err) => {
    			console.log("Error: " + err.message);
    		});
    	});
    }
    
    function readConfirmed() {
    	return new Promise(resolve => {
    		const result = [];
    		https.get(
    			'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Confirmed.csv',
    			(resp) => {
    				let data = '';
    
    				// A chunk of data has been recieved.
    				resp.on('data', (chunk) => {
    					data += chunk;
    				});
    
    				// The whole response has been received. Print out the result.
    				resp.on('end', () => {
    					csv({
    							noheader: false,
    							output: "json"
    						})
    						.fromString(data)
    						.then((csvRow) => {
    							const result = [];
    							var d = new Date();
    							var date = formatDate(d);
    							console.log(date);
    							for (var i = 0; i < csvRow.length; i++) {
    								result.push(csvRow[i][date]);
    							}
    							resolve(result);
    						})
    				});
    
    			}).on("error", (err) => {
    			console.log("Error: " + err.message);
    		});
    	});
    }
    
    function readDeath() {
    	return new Promise(resolve => {
    		const result = [];
    		https.get(
    			'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Deaths.csv',
    			(resp) => {
    				let data = '';
    
    				// A chunk of data has been recieved.
    				resp.on('data', (chunk) => {
    					data += chunk;
    				});
    
    				// The whole response has been received. Print out the result.
    				resp.on('end', () => {
    					csv({
    							noheader: false,
    							output: "json"
    						})
    						.fromString(data)
    						.then((csvRow) => {
    							const result = [];
    							var d = new Date();
    							var date = formatDate(d);
    							console.log(date);
    							for (var i = 0; i < csvRow.length; i++) {
    								result.push(csvRow[i][date]);
    							}
    							resolve(result);
    						})
    				});
    
    			}).on("error", (err) => {
    			console.log("Error: " + err.message);
    		});
    	});
    }
    
    function readRecovered() {
    	return new Promise(resolve => {
    		const result = [];
    		https.get(
    			'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Recovered.csv',
    			(resp) => {
    				let data = '';
    
    				// A chunk of data has been recieved.
    				resp.on('data', (chunk) => {
    					data += chunk;
    				});
    
    				// The whole response has been received. Print out the result.
    				resp.on('end', () => {
    					csv({
    							noheader: false,
    							output: "json"
    						})
    						.fromString(data)
    						.then((csvRow) => {
    							const result = [];
    							var d = new Date();
    							var date = formatDate(d);
    							console.log(date);
    							for (var i = 0; i < csvRow.length; i++) {
    								result.push(csvRow[i][date]);
    							}
    							resolve(result);
    						})
    				});
    
    			}).on("error", (err) => {
    			console.log("Error: " + err.message);
    		});
    	});
    }
    
    module.exports = function () {
    	var app = express.Router();
    	var userScope = null;
    
    	app.get("/getSessionInfo", (req, res) => {
    		async function msg() {
    			const header_msg = await readHeader();
    			const confirmed_msg = await readConfirmed();
    			const death_msg = await readDeath();
    			const recovered_msg = await readRecovered();
    
    			const result = [];
    
    			for (var i = 0; i < header_msg.length; i++) {
    				result.push({
    					'Province/State': header_msg[i]['Province/State'],
    					'Country/Region': header_msg[i]['Country/Region'],
    					'Lat': parseFloat(header_msg[i]['Lat']),
    					'Long': parseFloat(header_msg[i]['Long']),
    					'Recovered': parseFloat(recovered_msg[i]),
    					'Confirmed': parseFloat(confirmed_msg[i]),
    					'Death': parseFloat(death_msg[i])
    				})
    			}
    
    			var geojson = GeoJSON.parse(result, {
    				Point: ['Lat', 'Long']
    			});
    
    			res.type("application/json").status(200).send(JSON.stringify(geojson));
    		}
    		msg();
    	});
    
    	return app;
    };​

    Basically, I am getting the data from https://github.com/CSSEGISandData/COVID-19time_series_19-covid-Confirmed.csv, time_series_19-covid-Deaths.csv and time_series_19-covid-Recovered.csv and convert it to GeoJSON format.
    You can see the high level design in the diagram below:

Create Web Module

  • Right click again on the project covid19 and select Basic HTML5 module.
  • Enter the module name web. You can give any name you like. Click Next.
  • Click Finish to complete.
  • Open xs-app.json and replace the content with:
    {
    	"welcomeFile": "index.html",
    	"authenticationMethod": "none",
    	"routes": [{
    		"source": "/node(.*)",
    		"destination": "srv_api",
    		"csrfProtection": true,
    		"authenticationType": "none"
    	}]
    }​

  • Now edit mta.yaml and replace the content with the following:

    ID: covid19
    _schema-version: "2.1"
    version: 0.0.1
    modules:
      - name: covid19-srv
        type: nodejs
        path: srv
        parameters:
          memory: 512M
          disk-quota: 256M
        provides:
          - name: srv_api
            properties:
              url: '${default-url}'
        requires:
          - name: zcovid-uaa
    
    
      - name: covid19-web
        type: html5
        path: web
        requires:
          - name: zcovid-uaa
          - name: srv_api
            group: destinations
            properties:
              name: srv_api
              url: '~{url}'
              forwardAuthToken: true
    
    resources:
      - name: zcovid-uaa
        type: com.sap.xs.uaa-space
        parameters:
          config-path: ./xs-security.json
    ​

    Don’t forget to set zcovid-uaa. You may refer to this blog on how to do it.

  • Create a file xs-security.json  in the project root folder and replace the content with the following:
    {
    	"xsappname": "zcovid19",
    	"scopes": [{
    		"name": "$XSAPPNAME.Display",
    		"description": "display"
    	}, {
    		"name": "$XSAPPNAME.Create",
    		"description": "create"
    	}, {
    		"name": "$XSAPPNAME.Edit",
    		"description": "edit"
    	}, {
    		"name": "$XSAPPNAME.Delete",
    		"description": "delete"
    	}, {
    		"name": "$XSAPPNAME.DataGenerator",
    		"description": "data generator"
    	}, {
    		"name": "xs_authorization.read",
    		"description": "Read authorization information from UAA"
    	}, {
    		"name": "xs_authorization.write",
    		"description": "Write authorization information to UAA"
    	}, {
    		"name": "$XSAPPNAME.ODATASERVICEUSER",
    		"description": "Enter"
    	}, {
    		"name": "$XSAPPNAME.ODATASERVICEADMIN",
    		"description": "Enter"
    	}],
    	"attributes": [{
    		"name": "client",
    		"description": "Session Client",
    		"valueType": "int"
    	}, {
    		"name": "country",
    		"description": "country",
    		"valueType": "s"
    	}],
    	"role-templates": [{
    		"name": "Viewer",
    		"description": "View all records",
    		"scope-references": [
    			"$XSAPPNAME.Display"
    		],
    		"attribute-references": [
    			"client", "country"
    		]
    	}, {
    		"name": "Editor",
    		"description": "Edit and Delete records",
    		"scope-references": [
    			"$XSAPPNAME.Create",
    			"$XSAPPNAME.Edit",
    			"$XSAPPNAME.Delete",
    			"$XSAPPNAME.Display",
    			"$XSAPPNAME.DataGenerator",
    			"$XSAPPNAME.ODATASERVICEUSER",
    			"$XSAPPNAME.ODATASERVICEADMIN"
    		],
    		"attribute-references": [
    			"client"
    		]
    	}]
    }​

Run the App

  • Run the NodeJS module: select srv and click Run. If no error, you will see the similar below message:
  • Run the Web module: select web and click Run. If no error, you will see the similar below message:
  • Now open the web URL and add suffix /node/getSessionInfo: https://<HANA_Web_url>/node/getSessionInfo
    If there is no error, you will see the similar screenshot below. We will use this URL in  the web app later.

That’s all the steps that we need to do on HANA XSA.

Consume GeoJSON Data in Web App

Create a file map.html with the below content and put in local web server.

<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>COVID-19</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>

    <style>
        body {
            font-family: sans-serif;
            margin: 0px;
            border: 0px;
            padding: 0px;
        }
        .container {
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            position: absolute;
        }
        .header {
            position: absolute;
            top: -8px;
            left: 0px;
            padding-left: 50px;
            right: 0;
            padding: 10px;
            z-index: 1000;
            background-color: rgba(255,255,255,0.75);
        }
        h2 {
            margin: 10px 0;
            margin-left: 50px;
        }
        h3 {
            margin: 0;
        }
        #map {
            height: 100%;
            width: 100%;
        }

        #property-list, #playback {
            display: inline-block;
        }

        .view {
            display: inline-block;
            font-size: 12px;
            border: 1px solid black;
            border-radius: 3px;
            margin: 3px;
            padding: 4px;
            background: #ffab96;
        }

        .view:hover {
            background: dodgerblue;
            color: white;
            margin-top: -2px;
            box-shadow: 1px 1px 1px black;
        }

        .view:active {
            margin-top: -2px;
        }

        #playback {
            margin-right: 1em;
            margin-left: 1em;
        }

        #playback .view {
            background-color: #ab96ff;
        }

        .view.selected {
            background: white;
            color: black;
        }

        #datepicker {
            margin-left: 50px;
            font-size: 12px;
        }

        .flatpickr-minute {
            pointer-events: none;
        }

        .flatpickr-minute + .arrowUp {
            pointer-events: none;
        }

        .flatpickr-minute + .arrowUp + .arrowDown {
            pointer-events: none;
        }

        .numInputWrapper:nth-child(3):hover {
            background: none;
        }

        .numInputWrapper:nth-child(3):hover .arrowUp {
            display: none;
        }

        .numInputWrapper:nth-child(3):hover .arrowDown {
            display: none;
        }
    </style>
  </head>

  <body>
    <div class="header">
        <h2>COVID-19 (2019-nCoV)</h2>
    </div>
    <div class="container">
        <div id="map"></div>
    </div>

    <!-- leaflet -->
    <script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script>

    <!-- D3 -->
    <script src="https://d3js.org/d3.v5.min.js"></script>

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

    <!-- Demo setup -->
    <script>
        var circles = [];
        var initDate = '2001-02-23t09:00:00';
        var timeFormatter = d3.timeFormat('%Y-%m-%dt%H:%M:%S');
        var numberFormatter = d3.format(",");

        var properties = [
            { code: 'Confirmed', desc: 'Confirmed' },
            { code: 'Death', desc: 'Death' },
			{ code: 'Recovered', desc: 'Recovered' }
        ];
        var currProperty = 'Confirmed';
		var ProvinceState = 'Province/State';
		var CountryRegion = 'Country/Region';

        var theMap = L.map('map', {maxZoom: 14});
        theMap.attributionControl.addAttribution('COVID-19 (2019-nCoV) <a href="https://github.com/CSSEGISandData/COVID-19">JHU CSSE</a>');
        theMap.attributionControl.addAttribution('Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Map data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>');

        L.tileLayer('http://tile.stamen.com/terrain/{z}/{x}/{y}.png').addTo(theMap);
        // center of map
        theMap.setView([31.160629, 112.248863], 4);

        var radiusScale = d3.scaleLinear().domain([0, 200]).range([7, 70]).clamp(true);
        var colorScale = d3.scaleSequential(d3.interpolateOrRd).domain([0, 100]);
        function renderCircles() {
            circles.forEach(function (c) { c.remove(); })
            circles = [];

            theData.features.forEach(function (feature) {
                var c = L.circleMarker(
                    [feature.geometry.coordinates[1], feature.geometry.coordinates[0]],
                    {
                        radius: radiusScale(feature.properties[currProperty] * 0.001),
                        color: colorScale(feature.properties[currProperty]),
                        fillColor: colorScale(feature.properties[currProperty]),
                        fillOpacity: 0.5
                    });
                c.addTo(theMap);
				if(feature.properties[ProvinceState] !== "") {
					c.bindTooltip('<h3>' + feature.properties[ProvinceState] + '</h3> - ' + feature.properties[CountryRegion] + '<br><br><b>' + currProperty + ': </b>' + numberFormatter(feature.properties[currProperty]) + '<br>' + '<b>Death: </b>' + numberFormatter(feature.properties.Death) + '<br>' + '<b>Recovered: </b>' + numberFormatter(feature.properties.Recovered));
				} else {
					c.bindTooltip('<h3>' + feature.properties[CountryRegion] + '</h3><br><b>' + currProperty + ': </b>' + numberFormatter(feature.properties[currProperty]) + '<br>' + '<b>Death: </b>' + numberFormatter(feature.properties.Death) + '<br>' + '<b>Recovered: </b>' + numberFormatter(feature.properties.Recovered));
				}
                circles.push(c);
            });
        }

        function fetchData(dateStr) {
			var url = 'https://<HANA_Web_url>/node/getSessionInfo';
            d3.json(url).then(function(data) {
				console.log(data);
				theData = data;
                renderCircles();
            });
        }

        fetchData(initDate);

    </script>

  </body>
</html>

In fetchData function, replace the URL with the HANA Web URL:https://<HANA_Web_url>/node/getSessionInfo

Open map.html in local web browser:

I also created the custom widget in SAP Analytics Cloud to consume the GeoJSON data from SAP HANA XSA:

Complete source code: https://github.com/ferrygun/SAPHANA_COVID19

 

Please stay at home and stay safe everyone !!

References:

SAP Analytics Cloud Custom Widget Collection

Assigned tags

      54 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo jyothir aditya k
      jyothir aditya k

      great content !!

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Thanks Jyothir!

      Author's profile photo S Abinath
      S Abinath

      Thanks great content

      Author's profile photo Ashok Kumar
      Ashok Kumar

      Fun with COVID19? on S4H , Brilliant

      Author's profile photo Former Member
      Former Member

      Is it S4H???

      Author's profile photo Gregor Wolf
      Gregor Wolf

      No, HANA XSA only. But you could replicate the same also in ABAP.

      Author's profile photo Sudhir Ravi
      Sudhir Ravi

      Brilliant Content.

      Author's profile photo Aditya Sharma
      Aditya Sharma

      Ha ha ,its brilliant but only sap has mind on it.

      Author's profile photo Aditya Sharma
      Aditya Sharma

      It will be a great pleasure if sap puts it focus more in running their erp systems well.

      Rarely 6 chaps in complete market are multiskilled like abap,jquerys,javascript,abap,MVC,html?XS Js support  all together and plus sap has made development look more complex than ever.

      Such developments may keep sap in home team happy but all your customers are helpless today.

      Just one view , make development simpler.

      Author's profile photo Amber Peters
      Amber Peters

      What is your view on this that SAP has for an idea?  https://www.youtube.com/watch?v=YhZqLWUQd-4&feature=youtu.be

      Author's profile photo Aditya Sharma
      Aditya Sharma

      SAP has a positive view to check corona spreading trend.But it would have been better to choose the right medium and technology.If I think I have to develop such an app in my premises I need 6 people of these skills.

      1. HTML/JavaScript

      2.JQuery

      3.Abap

      4.UI5 & Fiori

      5.Java (prefferable).

      6.XML

       

      Kindly stop luring end customer with these examples of yours as they are achievable in sap premises only and not in customers.

      I have been working on these things and frankly you have taken total independence from developers making everything abstract,complex.The programming experience now compare to before is too sick.

      Those people whom are appreciating you here may be are looking the final output but not the colossal effort and skills needed to achieve it.

       

       

      Author's profile photo Ronnie André Bjørvik Sletta
      Ronnie André Bjørvik Sletta

      It is good to have a critical eye on SAP, but here you miss the target. You are listing 6 technologies/frameworks, but you don't need 6 people to 6 roles.

      1. HTML/JavaScript - Basic web technologies - Handled by frontend developer
      2. JQuery - old JavaScript library. Not a significant part of modern web frameworks. Still legacy support in UI5. Abstracted away by UI5, so knowledge is not needed.
      3. ABAP - Handled by backend developer
      4. UI5 & Fiori - Two different things. One is the web framework, the other a design language. Both are in the safe hands of your frontend developer
      5. Java - Handled by backend developer
      6. XML - If your're talking about XML in the UI5 context, that is an integrated part of UI5 and handled by the frontend developer.

      You have listed two backend programming languages. I would think you would choose one or the other, not both.

      So you would possibly need two developers, one for the frontend, and one backend developer. And if you choose to create the application on SAP Cloud Platform - Cloud Foundry (or XSA as shown here), you can code the backend in JavaScript, using node.js, as a third option. Then you can potentially get away with one full-stack JavaScript developer.

      This is not hard, it only requires some dedication on aquiring some new skills. And all the needed educational material is available for free on the internet. ?

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Very well said Ronnie. And you're the perfect role model on how to get deep into this technologies. Use the available resources and systems to learn and qualify for the next years.

      Author's profile photo Aditya Sharma
      Aditya Sharma

      Dear sir,

      But when an outsourced company manager explains the skill requirement to client,he narrates this way only to fetch money in projects that 6 skills and roles are involved.

      Client IT head get criticized by company MD for putting SAP that takes this much effort and cost for showing an app of only product catalog of company.

      This solution of amalgamating all technologies like microsoft ODATA,oracle java and atop of it creating a native sap framework like UI5 and then purely using these technologies for development purpose is not possible to implement in real world.Main reason is you have to consider human being brain adaptability.When a graduate pass out of college,he is average in even oops.These concepts are a much much extension to them.I remember when i passed out of college ,how much i was afraid of even word xml.

      Thats y i say only 15 year experienced person can only think such products to try them out .A developer at experience level of 4 years dont understand even inner joins properly. Using hana procedures,AMDPs is a far above level.Even today 70 percent if developers dont even know how to manage exception handling in BAPI using sap provided error container classes.

      Plus you have used the  word abstraction so purely in your product implementation that even after achieving results,somewhere developer doesnot feel a grip on final product.Somewhere at corner of mind he knows this product has complete tendency to trouble me in production.

      In totality i mean the step of this drastic change of development environment embedding multiskill is an impure and impractical approach to product solutioning.Job market will be flooded with people and not with skilled people.

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Maybe it's better to continue this discussion in your post: Not liking new software tools based on hana. I've already added my 2 cent's there. And please start using spaces after a . ;-).+

      Author's profile photo Shanthi Bhaskar
      Shanthi Bhaskar

      Great post. 

      Author's profile photo Pinak Deb
      Pinak Deb

      Very Interesting post...

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Ferry,

      thank you for addressing that topic with HANA XSA. I've created a pull request with improvements and include map from blog #1. Hope you find time to include that. Because than everyone can directly start by using just your GitHub repository.

      Do you plan to improve your solution to:

      • Use a job to update the data in a HANA table
      • Cleanup the not needed dependencies
      • Make use of the xs-security.json and add authentication
      • Run also on SAP CP Cloud foundry

      ?

      Best regards
      Gregor

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Hi Gregor, Thanks for your feedback!!

      Yes I am aware on the not needed dependencies. I was putting there just to have it ready when I want to use the database module in the future in XSA. The intention to build this PoC in my organization is to make sure people all aware on the status of this outbreak, so I have turned off the authentication for all users to have easy access.

      I think I am leaving to the SAP community here to enhance the code for both back-end and front-end (Leaflet) based on the initial codes.

      Regards

      Ferry

      Author's profile photo Aditya Sharma
      Aditya Sharma

      If somebody modify the code,do tell me

       

      Author's profile photo ABAP Team AZ
      ABAP Team AZ

      Is this possible in HANA SAP WEB IDE Trial account logon?

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Currently not as no destinations are used to fetch the data from GitHub. But you can contribute that.

      Author's profile photo Henrique Pinto
      Henrique Pinto

      We should publish this in some externally available instance and make the maps available to anyone.

      Cc Thomas Jung

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Check out Coronavirus COVID-19 Global Cases by Johns Hopkins CSSE. I've got this link from Helmut Tammen.

      Author's profile photo Pierre COL
      Pierre COL

      Yes, of course: this website from Johns Hopkins University is fueled with the same data, but it would be good for marketing/communication purpose to have Ferry Djaja's demo on an externally available website that we could promote on social channels...

      Author's profile photo Paula Colmenares
      Paula Colmenares

      I totally agree! can we do that please? I would like to include the link in an external communication/marketing post.. would be great if it can be customer face ready, with SAP logo showing and technology utilized...

      Author's profile photo Florian Henninger
      Florian Henninger

      You should think about what you can do to help. It's not the right time to talk about something that serious in a marketing aspect. MAybe you can put some effort on it to make the machine learning help to better decide what should be the next steps... btw it would also showcase the technology a lot more impressive 😉

      Author's profile photo Graham Hardy
      Graham Hardy

      This is the sort of functionality that really demonstrates the power of a product, and as Ferry has wonderfully demonstrated here even the most recent viral threat can be mapped in near real time showing the real extent of this outbreak.  Respect to Ferry here for a very nice job done

      Author's profile photo Abdul Mushfiq
      Abdul Mushfiq

      Can somebody make this for XS classic ?

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      I am not sure if you can do it in XS classic as we need the NodeJS module.

      Author's profile photo Gregor Wolf
      Gregor Wolf

      It can be done also with XS classic as you have JavaScript also there. But reading the data will be a bit more effort as you have to define destinations and import the certificates.

      Author's profile photo Bruno Nicolau
      Bruno Nicolau

      Hi Ferry,

      Congratulations! It is an amazing content.

      I am beginner in SAP Fiori and Java script. I try to reply this code, but I stopped into the file myNode.js because there is something wrong on this piece of code below:

      app.get("/getSessionInfo", (req, res) => {
      Exactly here (Sintax error)=> async function msg() {
      const header_msg = await readHeader();

      Could you please give me an idea how can I fix it?

       

      Thanks in advance!

      Best regards,

       

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Bruno,

      you are aware that this part of the project has to run on a NodeJS server. I was able to get the project from GitHub SAPHANA_COVID19 running just as it is. Hope that Ferry still accepts my pull request which will include the webpage with the map directly in the project.

      Best regards
      Gregor

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Thanks Gregor

      I have accepted your pull request.

      Regards

      Ferry

      Author's profile photo Mamadou FAYE
      Mamadou FAYE

      Congratulations very good job !

      Author's profile photo Witalij Rudnicki
      Witalij Rudnicki

      Thanks for sharing this. I just published a post as well showing how to load the same data files into SAP HANA database quickly using hana_ml Python package:

      https://blogs.sap.com/2020/03/11/quickly-load-covid-19-data-with-hana_ml-and-see-with-dbeaver/

      Stay healthy, everyone!

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Great article Witalij! Thanks for sharing!

      Regards

      Ferry

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Witalij,

      now make your part a module that can be executed by a SAP CP Cloud Foundry scheduler and adjust the project of Ferry Djaja to read the data from the persistence and make it deploy to SAP CP. Then Henrique Pinto, Pierre COL and Paula Colmenares have their wished demo.

      Best regards
      Gregor

      Author's profile photo Manel M
      Manel M

      Very interesting

      Author's profile photo MARCELLO VINCI
      MARCELLO VINCI

      Excellent! Great Job!

      Author's profile photo Luca Toldo
      Luca Toldo

      Well done, do you think you could also add a prediction on where / when the pandemic will be resolved first?

      Author's profile photo Thomas Madsen
      Thomas Madsen

      Hi Ferry

       

      Looks good. I am trying to implment on my own HANA server.

      when running the node server I get the following error (I am new to the webIDE and node - so sorry if simple question):

      1690 verbose stack requestretry: No valid versions available for requestretry

      1690 verbose stack     at pickManifest (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/npm-pick-manifest/index.js:20:11)

      1690 verbose stack     at fetchPackument.then.packument (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/pacote/lib/fetchers/registry/manifest.js:39:14)

      1690 verbose stack     at tryCatcher (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/util.js:16:23)

      1690 verbose stack     at Promise._settlePromiseFromHandler (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/promise.js:512:31)

      1690 verbose stack     at Promise._settlePromise (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/promise.js:569:18)

      1690 verbose stack     at Promise._settlePromiseCtx (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/promise.js:606:10)

      1690 verbose stack     at Async._drainQueue (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/async.js:138:12)

      1690 verbose stack     at Async._drainQueues (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/async.js:143:10)

      1690 verbose stack     at Immediate.Async.drainQueues [as _onImmediate] (/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/node_modules/bluebird/js/release/async.js:17:14)

      1690 verbose stack     at runCallback (timers.js:705:18)

      1690 verbose stack     at tryOnImmediate (timers.js:676:5)

      1690 verbose stack     at processImmediate (timers.js:658:5)

      1691 verbose cwd /disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/.sap_java_buildpack/tomcat/temp/builder/sap.nodejs/builds/build-7438965441220184789/srv

      1692 verbose Linux 4.4.180-94.100-default

      1693 verbose argv "/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/bin/node" "/disk1/hana/shared/CXC/xs/app_working/hanacxc/executionroot/94123f56-8b84-4c8a-9ee4-96a6052cc783/app/META-INF/resources/nodejs/vendor/node10.15/lib/node_modules/npm/bin/npm-cli.js" "install" "--no-save" "--registry" "https://hanacxc.cph.sap.corp:51006"

      1694 verbose node v10.15.3

      1695 verbose npm  v6.4.1

      1696 error code ENOVERSIONS

      1697 error No valid versions available for requestretry

      1698 verbose exit [ 1, true ]

                                                               

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Hi Thomas,

      Can you check if all dependencies are installed ? also check if your package.json is the same as mine.

      Regards,
      Ferry

      Author's profile photo Aditya Sharma
      Aditya Sharma

      Keep diving.I say only sap can make it and not it's customers.

      Author's profile photo Thomas Madsen
      Thomas Madsen

      Hi Ferry

      As far as I understand, the dependencies should be installed by XSA, right?

       

      Anyway after a reboot it now works.

      Thanks for replying.

       

      /Thomas

      Author's profile photo Thomas Madsen
      Thomas Madsen

      Hi Ferry

      Thanks again for a good explanation. Any chance you can share how you created the custom widget in Analytics cloud?

       

      Thanks

       

      Best regards

       

      Thomas

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      Hi Thomas,

       

      Sorry for the late reply. You can refer to my new blog here, where I described how to create a Google Gauge custom widget in SAC app designer. Part of the codes are also there.

      https://blogs.sap.com/2020/04/22/interface-sap-analytics-cloud-google-gauge-custom-widget-with-rgb-led/

      Regards,
      Ferry

      Author's profile photo Berkay Yilmaz
      Berkay Yilmaz

      Great Content !

       

      Author's profile photo Ferry Djaja
      Ferry Djaja
      Blog Post Author

      I have enhanced the design in SAC. I hope you like it 🙂

      Author's profile photo Ceyhun Alp
      Ceyhun Alp

      Very well done, both techically and functionally!

      I look forward to implementing this.

      Thanks...

      Author's profile photo Ahmed khan
      Ahmed khan

      Whenever I am trying to run srv folder I am getting an error, even though I have already configured my cloud foundry settings, May I know please how to resolve this error.

       

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Have you checked that your trial is still active? You have to be aware that you have to reactivate your trial after 30 days.

      Author's profile photo Ahmed khan
      Ahmed khan

      Hi Gregor, Thanks for responding, it was deactivated and after reactivation it worked.

      Author's profile photo Juliane Ort
      Juliane Ort

      Hi together,
      I wanted to build this application, however, I’m running into problems although I’ve tried it repetitively.

      I don’t understand why the application cannot run in the end. Here is a screenshot of the error message.

      Running module /covid/srv failed:

      My assuption is that I don’t have the possibility to choose a Basic HTML 5 Module. I can only choose HTML 5 Module. What do I have to choose then: SAP UI5 Application?

      Also, I’m not sure if I am using the correct plattform as you are talking about HANA XSA and I am working only with the SAP Web IDE Full-Stack.

      Thanks a lot for helping out!

      Juliane