Skip to content

Instantly share code, notes, and snippets.

@joshcanhelp
Last active February 11, 2020 17:55
Show Gist options
  • Save joshcanhelp/54607ffca99a7ab7c5f3851bacaad1b0 to your computer and use it in GitHub Desktop.
Save joshcanhelp/54607ffca99a7ab7c5f3851bacaad1b0 to your computer and use it in GitHub Desktop.
Google Script Editor functions for getting SDK quality metrics
/* globals SpreadsheetApp, Browser, CacheService, UrlFetchApp, PropertiesService */
/*
*
* Macro functions
*
*/
/**
* Prompt the user for a GitHub API token.
*/
function saveGitHubApiToken() {
'use strict';
var token = SpreadsheetApp.getUi().prompt('Enter your GitHub API token').getResponseText();
PropertiesService.getUserProperties().setProperty('GITHUB_API_TOKEN', token);
}
/**
* Prompt the user for a Codecov API token.
*/
function saveCodecovApiToken() {
'use strict';
var token = SpreadsheetApp.getUi().prompt('Enter your Codecov API token').getResponseText();
PropertiesService.getUserProperties().setProperty('CODECOV_API_TOKEN', token);
}
/**
* Updates selected row with new data.
*/
function updateRow() {
'use strict';
if (!GitHub.prototype.tokenCheck() || !Codecov.prototype.tokenCheck()) {
return;
}
var sheet = SpreadsheetApp.getActiveSheet();
var activeRange = SpreadsheetApp.getActiveRange();
var dataPoints = sheet.getRange("2:2").getValues()[0].filter(function(el) { return el.length; });
var currRow = activeRange.getRowIndex();
var bottomRow = activeRange.getLastRow();
if (!currRow || !bottomRow) {
Browser.msgBox('Select at least one cell in a repo row to update.');
return;
}
for (currRow; currRow <= bottomRow; currRow++) {
var repoName = sheet.getRange(currRow, 1).getValue();
if (currRow > 2 && repoName) {
var github = new GitHub(repoName);
var codecov = new Codecov(repoName);
var otherData = {
readme: github.getReadme(),
ci: github.getCi(),
topics: github.getTopics(),
coverage: codecov.getCoverage()
};
var currCol = 3;
dataPoints.forEach(function(el) {
var dataPoint = el.split('|');
var value;
if ('comm' === dataPoint[0]) {
dataPoint = dataPoint[1];
value = github.getCommunity(dataPoint);
} else if ('repo' === dataPoint[0]) {
dataPoint = dataPoint[1];
value = github.getRepo(dataPoint);
} else if ('release' === dataPoint[0]) {
dataPoint = dataPoint[1];
value = github.getLatestRelease(dataPoint);
} else if ('traffic' === dataPoint[0]) {
dataPoint = dataPoint[1];
value = github.getTraffic(dataPoint);
} else {
dataPoint = dataPoint[0];
value = otherData[dataPoint];
}
switch (dataPoint) {
case 'issue_template':
case 'pull_request_template':
case 'contributing':
case 'code_of_conduct':
value = value ? 'Yes' : 'No';
break;
case 'pushed_at':
value = value.split('T')[0];
break;
case 'readme':
value = processReadme(value);
break;
case 'coverage':
value = value / 100;
break;
case 'language':
case 'tag_name':
value = value || 'None';
break;
case 'published_at':
value = ( value && value.split('T')[0] ) || 'None';
break;
case 'topics':
value = value.length ? value.join(', ') : 'None';
break;
}
sheet.getRange(currRow, currCol).setValue(value);
currCol++;
});
} else if (!repoName) {
Browser.msgBox("Update complete!");
return;
}
}
Browser.msgBox("Update complete!");
}
/**
* Deletes the script cache.
*/
function deleteCache() {
'use strict';
var cache = CacheService.getScriptCache();
getRepoNames().forEach(function(repoName) {
cache.remove(repoName, '--codecov');
cache.remove(repoName, '--gh-comm');
cache.remove(repoName, '--gh-readme');
cache.remove(repoName, '--gh-repo');
cache.remove(repoName, '--gh-release');
});
}
/*
*
* Helper functions
*
*/
/**
* Get all repo names.
*/
function getRepoNames() {
'use strict';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A:A");
return range.getValues().filter(function(el) { return el && el !== 'Name'; });
}
/**
* Process README contents to look for sections and formatting.
*
* @param readme string - README contents in Markdown format.
*
* @returns {number}
*/
function processReadme(readme) {
'use strict';
var sections = [
'Table of Contents', 'Documentation', 'Installation', 'Getting Started', 'Contribution', 'Support + Feedback',
'Vulnerability Reporting', 'What is Auth0?', 'License'
];
var sectionPoints = '#' === readme[0] ? 1 : 0;
sections.forEach(function(section) {
sectionPoints += ( readme.indexOf('## ' + section) >= 0 ? 1: 0 );
});
return sectionPoints / (sections.length + 1);
}
/**
* GitHub processor constructor.
*
* @param repoName string - Repo name for coverage.
* @param skipCache boolean - Should we skip the script cache for this app?
*
* @constructor
*/
function GitHub(repoName, skipCache) {
'use strict';
this.apiToken = PropertiesService.getUserProperties().getProperty('GITHUB_API_TOKEN');
this.httpParams = { headers : { Authorization : 'token ' + this.apiToken } };
this.repoName = repoName || 'wp-auth0';
this.apiBaseUrl = 'https://api.github.com/repos/auth0/' + this.repoName;
this.skipCache = skipCache || false;
this.cacheExpires = 60; // One minute
}
/**
* Create the cache key to use.
*
* @param key string - Specific key being cached.
*
* @returns {string}
*/
GitHub.prototype.cacheKey = function (key) {
'use strict';
return this.repoName + '--' + key;
};
/**
* Prompt the user for a GitHub token.
*/
GitHub.prototype.tokenCheck = function() {
'use strict';
if (!this.apiToken && !PropertiesService.getUserProperties().getProperty('GITHUB_API_TOKEN')) {
Browser.msgBox('Select Tools > Macros > saveGitHubApiToken and enter your GitHub API token.');
return false;
}
return true;
}
/**
* Get the community profile.
* https://developer.github.com/v3/repos/community/
*
* @param key string - Specific key to get, null/false to return complete object.
*
* @returns {*}
*/
GitHub.prototype.getCommunity = function (key) {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-comm');
var commJson = cache.get(cacheKey);
if (!commJson || this.skipCache ) {
if (!this.tokenCheck()) {
return '';
}
this.httpParams.headers.Accept = 'application/vnd.github.black-panther-preview+json';
commJson = UrlFetchApp.fetch( this.apiBaseUrl + '/community/profile', this.httpParams).getContentText();
if (commJson) {
cache.put(cacheKey, commJson, this.cacheExpires);
}
}
var commData = {};
try {
commData = JSON.parse(commJson);
} catch (e) {
return 'JSON parse error';
}
if (!key) {
return commData;
} else if ('license' === key) {
return commData.files.license && commData.files.license.name;
} else if (['health_percentage', 'description', 'documentation'].indexOf(key) >= 0) {
return commData[key];
} else {
return commData.files[key] && commData.files[key].html_url;
}
};
/**
* Get a single repo.
* https://developer.github.com/v3/repos/#get
*
* @param key string - Specific key to get, null/false to return complete object.
*
* @returns {*}
*/
GitHub.prototype.getRepo = function (key) {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-repo');
var githubJson = cache.get(cacheKey);
if (!githubJson || this.skipCache ) {
if (!this.tokenCheck()) {
return '';
}
githubJson = UrlFetchApp.fetch( this.apiBaseUrl, this.httpParams ).getContentText();
if (githubJson) {
cache.put(cacheKey, githubJson, this.cacheExpires);
}
}
var githubData = {};
try {
githubData = JSON.parse(githubJson);
} catch (e) {
return 'JSON parse error';
}
return key ? githubData[key] : githubData;
};
/**
* Get the latest release from a repo.
* https://developer.github.com/v3/repos/releases/#get-the-latest-release
*
* @returns {*}
*/
GitHub.prototype.getLatestRelease = function (key) {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-release');
var releaseJson = cache.get(cacheKey);
if (!releaseJson || this.skipCache ) {
if (!this.tokenCheck()) {
return '';
}
var params = this.httpParams;
params.muteHttpExceptions = true;
releaseJson = UrlFetchApp.fetch( this.apiBaseUrl + '/releases/latest', params ).getContentText();
if (releaseJson) {
cache.put(cacheKey, releaseJson, this.cacheExpires);
}
}
var releaseData = {};
try {
releaseData = JSON.parse(releaseJson);
} catch (e) {
return 'JSON parse error';
}
return key ? releaseData[key] : releaseData;
};
/**
* Get the repo's view count.
* https://developer.github.com/v3/repos/traffic/#views
*
* @returns {*}
*/
GitHub.prototype.getTraffic = function (key) {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-traffic');
var trafficJson = cache.get(cacheKey);
if (!trafficJson || this.skipCache ) {
if (!this.tokenCheck()) {
return '';
}
var params = this.httpParams;
params.muteHttpExceptions = true;
trafficJson = UrlFetchApp.fetch( this.apiBaseUrl + '/traffic/views', params ).getContentText();
if (trafficJson) {
cache.put(cacheKey, trafficJson, this.cacheExpires);
}
}
var trafficData = {};
try {
trafficData = JSON.parse(trafficJson);
} catch (e) {
return 'JSON parse error';
}
return key ? trafficData[key] : trafficData;
};
/**
* Get topics for a repo.
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
*/
GitHub.prototype.getTopics = function () {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-topics');
var topicsJson = cache.get(cacheKey);
if (!topicsJson || this.skipCache ) {
if (!this.tokenCheck()) {
return '';
}
var params = this.httpParams;
params.headers.Accept = 'application/vnd.github.mercy-preview+json';
var topicsJson = UrlFetchApp.fetch( this.apiBaseUrl + '/topics', params ).getContentText();
if (topicsJson) {
cache.put(cacheKey, topicsJson, this.cacheExpires);
}
}
var topicsData = {};
try {
topicsData = JSON.parse(topicsJson);
} catch (e) {
return 'JSON parse error';
}
return topicsData && topicsData.names;
};
/**
* Get the README file contents.
*/
GitHub.prototype.getReadme = function() {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-readme');
var readmeContent = cache.get(cacheKey);
if (!readmeContent || this.skipCache) {
var readmeUrl = this.getCommunity('readme');
readmeUrl = readmeUrl.replace('github.com', 'raw.githubusercontent.com');
readmeUrl = readmeUrl.replace('blob/master', 'master');
readmeContent = UrlFetchApp.fetch( readmeUrl ).getContentText();
if (readmeContent) {
cache.put(cacheKey, readmeContent, this.cacheExpires);
}
}
return readmeContent;
};
/**
* Check for a Circle or Travis config file.
*/
GitHub.prototype.getCi = function () {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('gh-ci');
var ciApp = cache.get(cacheKey);
if (!ciApp || this.skipCache ) {
ciApp = 'None';
var params = { muteHttpExceptions: true };
var circleResponse = UrlFetchApp.fetch(
'https://github.com/auth0/' + this.repoName + '/tree/master/.circleci/config.yml',
params
);
if ( circleResponse.getResponseCode() < 300 ) {
ciApp = 'Circle';
} else {
var travisResponse = UrlFetchApp.fetch(
'https://github.com/auth0/' + this.repoName + '/tree/master/.travis.yml',
params
);
if ( travisResponse.getResponseCode() < 300 ) {
ciApp = 'Travis';
}
}
cache.put(cacheKey, ciApp, this.cacheExpires);
}
return ciApp;
};
/**
* Codecov processor constructor.
*
* @param repoName string - Repo name for coverage.
* @param skipCache boolean - Should we skip the script cache for this app?
*
* @constructor
*/
function Codecov(repoName, skipCache) {
'use strict';
this.apiToken = PropertiesService.getUserProperties().getProperty('CODECOV_API_TOKEN');
this.repoName = repoName || 'wp-auth0';
this.skipCache = skipCache || false;
this.cacheExpires = 60; // One minute
}
/**
* Create the cache key to use.
*
* @param key string - Specific key being cached.
*
* @returns {string}
*/
Codecov.prototype.cacheKey = function (key) {
'use strict';
return this.repoName + '--' + key;
};
/**
* Prompt the user for a Codecov token.
*/
Codecov.prototype.tokenCheck = function() {
'use strict';
if (!this.apiToken && !PropertiesService.getUserProperties().getProperty('CODECOV_API_TOKEN')) {
Browser.msgBox('Select Tools > Macros > saveCodecovApiToken and enter your Codecov API token.');
return false;
}
return true;
}
/**
* Get coverage percent of last commit in the master branch.
*
* @returns {string}
*/
Codecov.prototype.getCoverage = function () {
'use strict';
var cache = CacheService.getScriptCache();
var cacheKey = this.cacheKey('codecov');
var coverage = cache.get(cacheKey);
if (null === coverage || this.skipCache) {
if (!this.apiToken) {
Browser.msgBox('Add CODECOV_API_TOKEN in Script Editor > File > Project Properties > User Properties tab');
return '';
}
var response = UrlFetchApp.fetch(
'https://codecov.io/api/gh/auth0/' + this.repoName + '?access_token=' + this.apiToken
).getContentText();
var responseData = {};
try {
responseData = JSON.parse(response);
} catch (e) {
return 'JSON parse error';
}
coverage = responseData.commit && responseData.commit.totals && responseData.commit.totals.c;
coverage = coverage || 0;
cache.put(cacheKey, coverage, this.cacheExpires);
}
return coverage;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment