Building a custom Google Data Studio connector from A-Z. Part 1 — Basic setup.

Step 1 — Setup a Google Script

Google Scripts dashboard
Select correct sharing settings

Step 2 — Setup environments (a.k.a. “Deployments”)

Version management
Creating new deployment

Step 3 — Setup local development

  • Add CLASP as a devDependency using yarn package manager. Runyarn add @google/clasp
  • Create a src subfolder to keep the Google script files and CLASP config files (.clasp.json, .claspignore). The reason is that soon we’ll add tests folder for unit tests. Trust me ;)
  • Run clasp login and login with your google credentials. You may also need to enable Google scripts API on this page: https://script.google.com/home/usersettings
  • Run clasp clone <SCRIPT>.<SCRIPT_ID> can be found in the url of Google Script editor.
  • Add .claspignorefile to src folder with following entries:
**/**
!*.js
!appsscript.json
  • Now whenever you make a change run clasp push to update the remote code. You can also add a following script to package.json ‘s scripts : ”push”: “cd src;clasp push;cd -”

Step 4 — Prepare a connector with mock data

Manifest file

{
"dataStudio": {
"name": "Spotify Connector",
"company": "None",
"logoUrl": "https://developer.spotify.com/assets/branding-guidelines/icon1@2x.png",
"addonUrl": "https://github.com/Bajena/spotify-gds-connector",
"supportUrl": "https://github.com/Bajena/spotify-gds-connector",
"description": "This connector can be used to show stats about your last played tracks"
}
}

getAuthType function

function getAuthType() {
return {
type: "NONE"
};
}

getConfig function

function getConfig() {
return {
dateRangeRequired: true
};
}

getSchema function

function getSchema() {
return {
schema: [
{
name: 'track_name',
label: 'Track Name',
dataType: 'STRING',
semantics: {
conceptType: 'DIMENSION'
}
},
{
name: 'artist',
label: 'Artist',
dataType: 'STRING',
semantics: {
conceptType: 'DIMENSION'
}
},
{
name: 'played_at_hour',
label: 'Played at (date + hour)',
dataType: 'STRING',
semantics: {
conceptType: 'DIMENSION',
semanticGroup: 'DATETIME',
semanticType: 'YEAR_MONTH_DAY_HOUR'
}
},
{
name: 'played_at_date',
label: 'Played at (date)',
dataType: 'STRING',
semantics: {
conceptType: 'DIMENSION',
semanticGroup: 'DATETIME',
semanticType: 'YEAR_MONTH_DAY'
}
},
{
name: 'plays',
label: 'Plays',
dataType: 'NUMBER',
formula: 'COUNT(track_name)',
semantics: {
conceptType: 'METRIC',
isReaggregatable: false
}
},
{
name: 'tracks_count',
label: 'Played Tracks',
dataType: 'NUMBER',
formula: 'COUNT(track_name)',
semantics: {
conceptType: 'METRIC',
isReaggregatable: false
}
},
{
name: 'popularity',
label: 'Popularity',
dataType: 'NUMBER',
semantics: {
conceptType: 'METRIC'
}
}
]
};
}

getData function

function getData(request) {
// Prepare the schema for the fields requested.
var dataSchema = [];
var fixedSchema = getSchema().schema;
request.fields.forEach(function(field) {
for (var i = 0; i < fixedSchema.length; i++) {
if (fixedSchema[i].name == field.name) {
dataSchema.push(fixedSchema[i]);
break;
}
}
});
// We'll query Spotify API here. For now let's just return two mocked records
var mockedData = {
items: [
{
track: {
name: "Voice of the New Generation",
popularity: 25,
artists: [
{
name: "Santa Cruz"
}
]
},
played_at: "2018-06-08T16:16:13.185Z"
},
{
track: {
name: "Shorty Wanna Be A Thug",
popularity: 59,
artists: [
{
name: "2Pac"
}
]
},
played_at: "2018-06-08T14:11:02Z"
}
]
};
// Prepare the tabular data.
var data = [];
mockedData.items.forEach(function(play) {
var values = [];
var playTime = new Date(play.played_at);
// Google expects YYMMDD format
var playedAtDate = playTime.toISOString().slice(0, 10).replace(/-/g, "");
// Provide values in the order defined by the schema.
dataSchema.forEach(function(field) {
switch (field.name) {
case 'track_name':
values.push(play.track.name);
break;
case 'artist':
values.push(play.track.artists[0].name);
break;
case 'played_at_hour':
values.push(
playedAtDate +
(playTime.getHours() < 10 ? '0' : '') + playTime.getHours()
);
break;
case 'played_at_date':
values.push(playedAtDate);
break;
case 'popularity':
values.push(play.track.popularity);
break;
default:
values.push('');
}
});
data.push({
values: values
});
});
return {
schema: dataSchema,
rows: data
};
}

isAdminUser function

function isAdminUser() {
return true;
}

Step 5 — Test the connector

Testing the connector

Step 6 — debug your connector

When your connector works on the first run

Coming next…

--

--

--

I’m a software developer @ Productboard, mostly interested in building backends using Ruby language. CSS doesn’t make me cry though ;)

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Magento To Magento2 Migration: All You Wanted To Know

Magento To Magento2 Migration: All You Wanted To Know

Lazy Loading, Aggregate and CQRS

How to Choose a Database on AWS?

What skills are top-rated on Upwork in 2019?

How to use Cloudflare to redirect your website visitors to another website of yours even if your…

Weeknotes S1e9

Mobile Game Progression

HOW TO - Menu Builder [EN] v0.10

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jan Bajena

Jan Bajena

I’m a software developer @ Productboard, mostly interested in building backends using Ruby language. CSS doesn’t make me cry though ;)

More from Medium

Scrapy — An easy way to scraw data from web page

Google Apps Scripts Code Snippets Toolkit

How to implement smart recommendation online learning in oppo Smart Recommendation Sample Center…

Building your first web app with Flask