Last active
April 8, 2016 16:37
-
-
Save caesarsol/05d3bee9f75aa35fe7e2390fd49a2f58 to your computer and use it in GitHub Desktop.
BusFactor experiments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//const fetch = require('node-fetch'); | |
const _ = require('lodash'); | |
const moment = require('moment'); | |
const process = require('process'); | |
const d3 = require('d3'); | |
Object.prototype.inspect = function(fn = (x => x)) { console.log(fn(this)); return this; }; | |
const repos = [ | |
'clojure/clojurescript', | |
'facebook/react', | |
'Homebrew/homebrew', | |
'torvalds/linux', | |
]; | |
repos.map(fetchContributors).forEach(fetchedRepo => { | |
fetchedRepo.then((list) => { | |
busFactor(list); | |
const graphData = list.map((e, i) => ({y: e.percentNorm, x: i, user: e.login}) ) | |
drawRepoGraph(graphData, list.name) | |
}); | |
}); | |
function userRepoContributions(user) { | |
// deletions are more valuable, because they imply refactorings and/or | |
// better understanding of the codebase | |
return user.weeks.reduce((accu, n) => { | |
const timestamp = parseInt(n.w); | |
const week = moment.unix(timestamp); | |
const reference = moment().subtract(1, 'year'); | |
const diff = moment.duration(week.diff(reference)).months(); | |
const coeff = diff < 1 ? 0.5 : Math.exp(diff / 4); | |
return accu + (n.a + n.c + n.d * 2) * coeff; | |
}, 0); | |
} | |
function fetchContributors(repo) { | |
const url = `https://api.github.com/repos/${repo}/stats/contributors`; | |
return fetch(url) | |
.then((res) => res.json()) | |
.then((json) => { | |
const stats = json.map((user) => ({ | |
login: user.author.login, | |
contributions: userRepoContributions(user) | |
})); | |
const sorted = _.sortBy(stats, 'contributions'); | |
const sum = _.sum(sorted.map(x => x.contributions)); | |
const biggest = _.reverse(_.takeRight(sorted, 10)); | |
const list = biggest.map(x => { | |
x.percent = (x.contributions * 100.0 / sum).toFixed(2); | |
return x; | |
}) | |
const norm = _.sum(list.map(e => parseInt(e.percent))); | |
list.forEach(e => { e.percentNorm = e.percent / norm * 100 }); // yeah, mutating in-place | |
list.name = repo; | |
return list; | |
}); | |
} | |
function busFactor(list) { | |
average(list.map(e => e.percentNorm)).inspect(x => `Avg: ${x}`); | |
let std = standardDeviation(list.map(e => e.percentNorm)).inspect(x => `StdDev: ${x}`); | |
return (std * ((list.length - 1) / list.length)).inspect(x => `${x}`); | |
} | |
function standardDeviation(values){ | |
var avg = average(values); | |
var squareDiffs = values.map(function(value){ | |
var diff = value - avg; | |
var sqrDiff = Math.pow(diff, 2); | |
return sqrDiff; | |
}); | |
var avgSquareDiff = average(squareDiffs); | |
return Math.sqrt(avgSquareDiff); | |
} | |
function average(data){ | |
return _.sum(data) / data.length; | |
} | |
function drawRepoGraph(data, name) { | |
var margin = {top: 40, right: 50, bottom: 100, left: 30}; | |
var width = 200 - margin.left - margin.right; | |
var height = 200 - margin.top - margin.bottom; | |
var x = d3.scale.linear() | |
.domain([d3.min(data, d => d.x), d3.max(data, d => d.x)]) | |
.range([0, width]); | |
var y = d3.scale.linear() | |
.domain([d3.min(data, d => d.y), d3.max(data, d => d.y)]) | |
.range([height, 0]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient("bottom"); | |
var svg = d3.select("body") | |
.append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", `translate(${margin.left}, ${margin.top})`); | |
var bar = svg.selectAll(".bar") | |
.data(data) | |
.enter().append("g") | |
.style("fill", "#B4B4D6") | |
.attr("transform", d => `translate(${x(d.x)}, ${y(d.y)})`); | |
bar.append("rect") | |
.attr("x", 1) | |
.attr("width", width/15) | |
.attr("height", d => height - y(d.y)); | |
/* | |
bar.append("text") | |
.attr("y", -10) | |
.attr("x", 20) | |
.attr("text-anchor", "middle") | |
.text(d => d3.format(",.2f")(d.y)); | |
bar.append("text") | |
.attr("transform", d => `translate(${0}, ${height - y(d.y) + 20}) rotate(90)`) | |
.attr("dx", -10) | |
.attr("dy", -15) | |
.style("text-anchor", "start") | |
.text(d => d.user); | |
*/ | |
svg.append("text") | |
.text(name) | |
.attr("x", 10) | |
.attr("y", 10) | |
.style("text-anchor", "start") | |
.style("fill", "#7A7A94") | |
.style("font-family", "sans-serif"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment