Recently, while going through the COVID-19 Live Count link shared on a group, I found Covid19India.org had exposed the API endpoints publicly. Thought to build a dashboard using Charts.js and publish it for guest users using community.
The interesting part with this project is that not a single apex class is involved in making the API callout. All the callouts use the native "Fetch" method from JavaScript. Of course it's not secure for the authenticated callout, but you should give a try.

COVID-19 Live Dashboard
GIT repository is shared at the end of the article. Now, lets go through the components involved:
Components Involved:
Corona_Chart_Scripts.resource
As lightning components do not support CDN links, I downloaded Charts.js from the official website, added it into the zip and uploaded it as a static resource.
India_Corona_Map.cmp
<aura:component implements="flexipage:availableForAllPageTypes,forceCommunity:availableForAllPageTypes" >
<aura:attribute name="selectedState" type="String" default="Maharashtra"></aura:attribute>
<aura:attribute name="states" type="list"></aura:attribute>
<aura:attribute name="stateData" type="Object"></aura:attribute>
<aura:attribute name="stateChartInstance" type="Object"></aura:attribute>
<aura:attribute name="isLoading" type="boolean" default="true"></aura:attribute>
<ltng:require scripts="{!$Resource.Corona_Chart_Scripts+'/Chart.min.js'}"
afterScriptsLoaded="{!c.initialiseSetup}">
</ltng:require>
<div class="body">
<aura:if isTrue="{!!v.isLoading}">
<div class="slds-grid slds-wrap">
<div class="slds-col slds-size_1-of-1 slds-large-size_6-of-12">
<div class="slds-box slds-m-around_xx-small">
<canvas aura:id="dailyCount"></canvas>
</div>
</div>
<div class="slds-col slds-size_1-of-1 slds-large-size_6-of-12">
<div class="slds-box slds-m-around_xx-small">
<canvas aura:id="overallCount"></canvas>
</div>
</div>
<div class="slds-col slds-size_1-of-1 slds-large-size_6-of-12">
<div class="slds-box slds-m-around_xx-small">
<canvas aura:id="historicalPerDayCount"></canvas>
</div>
</div>
<div class="slds-col slds-size_1-of-1 slds-large-size_6-of-12">
<div class="slds-box slds-m-around_xx-small">
<canvas aura:id="historicalTotalCount"></canvas>
</div>
</div>
<div class="slds-col slds-size_1-of-1">
<div class="slds-box slds-m-around_xx-small">
<lightning:select label="Select State" value="{!v.selectedState}" onchange="{!c.onSelectState}">
<aura:iteration items="{!v.states}" var="state">
<option text="{!state}" value="{!state}" selected="{!state == v.selectedState}"></option>
</aura:iteration>
</lightning:select>
<canvas aura:id="stateCount"></canvas>
</div>
</div>
</div>
<aura:set attribute="else">
<div class="spinnerHolder">
<lightning:spinner class="spins" alternativeText="Fetching data from API.." size="large" />
</div>
</aura:set>
</aura:if>
</div>
</aura:component>
India_Corona_MapController.js
- There are two callouts made, so used Promise.all to finish both of them before proceeding with further data operations.
({
initialiseSetup : function(component, event, helper) {
Promise.all([helper.fetchData(component, 'https://api.covid19india.org/v2/state_district_wise.json' ,'v.nationalData'),
helper.fetchData(component, 'https://api.covid19india.org/data.json' ,'v.stateData')])
.then(values => {
component.set("v.isLoading", false);
let nationalData = values[1];
let stateData = values[0];
const lastArrayIndex = nationalData.cases_time_series.length - 1;
//draw donut for todays cases
helper.todaysCasesDonut(component, nationalData.cases_time_series[lastArrayIndex]);
//draw donut for todays cases
helper.overallCasesDonut(component, nationalData.cases_time_series[lastArrayIndex]);
//last 5 days count
const lastFiveDaysData = [];
for(let i = 0 ; i < 5 ; i ++)
lastFiveDaysData.push(nationalData.cases_time_series[lastArrayIndex - i]);
//historical per day count for past 5 days
helper.historicalPerDayCount(component, lastFiveDaysData);
//historical overall count for past 5 days
helper.historicalTotalCount(component, lastFiveDaysData);
//find state list
let states = [];
let districtDataByState = {};
stateData.forEach(st => {
states = [...states, st.state];
districtDataByState[st.state] = st.districtData;
});
component.set("v.states", states);
component.set("v.stateData", districtDataByState);
//statewise count
helper.stateWiseCount(component);
})
.catch( err => console.log(err));
},
onSelectState : function(component, event, helper){
//statewise count
helper.stateWiseCount(component);
}
})
India_Corona_MapHelper.js
- fetchData method is responsible to call the endpoint and pass the response back to the controller.
- Other methods prepare the required configs for the respective chart types. You can refer to the Chart.Js documentation for any configs.
({
fetchData : function(component, endpoint, variable){
return new Promise((resolve, reject) => {
fetch(endpoint)
.then(response => {
return response.json();
})
.then(data => {
resolve(data);
})
.catch(err => {
reject(err);
});
});
},
todaysCasesDonut : function (component, todaysCases){
const chartData = {
labels: ['Confirmed', 'Deceased', 'Recovered'],
datasets: [{
label: "Daily Cases",
backgroundColor: ["#8B0000", "#FF4500","#006400"],
data: [todaysCases.dailyconfirmed, todaysCases.dailydeceased, todaysCases.dailyrecovered]
}]
};
const chartOptions = {
title: {
display: true,
text: 'Todays cases in India'
},
animation: {
duration: 2000
}
}
this.generateChart(component, 'dailyCount', chartData, 'doughnut', chartOptions);
},
overallCasesDonut : function (component, overallCases){
const chartData = {
labels: ['Confirmed', 'Deceased', 'Recovered'],
datasets: [{
label: "Till Date Cases",
backgroundColor: ["#8B0000", "#FF4500","#006400"],
data: [overallCases.totalconfirmed, overallCases.totaldeceased, overallCases.totalrecovered]
}]
};
const chartOptions = {
title: {
display: true,
text: 'Till date cases in India'
},
animation: {
duration: 2000
}
}
this.generateChart(component, 'overallCount', chartData, 'doughnut', chartOptions);
},
historicalPerDayCount : function (component, lastFiveDaysData){
let labels = [];
let confirmedData = [];
let deseasedData = [];
let recoveredData = [];
lastFiveDaysData.forEach(info => {
labels = [...labels, info.date];
confirmedData = [...confirmedData, info.dailyconfirmed];
deseasedData = [...deseasedData, info.dailydeceased];
recoveredData = [...recoveredData, info.dailyrecovered];
});
const chartData = {
labels,
datasets: [{
label: "Confirmed",
backgroundColor: "#8B0000",
data:confirmedData
},{
label: "Deceased",
backgroundColor: "#FF4500",
data: deseasedData
},{
label: "Recovered",
backgroundColor: "#006400",
data: recoveredData
}]
};
const chartOptions = {
title: {
display: true,
text: 'Per day cases for last 5 days'
},
animation: {
duration: 2000
}
}
this.generateChart(component, 'historicalPerDayCount', chartData, 'bar', chartOptions);
},
stateWiseCount : function (component, lastFiveDaysData){
let labels = [];
let data = [];
let backgroundColor = [];
const selectedState = component.get("v.selectedState");
const districtInfo = component.get("v.stateData");
const selectedStateDistrictInfo = districtInfo[selectedState];
selectedStateDistrictInfo.forEach(dist => {
labels = [...labels, dist.district];
data = [...data, dist.confirmed];
backgroundColor = [...backgroundColor, '#'+(Math.random()*0xFFFFFF<<0).toString(16)];
})
const chartData = {
labels,
datasets: [{
label: "Districts",
backgroundColor,
data
}]
}
const chartOptions = {
title: {
display: true,
text: "Districts level confirmed cases for "+selectedState+" state"
},
animation: {
duration: 2000
}
}
this.generateChart(component, 'stateCount', chartData, 'bar', chartOptions);
},
historicalTotalCount : function (component, lastFiveDaysData){
let labels = [];
let confirmedData = [];
let deseasedData = [];
let recoveredData = [];
lastFiveDaysData.forEach(info => {
labels = [...labels, info.date];
confirmedData = [...confirmedData, info.totalconfirmed];
deseasedData = [...deseasedData, info.totaldeceased];
recoveredData = [...recoveredData, info.totalrecovered];
});
const chartData = {
labels,
datasets: [{
label: "Confirmed",
backgroundColor: "#8B0000",
data:confirmedData
},{
label: "Deceased",
backgroundColor: "#FF4500",
data: deseasedData
},{
label: "Recovered",
backgroundColor: "#006400",
data: recoveredData
}]
};
const chartOptions = {
title: {
display: true,
text: 'Overall cases for last 5 days'
},
animation: {
duration: 2000
}
}
this.generateChart(component, 'historicalTotalCount', chartData, 'bar', chartOptions);
},
generateChart : function(component, chartId, chartData, chartType, chartOptions) {
var ctx = component.find(chartId).getElement();
ctx.height = 250;
if(chartId == 'stateCount' && component.get("v.stateChartInstance") != null)
component.get("v.stateChartInstance").destroy();
var lineChart = new Chart(ctx ,{
type: chartType,
data: chartData,
options: chartOptions
});
if(chartId == 'stateCount')
component.set("v.stateChartInstance", lineChart);
}
})
Corona_Map.app
It's a container which will show the output of the component.
<aura:application extends="force:slds">
<c:India_Corona_Map></c:India_Corona_Map>
</aura:application>
That is us done from the coding side, so lets review the app. Wait, just getting a spinner instead of a dashboard! Why is this happening? Lets check the browser console.

CSP Trusted Site Error
Yes, the content security policy is blocking the callout. In short, Salesforce does not allow a callout to an endpoint unless it is marked as a CSP Trusted Site.
How to white list the endpoint domain?
- Navigate to Setup -> Security Controls -> CSP Trusted Sites
- Create the entry as follows:

CSP Trusted Site Entry
Voila, It should be working now! Comment if you face any issues.
Leave a Comment