├── Procfile ├── .bowerrc ├── client ├── app │ ├── scripts │ │ ├── client.js │ │ ├── directives │ │ │ ├── userinfo.html │ │ │ └── userinfo.js │ │ ├── compare │ │ │ ├── compare.js │ │ │ └── compare.html │ │ ├── services │ │ │ ├── auth.js │ │ │ ├── dendrogram.js │ │ │ ├── barChart.js │ │ │ ├── newAuth.js │ │ │ ├── dateFormat.js │ │ │ ├── chart.js │ │ │ └── gitapi.js │ │ └── home │ │ │ ├── home.html │ │ │ └── home.js │ ├── .DS_Store │ └── styles │ │ └── style.css ├── .DS_Store ├── app.js └── index.html ├── .DS_Store ├── .gitignore ├── server ├── server.js ├── middleware.js └── firebaseTest.js ├── .idea └── misc.xml ├── index.js ├── bower.json ├── PRESS-RELEASE.md ├── gulpfile.js ├── README.md ├── package.json ├── karma.conf.js ├── test └── gitApi.js ├── CONTRIBUTING.md └── STYLE-GUIDE.md /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/lib/" 3 | } -------------------------------------------------------------------------------- /client/app/scripts/client.js: -------------------------------------------------------------------------------- 1 | // D3 Main js file. 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnygames/GitInsights/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | client/lib 3 | client/app/dist 4 | 5 | .idea 6 | -------------------------------------------------------------------------------- /client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnygames/GitInsights/HEAD/client/.DS_Store -------------------------------------------------------------------------------- /client/app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnygames/GitInsights/HEAD/client/app/.DS_Store -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | require('./middleware.js')(app); 5 | 6 | module.exports = app; -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var app = require('./server/server.js'); 2 | 3 | var port = process.env.PORT || 3000; 4 | 5 | app.listen(port, function() { 6 | console.log('Server Running at port:', port); 7 | }); 8 | -------------------------------------------------------------------------------- /client/app/scripts/directives/userinfo.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

{{user.name}}

6 | {{user.blog}} 7 | {{user.email}} 8 | {{user.company}} 9 | {{user.followers}} 10 | github 11 |

-------------------------------------------------------------------------------- /server/middleware.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyparser = require('body-parser'); 3 | var morgan = require('morgan') 4 | 5 | module.exports = function (app) { 6 | 7 | app.use(bodyparser.json()); 8 | app.use(morgan('dev')); 9 | app.use(express.static(__dirname + '/../client')) 10 | 11 | app.get('/', function (req, res) { 12 | res.sendFile('index.html'); 13 | }); 14 | 15 | } -------------------------------------------------------------------------------- /client/app/scripts/directives/userinfo.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.userinfo', []) 5 | .directive('userInfo', userInfo); 6 | 7 | function userInfo () { 8 | return { 9 | link: link, 10 | restrict: 'E', 11 | templateUrl: 'app/scripts/directives/userinfo.html', 12 | scope: { 13 | user: '=' 14 | } 15 | }; 16 | 17 | function link (scope, ele, attrs) { 18 | } 19 | } 20 | 21 | })(); -------------------------------------------------------------------------------- /client/app/scripts/compare/compare.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.compare', []) 5 | .controller('CompareController', CompareController); 6 | 7 | CompareController.$inject = ['$scope', 'GitApi', 'Auth']; 8 | function CompareController ($scope, GitApi, Auth) { 9 | $scope.userOne = {}; 10 | $scope.userTwo = {}; 11 | $scope.toDo = function(){ 12 | console.log('howdy'); 13 | }; 14 | } 15 | 16 | })(); -------------------------------------------------------------------------------- /client/app/scripts/compare/compare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search 7 | 8 | 9 | 10 | 11 | 12 | 13 | Search 14 | -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | angular.module('gitInsight', [ 2 | 'gitInsight.home', 3 | 'ngRoute', 4 | 'ngResource', 5 | 'gitInsight.gitapi', 6 | 'gitInsight.auth', 7 | 'gitInsight.userinfo', 8 | 'gitInsight.compare', 9 | 'gitInsight.chart', 10 | 'gitInsight.dateFormat', 11 | 'gitInsight.dendrogram', 12 | 'gitInsight.barChart', 13 | // 'gitInsight.newAuth', 14 | 'firebase' 15 | ]) 16 | .config(function($routeProvider, $httpProvider){ 17 | $routeProvider 18 | .when('/', { 19 | templateUrl: 'app/scripts/home/home.html', 20 | controller: 'HomeController' 21 | }) 22 | .when('/compare', { 23 | templateUrl: 'app/scripts/compare/compare.html', 24 | controller: 'CompareController' 25 | }) 26 | .otherwise({ 27 | redirectTo: '/' 28 | }); 29 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitInsights", 3 | "main": "index.js", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/waieez/GitInsights", 6 | "authors": [ 7 | "John Zhang", 8 | "Seunghoon Ko", 9 | "Eli Xian", 10 | "Kate Jefferson", 11 | "Sean Kim", 12 | "John Games", 13 | "David Blanchard" 14 | ], 15 | "description": "Github User Insights", 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "client/bower/", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "angular": "~1.3.14", 26 | "bootstrap": "~3.3.2", 27 | "angular-material": "~0.8.3", 28 | "angular-route": "~1.3.14", 29 | "firebase": "~2.2.2", 30 | "nvd3": "~1.7.1", 31 | "angular-resource": "~1.3.15", 32 | "angularfire": "~1.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | ## GitInsights ## 2 | For Recruiters/Developers looking to find talent. 3 | 4 | ## Summary ## 5 | Quickly visualize/analyze Github user activity. 6 | 7 | ## Problem ## 8 | Finding talent via Github is hard. Github Insights makes it easy to analyse and visualize the information you care about by simply searching their username. 9 | 10 | ## Solution ## 11 | Quickly visualize/analyze Github user activity 12 | 13 | ## Quote from You ## 14 | GitInsight as your talent finder. 15 | 16 | ## How to Get Started ## 17 | Type the github username you are interested in, boom info at a glance. 18 | 19 | ## Customer Quote ## 20 | “I recruited the best engineer with GitInsight! I saw that he actually a lot of experience with Rust, instead of some students just trying to make a greenfield project in Rust.” 21 | 22 | ## Closing and Call to Action ## 23 | GitInsights Today! 24 | 25 | ## Licence 26 | MIT -------------------------------------------------------------------------------- /server/firebaseTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Firebase module 3 | * @type {module|*} 4 | */ 5 | var db = angular.module('Firebase', ['firebase']); 6 | // makes $firebaseObject, $firebaseArray, and $firebaseAuth available 7 | 8 | /** Favorite Factory 9 | * returns the user's list of favorites 10 | */ 11 | db.factory('favoritesList', ['$firebaseObject', 12 | function($firebaseObject) { 13 | return function(username) { 14 | // need reference to user's data ? 15 | var ref = new Firebase('https://gitinsights.firebaseio.com/'); 16 | var profileRef = ref.child(username); 17 | // return synchronized object 18 | return $firebaseObject(profileRef); 19 | }; 20 | } 21 | ]); 22 | 23 | /** 24 | * Firebase controller for favorite list 25 | */ 26 | db.controller('favoriteController', ['$scope', 'favoritesList', 27 | function($scope, favoritesList) { 28 | // make list available to DOM 29 | // need username from auth 30 | $scope.user = 'Guest' + Math.round(Math.random() * 100); 31 | $scope.list = favoritesList($scope.user); 32 | 33 | // 3-way binding, may eliminate need for save function 34 | favoritesList($scope.user).$bindTo($scope, 'favoritesList'); 35 | } 36 | ]); 37 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var concat = require('gulp-concat'); 3 | var uglify = require('gulp-uglify'); 4 | var jshint = require('gulp-jshint'); 5 | var karma = require('karma').server 6 | var nodemon = require('gulp-nodemon'); 7 | 8 | var paths = { 9 | scripts: [ 10 | 'client/*.js', 11 | 'client/app/scripts/**/*.js', 12 | ], 13 | dist: 'client/app/dist' 14 | } 15 | 16 | gulp.task('default', ['lint', 'test', 'build', 'watch']); 17 | 18 | // Lint js files 19 | gulp.task('lint', function () { 20 | gulp.src(paths.scripts) 21 | .pipe(jshint()) 22 | .pipe(jshint.reporter('default')) 23 | }); 24 | 25 | gulp.task('test', function (done) { 26 | karma.start({ 27 | configFile: __dirname + '/karma.conf.js', 28 | singleRun: true 29 | }, done); 30 | }); 31 | 32 | // Uglify and Concat js files 33 | gulp.task('build', function () { 34 | gulp.src(paths.scripts) 35 | .pipe(uglify()) 36 | .pipe(concat('scripts.min.js')) 37 | .pipe(gulp.dest(paths.dist)) 38 | }); 39 | 40 | gulp.task('watch', function () { 41 | gulp.watch(paths.scripts, ['lint', 'test', 'build']); 42 | }); 43 | 44 | gulp.task('serve', function () { 45 | nodemon({ 46 | script: 'index.js', 47 | ext: 'html js', 48 | ignore: ['node_modules'] 49 | }) 50 | .on('change', ['lint', 'test']) 51 | .on('restart', function () { 52 | console.log('restarting server'); 53 | }); 54 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Codeshipped!](https://codeship.com/projects/7cdce540-ab49-0132-e8c7-262a69de4513/status?branch=test) 2 | 3 | # GitInsights! 4 | 5 | Quickly visualize/analyze Github user activity, GitInsights Today! 6 | 7 | ## Team 8 | 9 | - __Product Owner__: Sean Kim 10 | - __Scrum Master__: John Games 11 | - __Development Team Members__: Kate Jefferson & David Blanchard 12 | 13 | ## Table of Contents 14 | 15 | 1. [Usage](#Usage) 16 | 1. [Requirements](#requirements) 17 | 1. [Development](#development) 18 | 1. [Installing Dependencies](#installing-dependencies) 19 | 1. [Tasks](#tasks) 20 | 1. [Team](#team) 21 | 1. [Contributing](#contributing) 22 | 23 | ## Usage 24 | 25 | Finding talent via Github is hard. Github Insights makes it easy to analyse and visualize the information you care about by simply searching their username. 26 | 27 | ## Requirements 28 | 29 | - Node 0.10.x 30 | - Express 4.12.x 31 | - Angular 1.3.x 32 | 33 | ## Development 34 | 35 | ### Installing Dependencies 36 | 37 | If you don't already have bower and npm installed, you'll need to do that first. 38 | After that, cd into the root directory and simply run: 39 | 40 | ```sh 41 | npm install 42 | bower install 43 | ``` 44 | 45 | Bam, cake. 46 | 47 | ### Roadmap 48 | 49 | View the project roadmap [here](LINK_TO_PROJECT_ISSUES) 50 | 51 | 52 | ## Contributing 53 | 54 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitInsights", 3 | "version": "0.0.0", 4 | "description": "Github User Insights", 5 | "main": "index.js", 6 | "dependencies": { 7 | "angular": "^1.3.14", 8 | "angular-mocks": "^1.3.14", 9 | "body-parser": "^1.12.0", 10 | "bower": "^1.3.12", 11 | "express": "^4.12.2", 12 | "firebase": "^2.2.3", 13 | "morgan": "^1.5.1" 14 | }, 15 | "devDependencies": { 16 | "gulp": "^3.8.11", 17 | "gulp-concat": "^2.5.2", 18 | "gulp-jshint": "^1.9.2", 19 | "gulp-nodemon": "^1.0.5", 20 | "gulp-sourcemaps": "^1.5.0", 21 | "gulp-uglify": "^1.1.0", 22 | "karma": "^0.12.31", 23 | "karma-chai": "^0.1.0", 24 | "karma-mocha": "^0.1.10", 25 | "karma-nyan-reporter": "0.0.60", 26 | "karma-phantomjs-launcher": "^0.1.4" 27 | }, 28 | "scripts": { 29 | "test": "echo \"Error: no test specified\" && exit 1", 30 | "start": "node index", 31 | "postinstall": "bower install" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/GitInsights/GitInsights" 36 | }, 37 | "keywords": [ 38 | "Github", 39 | "Insights" 40 | ], 41 | "author": "John Zhang, Seunghoon Ko, Eli Xian, Kate Jefferson, Sean Kim, John Games, David Blanchard", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/GitInsights/GitInsights/issues" 45 | }, 46 | "homepage": "https://github.com/GitInsights/GitInsights" 47 | } 48 | -------------------------------------------------------------------------------- /client/app/scripts/services/auth.js: -------------------------------------------------------------------------------- 1 | //var Firebase = require('server/firebaseTest'); 2 | 3 | //This currently needs to be uncommented in order for the app to authenticate 4 | 5 | (function () { 6 | 'use strict'; 7 | 8 | angular.module('gitInsight.auth', []) 9 | .factory('Auth', Auth); 10 | 11 | Auth.$inject = ['$q']; 12 | function Auth ($q) { 13 | //Uses Firebase only to authenticate via Github 14 | //Authenticated Github users are allowed to make 2000api calls/hr vs 60 for unauthenticated users 15 | 16 | //create an instance to interact with firebase api. 17 | //url refers to Firebase database. 18 | //for more information about Firebase please refer to http://www.firebase.com 19 | var ref = new Firebase("https://boiling-torch-2275.firebaseio.com"); 20 | 21 | var github; 22 | 23 | return { 24 | login: login, 25 | getToken: getToken, 26 | getUsername: getUsername 27 | }; 28 | 29 | function getToken () { 30 | return github && github.accessToken; 31 | } 32 | 33 | function getUsername () { 34 | return github && github.login; 35 | } 36 | 37 | function login () { 38 | //creates a popup for authentication via github 39 | //closes pop after and sets the githubToken for use in GitApi calls 40 | return $q(function (resolve, reject) { 41 | ref.authWithOAuthPopup("github", function (error, authData) { 42 | if (error) { 43 | console.log("Login Failed!", error); 44 | reject(error); 45 | } else { 46 | console.log("Authenticated successfully with payload:", authData); 47 | github = authData.github; 48 | resolve(github); 49 | } 50 | }); 51 | }) 52 | } 53 | } 54 | 55 | })(); 56 | -------------------------------------------------------------------------------- /client/app/scripts/services/dendrogram.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.dendrogram', []) 5 | .factory('Dendrogram', Dendrogram); 6 | 7 | 8 | Dendrogram.$inject = []; 9 | function Dendrogram () { 10 | 11 | return { 12 | dendrogram: dendrogram 13 | }; 14 | 15 | function dendrogram(followerData) { 16 | 17 | var radius = 960 / 2; 18 | 19 | var cluster = d3.layout.cluster() 20 | .size([360, radius - 120]); 21 | 22 | var diagonal = d3.svg.diagonal.radial() 23 | .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); 24 | 25 | var svg = d3.select("#chart5").append("svg") 26 | .attr("width", radius * 2) 27 | .attr("height", radius * 2) 28 | .append("g") 29 | .attr("transform", "translate(" + radius + "," + radius + ")"); 30 | //data = JSON.parse(followerData); 31 | 32 | var nodes = cluster.nodes(followerData); 33 | 34 | var link = svg.selectAll("path.link") 35 | .data(cluster.links(nodes)) 36 | .enter().append("path") 37 | .attr("class", "link") 38 | .attr("d", diagonal); 39 | 40 | var node = svg.selectAll("g.node") 41 | .data(nodes) 42 | .enter().append("g") 43 | .attr("class", "node") 44 | .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) 45 | 46 | node.append("circle") 47 | .attr("r", 4.5); 48 | 49 | node.append("text") 50 | .attr("dy", ".31em") 51 | .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) 52 | .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }) 53 | .text(function(d) { return d.name; }); 54 | }; 55 | d3.select("#chart5").style("height", radius * 2 + "px"); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Mar 13 2015 23:23:21 GMT-0500 (Central Daylight Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'client/lib/firebase/firebase.js', 19 | 'client/lib/angular/angular.js', 20 | 'node_modules/angular-mocks/angular-mocks.js', 21 | 'client/app/scripts/**/*.js', 22 | 'test/*.js', 23 | ], 24 | 25 | 26 | // list of files to exclude 27 | exclude: [ 28 | 'karma.conf.js' 29 | ], 30 | 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | }, 36 | 37 | 38 | // test results reporter to use 39 | // possible values: 'dots', 'progress' 40 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 41 | reporters: ['nyan'], 42 | 43 | 44 | // web server port 45 | port: 9876, 46 | 47 | 48 | // enable / disable colors in the output (reporters and logs) 49 | colors: true, 50 | 51 | 52 | // level of logging 53 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 54 | logLevel: config.LOG_INFO, 55 | 56 | 57 | // enable / disable watching file and executing tests whenever any file changes 58 | autoWatch: true, 59 | 60 | 61 | // start these browsers 62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 63 | browsers: ['PhantomJS'], 64 | 65 | 66 | // Continuous Integration mode 67 | // if true, Karma captures browsers, runs the tests and exits 68 | singleRun: false 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /client/app/scripts/services/barChart.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.barChart', []) 5 | .factory('barChart', barChart); 6 | 7 | 8 | barChart.$inject = []; 9 | function barChart () { 10 | 11 | var usersData = []; 12 | var usersFanSData = []; 13 | 14 | return { 15 | makeBarChart: makeBarChart 16 | }; 17 | 18 | 19 | 20 | function makeBarChart (data) { 21 | /* data comes in the form of [{ 22 | { 23 | email: email, 24 | link: link, 25 | overallAverage: overallAverage, 26 | pastMonthAverage: pastMonthAverage, 27 | total: total, 28 | username: username 29 | } 30 | }] 31 | */ 32 | 33 | var usersContribData = [{"key": "Daily Average, Lifetime", "values": []}, {"key": "Daily Average, Past Month", "values": []}]; 34 | 35 | for (var i = data.length-1; i >= 0; i--) { 36 | console.log('data in barChart - ', data[i]) 37 | usersContribData[0].values.push({"label": data[i].username, "value": data[i].overallAverage}); 38 | usersContribData[1].values.push({"label": data[i].username, "value": data[i].pastMonthAverage}); 39 | } 40 | 41 | // usersFanSData.push(usersContribData); 42 | console.log('usersContribData: ', usersContribData); 43 | 44 | nv.addGraph(function() { 45 | var chart = nv.models.multiBarChart() 46 | .x(function(d) {return d.label;}) 47 | .y(function(d) {return d.value;}) 48 | .reduceXTicks(false) //If 'false', every single x-axis tick label will be rendered. 49 | .showControls(false) //Hide the 'Grouped'/'Stacked' options. 50 | .groupSpacing(0.1) //Distance between each group of bars. 51 | ; 52 | 53 | // chart.xAxis 54 | // .tickFormat(d3.format(',f')); 55 | 56 | chart.yAxis 57 | .tickFormat(d3.format(',.1f')); 58 | 59 | d3.select('#contribsChart svg') 60 | .datum(usersContribData) 61 | .transition() 62 | .duration(350) 63 | .call(chart); 64 | 65 | nv.utils.windowResize(chart.update); 66 | 67 | return chart; 68 | }); 69 | }; 70 | 71 | } 72 | })(); 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /client/app/scripts/services/newAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kate on 3/26/15. 3 | */ 4 | 5 | db.factory('Auth', ['$firebaseAuth', 6 | function($firebaseAuth) { 7 | var ref = new Firebase('https://gitinsights.firebaseio.com'); 8 | return $firebaseAuth(ref); 9 | } 10 | ]); 11 | 12 | db.controller('AuthController', ['$scope', 'Auth', 13 | function($scope, Auth) { 14 | $scope.createUser = function() { 15 | $scope.message = null; 16 | $scope.error = null; 17 | 18 | Auth.$createUser({ 19 | email: $scope.email, 20 | password: $scope.password 21 | }).then(function(userData) { 22 | $scope.message = 'User created with uid:' + userData.uid; 23 | // logs in the newly created user 24 | return Auth.$authWithPassword({ 25 | email: $scope.email, 26 | password: $scope.password 27 | }); 28 | }).then(function(authData) { 29 | console.log('Logged in as:' + authData.uid); 30 | }).catch(function(error) { 31 | $scope.error = error; 32 | }); 33 | }; 34 | 35 | $scope.removeUser = function() { 36 | $scope.message = null; 37 | $scope.error = null; 38 | 39 | Auth.$removeUser({ 40 | email: $scope.email, 41 | password: $scope.password 42 | }).then(function() { 43 | $scope.message = 'User removed.'; 44 | }).catch(function(error) { 45 | $scope.error = error; 46 | }); 47 | } 48 | } 49 | ]); 50 | 51 | //db.controller('AuthController', ['$scope', '$firebaseAuth', 52 | // function($scope, $firebaseAuth) { 53 | // var ref = new Firebase('https://gitinsights.firebaseio.com'); 54 | // 55 | // var auth = $firebaseAuth(ref); 56 | // 57 | // $scope.login = function() { 58 | // $scope.authData = null; 59 | // $scope.error = null; 60 | // 61 | // auth.$authAnonymously().then(function(authData) { 62 | // $scope.authData = authData; 63 | // }).catch(function(error) { 64 | // $scope.error = error; 65 | // }); 66 | // }; 67 | // 68 | // auth.$authWithOAuthPopup('github', function(error, authData) { 69 | // if (error) { 70 | // console.log('Login failed:', error); 71 | // } else { 72 | // console.log('Authenticated successfully with payload:', authData); 73 | // } 74 | // }, 75 | // { // session expires on browser shutdown 76 | // remember: 'sessionOnly', 77 | // scope: 'user,gist' 78 | // }); 79 | // } 80 | //]); -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Git Insights 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /client/app/scripts/services/dateFormat.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.dateFormat', []) 5 | .factory('dateFormat', dateFormat); 6 | 7 | dateFormat.$inject = []; 8 | 9 | function dateFormat () { 10 | return { 11 | ymdFormat: ymdFormat, 12 | dayRange: dayRange, 13 | processContributionData: processContributionData, 14 | determineLastMonth: determineLastMonth, 15 | contribsPerDay: contribsPerDay 16 | }; 17 | 18 | function ymdFormat (ymdDate) { 19 | // "2014-12-10" 20 | // ['2014', '12', '10']; 21 | // {year: 2014, month: 12, day: 10}; 22 | var slicedDate = ymdDate.slice(0,10); 23 | var splitDate = slicedDate.split('-'); 24 | var finalDate = { 25 | year: splitDate[0], 26 | month: splitDate[1], 27 | day: splitDate[2] 28 | }; 29 | 30 | return finalDate; 31 | }; 32 | 33 | function dayRange (startDate, endDate) { 34 | // {year: 2014, month: 12, day: 10}; 35 | // {year: 2015, month: 02, day: 14}; 36 | // year difference * 365 ==> 365 37 | // month difference * 30.5 ==> -305 38 | // day difference == 4 39 | // 64 days -ish 40 | var yearDiff = endDate.year - startDate.year; 41 | var monthDiff = endDate.month - startDate.month; 42 | var dayDiff = endDate.day - startDate.day; 43 | 44 | var range = (yearDiff*356) + Math.floor(monthDiff*30.5) + dayDiff; 45 | 46 | return range; 47 | }; 48 | 49 | function determineLastMonth (allData) { 50 | var dates = [] 51 | 52 | allData.forEach(function(e){ 53 | dates.push(ymdFormat(e.created_at)); 54 | }); 55 | var length = dates.length; 56 | var endDate = dates[0]; 57 | var dateRange = null; 58 | 59 | var subRoutine = function(newStart){ 60 | if(dayRange(newStart, endDate) < 32){ 61 | console.log('in base Case - ', newStart); 62 | dateRange = dayRange(newStart, endDate); 63 | return; 64 | } 65 | var nextDate = dates.pop(); 66 | subRoutine(nextDate); 67 | }; 68 | subRoutine(dates.pop()); 69 | 70 | var contributions = dates.length; 71 | console.log('Last month contributions - ', contributions); 72 | console.log('Last month dateRange - ', dateRange); 73 | var uglyAverage = contributions / 30; 74 | console.log('Last month ugly average - ', uglyAverage); 75 | var pastMonthAverage = uglyAverage.toFixed(2); 76 | 77 | return pastMonthAverage; 78 | }; 79 | 80 | function processContributionData (allData, username) { 81 | var contributions = allData.length 82 | var startDate = ymdFormat(allData[contributions-1].created_at); 83 | var endDate = ymdFormat(allData[0].created_at); 84 | var dateRange = dayRange(startDate, endDate); 85 | var uglyAverage = contributions / dateRange; 86 | var overallAverage = uglyAverage.toFixed(2); 87 | 88 | if(dateRange > 30){ 89 | var pastMonthAverage = determineLastMonth(allData); 90 | } 91 | 92 | var result = { 93 | username: username, 94 | total: contributions, 95 | dateRange: dateRange, 96 | overallAverage: overallAverage, 97 | pastMonthAverage: pastMonthAverage 98 | } 99 | return result; 100 | }; 101 | 102 | // Not currently being used, but it returns an object in the form of {{date1: freq1}, {date2: freq2}} 103 | // Date is the date a contribution was made by a user and freq is the number of contribs the user made that day 104 | function contribsPerDay (allData) { 105 | var dates = {}; 106 | 107 | allData.forEach(function(e){ 108 | var ymdDate = e.created_at.slice(0,10); 109 | dates[ymdDate] = dates[ymdDate] || 0; 110 | var freq = dates[ymdDate]; 111 | dates[ymdDate] = freq++; 112 | dates[ymdDate] = freq++; 113 | }); 114 | console.log('Dates - ', dates); 115 | return dates; 116 | } 117 | } 118 | 119 | })(); 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /client/app/styles/style.css: -------------------------------------------------------------------------------- 1 | /* Custom style */ 2 | 3 | /* 4 | * Globals 5 | */ 6 | 7 | /* Links */ 8 | a, 9 | a:focus, 10 | a:hover { 11 | color: #222; 12 | } 13 | 14 | /* Custom default button */ 15 | .btn-default, 16 | .btn-default:hover, 17 | .btn-default:focus { 18 | color: #333; 19 | text-shadow: none; /* Prevent inheritence from `body` */ 20 | /*background-color: #fff;*/ 21 | border: 1px solid #fff; 22 | } 23 | 24 | .node circle { 25 | fill: #fff; 26 | stroke: steelblue; 27 | stroke-width: 1.5px; 28 | } 29 | 30 | .node { 31 | font: 10px sans-serif; 32 | } 33 | 34 | .link { 35 | fill: none; 36 | stroke: #ccc; 37 | stroke-width: 1.5px; 38 | } 39 | 40 | 41 | /* 42 | * Base structure 43 | */ 44 | 45 | html, 46 | body { 47 | height: 100%; 48 | /*background-color: #333;*/ 49 | } 50 | body { 51 | color: #333; 52 | text-align: center; 53 | text-shadow: 0 1px 3px rgba(0,0,0,.5); 54 | } 55 | 56 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 57 | .site-wrapper { 58 | display: table; 59 | width: 100%; 60 | height: 100%; /* For at least Firefox */ 61 | min-height: 100%; 62 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5); 63 | box-shadow: inset 0 0 100px rgba(0,0,0,.5); 64 | } 65 | .site-wrapper-inner { 66 | display: table-cell; 67 | vertical-align: top; 68 | } 69 | .cover-container { 70 | margin-right: auto; 71 | margin-left: auto; 72 | } 73 | 74 | /* Padding for spacing */ 75 | .inner { 76 | padding: 30px; 77 | } 78 | 79 | 80 | /* 81 | * Header 82 | */ 83 | .masthead-brand { 84 | margin-top: 10px; 85 | margin-bottom: 10px; 86 | } 87 | 88 | .masthead-nav > li { 89 | display: inline-block; 90 | } 91 | .masthead-nav > li + li { 92 | margin-left: 20px; 93 | } 94 | .masthead-nav > li > a { 95 | padding-right: 0; 96 | padding-left: 0; 97 | font-size: 16px; 98 | font-weight: bold; 99 | color: #fff; /* IE8 proofing */ 100 | color: rgba(55,55,55,.75); 101 | border-bottom: 2px solid transparent; 102 | } 103 | .masthead-nav > li > a:hover, 104 | .masthead-nav > li > a:focus { 105 | background-color: transparent; 106 | border-bottom-color: #a9a9a9; 107 | border-bottom-color: rgba(255,255,255,.25); 108 | } 109 | .masthead-nav > .active > a, 110 | .masthead-nav > .active > a:hover, 111 | .masthead-nav > .active > a:focus { 112 | color: #333; 113 | border-bottom-color: #fff; 114 | } 115 | 116 | @media (min-width: 768px) { 117 | .masthead-brand { 118 | float: left; 119 | } 120 | .masthead-nav { 121 | float: right; 122 | } 123 | } 124 | 125 | 126 | /* 127 | * Cover 128 | */ 129 | 130 | .cover { 131 | padding: 0 20px; 132 | } 133 | .cover .btn-lg { 134 | padding: 10px 20px; 135 | font-weight: bold; 136 | } 137 | 138 | 139 | /* 140 | * Footer 141 | */ 142 | 143 | .mastfoot { 144 | color: #999; /* IE8 proofing */ 145 | color: rgba(55,55,55,.5); 146 | } 147 | 148 | 149 | /* 150 | * Affix and center 151 | */ 152 | 153 | @media (min-width: 768px) { 154 | /* Pull out the header and footer */ 155 | .masthead { 156 | position: relative; 157 | top: 0; 158 | } 159 | .mastfoot { 160 | position: relative; 161 | bottom: 0; 162 | } 163 | /* Start the vertical centering */ 164 | .site-wrapper-inner { 165 | /*vertical-align: middle;*/ 166 | } 167 | /* Handle the widths */ 168 | .masthead, 169 | .mastfoot, 170 | .cover-container { 171 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 172 | } 173 | } 174 | 175 | @media (min-width: 992px) { 176 | .masthead, 177 | .mastfoot, 178 | .cover-container { 179 | width: 1100px; 180 | } 181 | } 182 | 183 | #chart { 184 | margin-top: 100px; 185 | clear: both; 186 | padding: 1px; 187 | } 188 | 189 | div .welcome { 190 | margin-top:14%; 191 | } 192 | 193 | /*Table Styling*/ 194 | 195 | .table { 196 | width: 100%; 197 | max-width: 100% 198 | margin-bottom: 2rem; 199 | text-align: left; 200 | vertical-align: top; 201 | 202 | } 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /test/gitApi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe('GitApi', function(){ 4 | 5 | describe('Core', function(){ 6 | 7 | var $httpBackend, GitApi, Auth; 8 | var gitApi = 'https://api.github.com'; 9 | 10 | beforeEach(module('gitInsight.gitapi')); 11 | beforeEach(module('gitInsight.auth')); 12 | beforeEach(inject(function ($injector, _GitApi_, _Auth_){ 13 | $httpBackend = $injector.get('$httpBackend'); 14 | GitApi = _GitApi_; 15 | Auth = _Auth_; 16 | 17 | //resource used by getUserRepos 18 | var _userResource = gitApi + '/users/waieez/repos'; 19 | $httpBackend 20 | .when('GET', _userResource) 21 | .respond([ 22 | { 23 | "full_name": "waieez/GitInsights", 24 | "owner": { 25 | "login":'waieez' 26 | }, 27 | "url": gitApi + '/repos/waieez/GitInsights' 28 | }, 29 | { 30 | "full_name": "waieez/Blog", 31 | "owner": { 32 | "login":'waieez' 33 | }, 34 | "url": gitApi + '/repos/waieez/Blog' 35 | } 36 | ]); 37 | 38 | //getAllWeeklyData will make an api call for each repo 39 | var _repoResource1 = gitApi + '/repos/waieez/GitInsights/stats/contributors'; 40 | $httpBackend 41 | .when('GET', _repoResource1) 42 | .respond([ 43 | { 44 | "weeks": [{}, {}, {}], 45 | "author": {"login":"imskojs"} 46 | }, 47 | { 48 | "weeks": [{}, {}, {}], 49 | "author": {"login":"johnz133"} 50 | }, 51 | { 52 | "weeks": [{}, {}, {}], 53 | "author": {"login":"waieez"} 54 | } 55 | ]); 56 | 57 | var _repoResource2 = gitApi + '/repos/waieez/Blog/stats/contributors'; 58 | $httpBackend 59 | .when('GET', _repoResource2) 60 | .respond([ 61 | { 62 | "weeks": [{}, {}, {}], 63 | "author": {"login":"waieez"} 64 | } 65 | ]); 66 | })); 67 | 68 | afterEach(function () { 69 | $httpBackend.verifyNoOutstandingExpectation(); 70 | $httpBackend.verifyNoOutstandingRequest(); 71 | }); 72 | 73 | it('should return an object with all the required methods', function(){ 74 | expect(GitApi).to.be.an('object'); 75 | expect(GitApi.getUserRepos).to.be.a('function'); 76 | expect(GitApi.getRepoWeeklyData).to.be.a('function'); 77 | expect(GitApi.getAllWeeklyData).to.be.a('function'); 78 | }); 79 | 80 | describe('getUserRepos', function () { 81 | 82 | it("should return a list of a user's repos", function(){ 83 | GitApi.getUserRepos('waieez') 84 | .then(function (repos) { 85 | var username = repos[0].owner.login; 86 | var repoName = repos[0].full_name; 87 | expect(username).to.equal('waieez'); 88 | expect(repoName).to.equal('waieez/GitInsights'); 89 | expect(repos.length).to.equal(2); 90 | }); 91 | $httpBackend.flush(); 92 | }); 93 | 94 | }); 95 | 96 | describe('getRepoWeeklyData', function(){ 97 | 98 | it('should return an array of objects', function () { 99 | var mockRepoObject = {url:gitApi + '/repos/waieez/GitInsights'}; 100 | GitApi.getRepoWeeklyData(mockRepoObject, 'waieez') 101 | .then(function(data){ 102 | var authorName = data.author.login; 103 | var weeksData = data.weeks; 104 | expect(authorName).to.equal('waieez'); 105 | expect(weeksData.length).to.eql(3); 106 | }); 107 | $httpBackend.flush(); 108 | }); 109 | 110 | }); 111 | 112 | describe('getAllWeeklyData', function () { 113 | 114 | it("should return the user's weekly data", function(){ 115 | GitApi.getAllWeeklyData('waieez') 116 | .then(function (result) { 117 | expect(result.length).to.equal(2); 118 | }); 119 | $httpBackend.flush(); 120 | }); 121 | }); 122 | }); 123 | }); -------------------------------------------------------------------------------- /client/app/scripts/services/chart.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.chart', []) 5 | .factory('Chart', Chart); 6 | 7 | 8 | Chart.$inject = []; 9 | function Chart () { 10 | 11 | var usersData = []; 12 | var usersFanSData = []; 13 | 14 | return { 15 | lineGraph: lineGraph, 16 | pieChart: pieChart, 17 | multiBarChart: multiBarChart, 18 | reset: reset 19 | }; 20 | 21 | function lineGraph (data, username) { 22 | var secondsPerYear = 525600 * 60; 23 | var dateNow = new Date() / 1000; //convert to unix 24 | var dateXYearsAgo = dateNow - (secondsPerYear * 1); 25 | 26 | var netAdditions = []; 27 | var unixTimeStamps = []; 28 | var newTimeStamps = []; 29 | 30 | for(var week in data){ 31 | unixTimeStamps.push(+week); 32 | netAdditions.push(data[week].a - data[week].d); 33 | } 34 | var userData = {"key": username + "'s Net Additions", "values": []}; 35 | 36 | for(var i = 0; i < unixTimeStamps.length; i++){ 37 | if (unixTimeStamps[i] > dateXYearsAgo) { 38 | userData.values.push([unixTimeStamps[i], netAdditions[i]]); 39 | } 40 | } 41 | 42 | if(usersData.length >= 2){ 43 | usersData = []; 44 | } 45 | 46 | usersData.push(userData); 47 | console.log('usersData: ', usersData); 48 | console.log('userData: ', userData); 49 | 50 | // nv is a nvd3 library object. (on global scope) 51 | nv.addGraph(function() { 52 | // Creates multi-line graph 53 | var chart = nv.models.lineChart() 54 | .x(function(d) { 55 | // console.log('d[0] - ', d) 56 | return d[0] 57 | 58 | }) 59 | .y(function(d) { 60 | // console.log('d[1] - ', d) 61 | return d[1] 62 | }) 63 | .color(d3.scale.category10().range()) 64 | .useInteractiveGuideline(true); 65 | 66 | // Define x axis 67 | chart.xAxis 68 | // .tickValues(unixTimeStamps) 69 | .tickFormat(function(d) { 70 | return d3.time.format('%x')(new Date(d*1000)) 71 | }); 72 | 73 | // Define y axis 74 | chart.yAxis 75 | .domain(d3.range(netAdditions)) 76 | .tickFormat(d3.format('d')); 77 | 78 | // append defined chart to svg element 79 | d3.select('#chart svg') 80 | .datum(usersData) 81 | .call(chart); 82 | 83 | // resizes graph when window resizes 84 | nv.utils.windowResize(chart.update); 85 | return chart; 86 | }); 87 | console.log('Net Additions - ', netAdditions); 88 | console.log('unixTimeStamps - ', unixTimeStamps); 89 | console.log('newTimeStamps - ', newTimeStamps); 90 | }; 91 | 92 | function pieChart (languages, config) { 93 | // Limits max user comparison = 2 94 | //Changes format from {JavaScript: 676977.4910200321, CSS: 3554.990878681176, HTML: 41.838509316770185, Shell: 4024.4960858041054} 95 | // to [{"key": "One", "value": 222}, ... , {"key": "Last", "value": 222}] 96 | var languageData = d3.entries(languages) 97 | 98 | // Add second pie chart when comparing users. 99 | var chart = config.chart; 100 | 101 | // nvd3 library's pie chart. 102 | nv.addGraph(function() { 103 | var pieChart = nv.models.pieChart() 104 | .x(function(d) { return d.key }) 105 | .y(function(d) { return d.value }) 106 | .showLabels(true) 107 | .labelType("percent"); 108 | 109 | d3.select(chart + " svg") 110 | .datum(languageData) 111 | .transition().duration(350) 112 | .call(pieChart); 113 | 114 | return pieChart; 115 | }); 116 | }; 117 | 118 | function multiBarChart (data, username) { 119 | console.log('this is userFanSData: ', userFanSData); 120 | var userFanSData = [{"key": username + "'s Forks", "values": []}, {"key": username + "'s Stars", "values": []}]; 121 | 122 | for (var i = data.length-1; i >= 0; i--) { 123 | userFanSData[0].values.push({"label": data[i][2], "value": data[i][0]}); // forks stream 124 | userFanSData[1].values.push({"label": data[i][2], "value": data[i][1]}); // stars stream 125 | } 126 | 127 | console.log('userFanSData: ', userFanSData); 128 | 129 | nv.addGraph(function() { 130 | var chart = nv.models.multiBarChart() 131 | .x(function(d) {return d.label;}) 132 | .y(function(d) {return d.value;}) 133 | .reduceXTicks(false) //If 'false', every single x-axis tick label will be rendered. 134 | .rotateLabels(-60) //Angle to rotate x-axis labels. 135 | .showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode. 136 | .groupSpacing(0.1) //Distance between each group of bars. 137 | ; 138 | 139 | //chart.xAxis 140 | //.tickFormat(d3.format(',f')); 141 | 142 | chart.yAxis 143 | .tickFormat(d3.format(',f')); 144 | 145 | d3.select('#chart4 svg') 146 | .datum(userFanSData) 147 | .transition() 148 | .duration(350) 149 | .call(chart); 150 | 151 | nv.utils.windowResize(chart.update); 152 | 153 | return chart; 154 | }); 155 | }; 156 | 157 | // Barbaric reset function 158 | function reset () { 159 | pieChart(); 160 | multiBarChart(); 161 | lineGraph(); 162 | console.log('Reset called'); 163 | }; 164 | } 165 | })(); 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /client/app/scripts/home/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Git Insight

6 | 16 |
17 |
18 | 19 |
20 | 21 |
22 |

Git Insight

23 |

We help recruiters and employers analyze candidate github profiles

24 |

25 | 26 | 27 | 28 | 29 | 30 | Search 31 | 32 |

33 |
34 | 35 | 36 |
37 |

38 | 39 | 40 | 41 | 42 | 43 | Compare Users 44 | 45 |

46 |
47 | 48 |
49 | 50 | 51 |
52 | 53 |
54 | 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 |
76 |
77 |
78 | 79 |
80 | 81 | 88 | 89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 | 98 |
99 | 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
UsernameTotal ContributionsDaily Average, LifetimeDaily Average, Past MonthLinkEmail
{{event.username}}{{event.total}}{{event.overallAverage}}{{event.pastMonthAverage}}GitHub{{event.email}}
127 |
128 |
129 | 130 |
131 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut a namespaced feature branch from master 7 | - bug/... 8 | - feat/... 9 | - test/... 10 | - doc/... 11 | - refactor/... 12 | 1. Make commits to your feature branch. Prefix each commit like so: 13 | - (feat) Added a new feature 14 | - (fix) Fixed inconsistent tests [Fixes #0] 15 | - (refactor) ... 16 | - (cleanup) ... 17 | - (test) ... 18 | - (doc) ... 19 | 1. When you've finished with your fix or feature, Rebase upstream changes into your branch. submit a [pull request][] 20 | directly to master. Include a description of your changes. 21 | 1. Your pull request will be reviewed by another maintainer. The point of code 22 | reviews is to help keep the codebase clean and of high quality and, equally 23 | as important, to help you grow as a programmer. If your code reviewer 24 | requests you make a change you don't understand, ask them why. 25 | 1. Fix any issues raised by your code reviwer, and push your fixes as a single 26 | new commit. 27 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. Do not merge your own commits. 28 | 29 | ## Detailed Workflow 30 | 31 | ### Fork the repo 32 | 33 | Use github’s interface to make a fork of the repo, then add that repo as an upstream remote: 34 | 35 | ``` 36 | git remote add upstream https://github.com/hackreactor-labs/.git 37 | ``` 38 | 39 | ### Cut a namespaced feature branch from master 40 | 41 | Your branch should follow this naming convention: 42 | - bug/... 43 | - feat/... 44 | - test/... 45 | - doc/... 46 | - refactor/... 47 | 48 | These commands will help you do this: 49 | 50 | ``` bash 51 | 52 | # Creates your branch and brings you there 53 | git checkout -b `your-branch-name` 54 | ``` 55 | 56 | ### Make commits to your feature branch. 57 | 58 | Prefix each commit like so 59 | - (feat) Added a new feature 60 | - (fix) Fixed inconsistent tests [Fixes #0] 61 | - (refactor) ... 62 | - (cleanup) ... 63 | - (test) ... 64 | - (doc) ... 65 | 66 | Make changes and commits on your branch, and make sure that you 67 | only make changes that are relevant to this branch. If you find 68 | yourself making unrelated changes, make a new branch for those 69 | changes. 70 | 71 | #### Commit Message Guidelines 72 | 73 | - Commit messages should be written in the present tense; e.g. "Fix continuous 74 | integration script". 75 | - The first line of your commit message should be a brief summary of what the 76 | commit changes. Aim for about 70 characters max. Remember: This is a summary, 77 | not a detailed description of everything that changed. 78 | - If you want to explain the commit in more depth, following the first line should 79 | be a blank line and then a more detailed description of the commit. This can be 80 | as detailed as you want, so dig into details here and keep the first line short. 81 | 82 | ### Rebase upstream changes into your branch 83 | 84 | Once you are done making changes, you can begin the process of getting 85 | your code merged into the main repo. Step 1 is to rebase upstream 86 | changes to the master branch into yours by running this command 87 | from your branch: 88 | 89 | ```bash 90 | git pull --rebase upstream master 91 | ``` 92 | 93 | This will start the rebase process. You must commit all of your changes 94 | before doing this. If there are no conflicts, this should just roll all 95 | of your changes back on top of the changes from upstream, leading to a 96 | nice, clean, linear commit history. 97 | 98 | If there are conflicting changes, git will start yelling at you part way 99 | through the rebasing process. Git will pause rebasing to allow you to sort 100 | out the conflicts. You do this the same way you solve merge conflicts, 101 | by checking all of the files git says have been changed in both histories 102 | and picking the versions you want. Be aware that these changes will show 103 | up in your pull request, so try and incorporate upstream changes as much 104 | as possible. 105 | 106 | You pick a file by `git add`ing it - you do not make commits during a 107 | rebase. 108 | 109 | Once you are done fixing conflicts for a specific commit, run: 110 | 111 | ```bash 112 | git rebase --continue 113 | ``` 114 | 115 | This will continue the rebasing process. Once you are done fixing all 116 | conflicts you should run the existing tests to make sure you didn’t break 117 | anything, then run your new tests (there are new tests, right?) and 118 | make sure they work also. 119 | 120 | If rebasing broke anything, fix it, then repeat the above process until 121 | you get here again and nothing is broken and all the tests pass. 122 | 123 | ### Make a pull request 124 | 125 | Make a clear pull request from your fork and branch to the upstream master 126 | branch, detailing exactly what changes you made and what feature this 127 | should add. The clearer your pull request is the faster you can get 128 | your changes incorporated into this repo. 129 | 130 | At least one other person MUST give your changes a code review, and once 131 | they are satisfied they will merge your changes into upstream. Alternatively, 132 | they may have some requested changes. You should make more commits to your 133 | branch to fix these, then follow this process again from rebasing onwards. 134 | 135 | Once you get back here, make a comment requesting further review and 136 | someone will look at your code again. If they like it, it will get merged, 137 | else, just repeat again. 138 | 139 | Thanks for contributing! 140 | 141 | ### Guidelines 142 | 143 | 1. Uphold the current code standard: 144 | - Keep your code [DRY][]. 145 | - Apply the [boy scout rule][]. 146 | - Follow [STYLE-GUIDE.md](STYLE-GUIDE.md) 147 | 1. Run the [tests][] before submitting a pull request. 148 | 1. Tests are very, very important. Submit tests if your pull request contains 149 | new, testable behavior. 150 | 1. Your pull request is comprised of a single ([squashed][]) commit. 151 | 152 | ## Checklist: 153 | 154 | This is just to help you organize your process 155 | 156 | - [ ] Did I cut my work branch off of master (don't cut new branches from existing feature brances)? 157 | - [ ] Did I follow the correct naming convention for my branch? 158 | - [ ] Is my branch focused on a single main change? 159 | - [ ] Do all of my changes directly relate to this change? 160 | - [ ] Did I rebase the upstream master branch after I finished all my 161 | work? 162 | - [ ] Did I write a clear pull request message detailing what changes I made? 163 | - [ ] Did I get a code review? 164 | - [ ] Did I make any requested changes from that code review? 165 | 166 | If you follow all of these guidelines and make good changes, you should have 167 | no problem getting your changes merged in. 168 | 169 | 170 | 171 | [style guide]: https://github.com/hackreactor-labs/style-guide 172 | [n-queens]: https://github.com/hackreactor-labs/n-queens 173 | [Underbar]: https://github.com/hackreactor-labs/underbar 174 | [curriculum workflow diagram]: http://i.imgur.com/p0e4tQK.png 175 | [cons of merge]: https://f.cloud.github.com/assets/1577682/1458274/1391ac28-435e-11e3-88b6-69c85029c978.png 176 | [Bookstrap]: https://github.com/hackreactor/bookstrap 177 | [Taser]: https://github.com/hackreactor/bookstrap 178 | [tools workflow diagram]: http://i.imgur.com/kzlrDj7.png 179 | [Git Flow]: http://nvie.com/posts/a-successful-git-branching-model/ 180 | [GitHub Flow]: http://scottchacon.com/2011/08/31/github-flow.html 181 | [Squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 182 | -------------------------------------------------------------------------------- /client/app/scripts/home/home.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular.module('gitInsight.home', ['ngMaterial', 'ngMessages']) 5 | .controller('HomeController', HomeController) 6 | //defines the colors 7 | .config( function($mdThemingProvider){ 8 | $mdThemingProvider.theme('docs-dark', 'default') 9 | .primaryPalette('light-blue') 10 | }); 11 | 12 | HomeController.$inject = ['$scope', 'GitApi', 'Auth', 'Chart', 'Dendrogram', '$q', '$timeout', '$http', '$resource', 'dateFormat', 'barChart']; 13 | 14 | function HomeController($scope, GitApi, Auth, Chart, Dendrogram, $q, $timeout, $http, $resource, dateFormat, barChart){ 15 | $scope.github = {}; 16 | $scope.currentUser = {}; 17 | $scope.loaded = false; 18 | $scope.loaded3 = true; 19 | $scope.numUsers = 0; 20 | $scope.gitName = $scope.gitName; 21 | $scope.totalEvents = []; 22 | $scope.userData = []; 23 | $scope.tableFuncCalled = false; 24 | $scope.contribChartCalled = false; 25 | 26 | $scope.login = function(){ 27 | Auth.login() 28 | .then(function (github) { 29 | $scope.github = github; 30 | }); 31 | } 32 | 33 | $scope.getAllWeeklyData = function(username){ 34 | 35 | // first we make a set of queries to get data from all the repo's the user has contributed to. 36 | // the process also tags some metadata to help with chaining 37 | GitApi.getAllWeeklyData(username) 38 | .then(function (data){ 39 | // here we can immediately process the data to draw a line graph of the user's activity 40 | var weeklyData = GitApi.reduceAllWeeklyData(data); 41 | Chart.lineGraph(weeklyData, username); 42 | console.log('weeklydata is ', weeklyData); 43 | $scope.loaded = true; 44 | $scope.currentUser = {}; 45 | return data; 46 | }) 47 | .then(function (data) { 48 | return GitApi.gatherLanguageData(data); 49 | // this returns an array of tuples with the form 50 | // [user contributions to this repo, repo language stats, total repo activity] when it resolves 51 | }) 52 | .then(function (data) { 53 | // this time the data is processed to create a pie chart that estimates 54 | // the % of the each language the user codes in by taking the repo language stats * (user activity / total repo activity) 55 | var languages = GitApi.getUserLanguages(data); 56 | $scope.numUsers++; 57 | $scope.loaded3 = !($scope.loaded3); 58 | 59 | var config = {}; 60 | config.chart = "#chart2" 61 | if($scope.numUsers % 2 === 0){ 62 | config.chart = "#chart3" 63 | } 64 | 65 | Chart.pieChart(languages, config); 66 | }) 67 | .then(function (data) { 68 | var allRepoFanSData = GitApi.getRepoFanS(username); 69 | Chart.multiBarChart(allRepoFanSData, username); 70 | }); 71 | $timeout(function() { 72 | $scope.getUserFollowers(username); 73 | }, 1000); 74 | }; 75 | 76 | // 77 | // 78 | // David's quick access play area to test different functions 79 | // 80 | // 81 | 82 | $scope.makeBarChart = function(){ 83 | var data = $scope.totalEvents; 84 | barChart.makeBarChart(data); 85 | $scope.contribChartCalled = true; 86 | }; 87 | 88 | $scope.resetContribChart = function(){ 89 | $scope.totalEvents = []; 90 | $scope.tableFuncCalled = false; 91 | $scope.contribChartCalled = false; 92 | }; 93 | 94 | $scope.getUserContributionData = function(username){ 95 | var username = $scope.gitName; 96 | if($scope.gitName === undefined){ return; } 97 | 98 | 99 | function getEventsData (username) { 100 | var allEventData = []; 101 | // Github API endpoint for a user's events 102 | var Events = $resource('https://api.github.com/users/:username/events?page=:number') 103 | // Start on page 1 104 | var num = 1; 105 | // recursive subroutine for traversing the paginated results 106 | var pageTraverse = function(num){ 107 | return Events.query({username: username, number: num, access_token: Auth.getToken()}, function(data){ 108 | // base case 109 | // since pages can be up to 30 items in length, if the page has fewer than 30, it's the last page 110 | if(data.length < 30){ 111 | data.forEach(function(singleEvent){ 112 | allEventData.push(singleEvent); 113 | }); 114 | $scope.gitName = ""; 115 | $scope.totalEvents.push(dateFormat.processContributionData(allEventData, username)); 116 | // if contribChart has already been rendered, re-render it with new data 117 | if($scope.contribChartCalled){ 118 | $scope.makeBarChart(); 119 | } 120 | return; 121 | } 122 | // increase num to move to the next page 123 | num ++; 124 | data.forEach(function(singleEvent){ 125 | allEventData.push(singleEvent); 126 | }) 127 | // recurse 128 | pageTraverse(num); 129 | }) 130 | }; 131 | pageTraverse(num); 132 | }; 133 | getEventsData(username); 134 | 135 | function getUserData (username) { 136 | var Events = $resource('https://api.github.com/users/:username') 137 | Events.get({username: username, access_token: Auth.getToken()}, function(data){ 138 | var length = $scope.totalEvents.length - 1; 139 | $scope.totalEvents[length].email = data.email; 140 | $scope.totalEvents[length].link = data.html_url; 141 | $scope.tableFuncCalled = true; 142 | $scope.totalEvents[length].loaded = true; 143 | 144 | }) 145 | } 146 | setTimeout(function(){ getUserData(username); }, 2000); 147 | }; 148 | 149 | // As mentioned in the html, this should be able to add a user to a list of favorites, but not sure how to do that yet. 150 | $scope.addToFavorites = function(username){ 151 | } 152 | 153 | $scope.basicReset = function(){ 154 | // currently clears out both pie charts, if I clear out the lineGraph, then it won't come back up again. 155 | $scope.loaded = false; 156 | Chart.reset() 157 | 158 | }; 159 | 160 | // End of David's Play Area 161 | 162 | $scope.getUserFollowers = function(username) { 163 | 164 | GitApi.getUserFollowers(username) 165 | .then(function (data) { 166 | return GitApi.initialFollowerChain(data); 167 | }) 168 | .then(function (data) { 169 | return GitApi.followerCreation(data); 170 | }) 171 | .then(function (data) { 172 | data.children.forEach(function (entry) { 173 | GitApi.getUserFollowers2(entry.name) 174 | .then(function (newData) { 175 | for (var j = 0; j < newData.length; j++) { 176 | entry.children.push( 177 | { 178 | name: newData[j].login, 179 | children: [] 180 | }); 181 | } 182 | return data; 183 | }) 184 | }) 185 | return data; 186 | }) 187 | .then(function (data) { 188 | $timeout(function () { 189 | Dendrogram.dendrogram(data) 190 | } 191 | , 1000) 192 | }) 193 | } 194 | } 195 | })(); 196 | 197 | -------------------------------------------------------------------------------- /STYLE-GUIDE.md: -------------------------------------------------------------------------------- 1 | ### Indentation 2 | 3 | When writing any block of code that is logically subordinate to the line immediately before and after it, that block should be indented two spaces more than the surrounding lines 4 | 5 | * Do not put any tab characters anywhere in your code. You would do best to stop pressing the tab key entirely. 6 | * Increase the indent level for all blocks by two extra spaces 7 | * When a line opens a block, the next line starts 2 spaces further in than the line that opened 8 | 9 | ```javascript 10 | // good: 11 | if(condition){ 12 | action(); 13 | } 14 | 15 | // bad: 16 | if(condition){ 17 | action(); 18 | } 19 | ``` 20 | 21 | * When a line closes a block, that line starts at the same level as the line that opened the block 22 | ```javascript 23 | // good: 24 | if(condition){ 25 | action(); 26 | } 27 | 28 | // bad: 29 | if(condition){ 30 | action(); 31 | } 32 | ``` 33 | 34 | * No two lines should ever have more or less than 2 spaces difference in their indentation. Any number of mistakes in the above rules could lead to this, but one example would be: 35 | 36 | ```javascript 37 | // bad: 38 | transmogrify({ 39 | a: { 40 | b: function(){ 41 | } 42 | }}); 43 | ``` 44 | 45 | * use sublime's arrow collapsing as a guide. do the collapsing lines seem like they should be 'contained' by the line with an arrow on it? 46 | 47 | 48 | ### Variable names 49 | 50 | * A single descriptive word is best. 51 | 52 | ```javascript 53 | // good: 54 | var animals = ['cat', 'dog', 'fish']; 55 | 56 | // bad: 57 | var targetInputs = ['cat', 'dog', 'fish']; 58 | ``` 59 | 60 | * Collections such as arrays and maps should have plural noun variable names. 61 | 62 | ```javascript 63 | // good: 64 | var animals = ['cat', 'dog', 'fish']; 65 | 66 | // bad: 67 | var animalList = ['cat', 'dog', 'fish']; 68 | 69 | // bad: 70 | var animal = ['cat', 'dog', 'fish']; 71 | ``` 72 | 73 | * Name your variables after their purpose, not their structure 74 | 75 | ```javascript 76 | // good: 77 | var animals = ['cat', 'dog', 'fish']; 78 | 79 | // bad: 80 | var array = ['cat', 'dog', 'fish']; 81 | ``` 82 | 83 | 84 | ### Language constructs 85 | 86 | * Do not use `for...in` statements with the intent of iterating over a list of numeric keys. Use a for-with-semicolons statement in stead. 87 | 88 | ```javascript 89 | // good: 90 | var list = ['a', 'b', 'c'] 91 | for(var i = 0; i < list.length; i++){ 92 | alert(list[i]); 93 | } 94 | 95 | // bad: 96 | var list = ['a', 'b', 'c'] 97 | for(var i in list){ 98 | alert(list[i]); 99 | } 100 | ``` 101 | 102 | * Never omit braces for statement blocks (although they are technically optional). 103 | ```javascript 104 | // good: 105 | for(key in object){ 106 | alert(key); 107 | } 108 | 109 | // bad: 110 | for(key in object) 111 | alert(key); 112 | ``` 113 | 114 | * Always use `===` and `!==`, since `==` and `!=` will automatically convert types in ways you're unlikely to expect. 115 | 116 | ```javascript 117 | // good: 118 | 119 | // this comparison evaluates to false, because the number zero is not the same as the empty string. 120 | if(0 === ''){ 121 | alert('looks like they\'re equal'); 122 | } 123 | 124 | // bad: 125 | 126 | // This comparison evaluates to true, because after type coercion, zero and the empty string are equal. 127 | if(0 == ''){ 128 | alert('looks like they\'re equal'); 129 | } 130 | ``` 131 | 132 | * Don't use function statements for the entire first half of the course. They introduce a slew of subtle new rules to how the language behaves, and without a clear benefit. Once you and all your peers are expert level in the second half, you can start to use the more (needlessly) complicated option if you like. 133 | 134 | ```javascript 135 | // good: 136 | var go = function(){...}; 137 | 138 | // bad: 139 | function stop(){...}; 140 | ``` 141 | 142 | 143 | ### Semicolons 144 | 145 | * Don't forget semicolons at the end of lines 146 | 147 | ```javascript 148 | // good: 149 | alert('hi'); 150 | 151 | // bad: 152 | alert('hi') 153 | ``` 154 | 155 | * Semicolons are not required at the end of statements that include a block--i.e. `if`, `for`, `while`, etc. 156 | 157 | 158 | ```javascript 159 | // good: 160 | if(condition){ 161 | response(); 162 | } 163 | 164 | // bad: 165 | if(condition){ 166 | response(); 167 | }; 168 | ``` 169 | 170 | * Misleadingly, a function may be used at the end of a normal assignment statement, and would require a semicolon (even though it looks rather like the end of some statement block). 171 | 172 | ```javascript 173 | // good: 174 | var greet = function(){ 175 | alert('hi'); 176 | }; 177 | 178 | // bad: 179 | var greet = function(){ 180 | alert('hi'); 181 | } 182 | ``` 183 | 184 | # Supplemental reading 185 | 186 | ### Code density 187 | 188 | * Conserve line quantity by minimizing the number lines you write in. The more concisely your code is written, the more context can be seen in one screen. 189 | * Conserve line length by minimizing the amount of complexity you put on each line. Long lines are difficult to read. Rather than a character count limit, I recommend limiting the amount of complexity you put on a single line. Try to make it easily read in one glance. This goal is in conflict with the line quantity goal, so you must do your best to balance them. 190 | 191 | ### Comments 192 | 193 | * Provide comments any time you are confident it will make reading your code easier. 194 | * Be aware that comments come at some cost. They make a file longer and can drift out of sync with the code they annotate. 195 | * Comment on what code is attempting to do, not how it will achieve it. 196 | * A good comment is often less effective than a good variable name. 197 | 198 | 199 | ### Padding & additional whitespace 200 | 201 | * Generally, we don't care where you put extra spaces, provided they are not distracting. 202 | * You may use it as padding for visual clarity. If you do though, make sure it's balanced on both sides. 203 | 204 | ```javascript 205 | // optional: 206 | alert( "I chose to put visual padding around this string" ); 207 | 208 | // bad: 209 | alert( "I only put visual padding on one side of this string"); 210 | ``` 211 | 212 | * You may use it to align two similar lines, but it is not recommended. This pattern usually leads to unnecessary edits of many lines in your code every time you change a variable name. 213 | 214 | ```javascript 215 | // discouraged: 216 | var firstItem = getFirst (); 217 | var secondItem = getSecond(); 218 | ``` 219 | 220 | * Put `else` and `else if` statements on the same line as the ending curly brace for the preceding `if` block 221 | ```javascript 222 | // good: 223 | if(condition){ 224 | response(); 225 | }else{ 226 | otherResponse(); 227 | } 228 | 229 | // bad: 230 | if(condition){ 231 | response(); 232 | } 233 | else{ 234 | otherResponse(); 235 | } 236 | ``` 237 | 238 | 239 | 240 | ### Working with files 241 | 242 | * Do not end a file with any character other than a newline. 243 | * Don't use the -a or -m flags for `git commit` for the first half of the class, since they conceal what is actually happening (and do slightly different things than most people expect). 244 | 245 | ```shell 246 | # good: 247 | > git add . 248 | > git commit 249 | [save edits to the commit message file using the text editor that opens] 250 | 251 | # bad: 252 | > git commit -a 253 | [save edits to the commit message file using the text editor that opens] 254 | 255 | # bad: 256 | > git add . 257 | > git commit -m "updated algorithm" 258 | ``` 259 | 260 | 261 | ### Opening or closing too many blocks at once 262 | 263 | * The more blocks you open on a single line, the more your reader needs to remember about the context of what they are reading. Try to resolve your blocks early, and refactor. A good rule is to avoid closing more than two blocks on a single line--three in a pinch. 264 | 265 | ```javascript 266 | // avoid: 267 | _.ajax(url, {success: function(){ 268 | // ... 269 | }}); 270 | 271 | // prefer: 272 | _.ajax(url, { 273 | success: function(){ 274 | // ... 275 | } 276 | }); 277 | ``` 278 | 279 | 280 | ### Variable declaration 281 | 282 | * Use a new var statement for each line you declare a variable on. 283 | * Do not break variable declarations onto mutiple lines. 284 | * Use a new line for each variable declaration. 285 | * See http://benalman.com/news/2012/05/multiple-var-statements-javascript/ for more details 286 | 287 | ```javascript 288 | // good: 289 | var ape; 290 | var bat; 291 | 292 | // bad: 293 | var cat, 294 | dog 295 | 296 | // use sparingly: 297 | var eel, fly; 298 | ``` 299 | 300 | ### Capital letters in variable names 301 | 302 | * Some people choose to use capitalization of the first letter in their variable names to indicate that they contain a [class](http://en.wikipedia.org/wiki/Class_(computer_science\)). This capitalized variable might contain a function, a prototype, or some other construct that acts as a representative for the whole class. 303 | * Optionally, some people use a capital letter only on functions that are written to be run with the keyword `new`. 304 | * Do not use all-caps for any variables. Some people use this pattern to indicate an intended "constant" variable, but the language does not offer true constants, only mutable variables. 305 | 306 | 307 | ### Minutia 308 | 309 | * Don't rely on JavaScripts implicit global variables. If you are intending to write to the global scope, export things to `window.*` explicitly instead. 310 | 311 | ```javascript 312 | // good: 313 | var overwriteNumber = function(){ 314 | window.exported = Math.random(); 315 | }; 316 | 317 | // bad: 318 | var overwriteNumber = function(){ 319 | exported = Math.random(); 320 | }; 321 | ``` 322 | 323 | * For lists, put commas at the end of each newline, not at the beginning of each item in a list 324 | 325 | ```javascript 326 | // good: 327 | var animals = [ 328 | 'ape', 329 | 'bat', 330 | 'cat' 331 | ]; 332 | 333 | // bad: 334 | var animals = [ 335 | 'ape' 336 | , 'bat' 337 | , 'cat' 338 | ]; 339 | ``` 340 | 341 | * Avoid use of `switch` statements altogether. They are hard to outdent using the standard whitespace rules above, and are prone to error due to missing `break` statements. See [this article](http://ericleads.com/2012/12/switch-case-considered-harmful/) for more detail. 342 | 343 | * Prefer single quotes around JavaScript strings, rather than double quotes. Having a standard of any sort is preferable to a mix-and-match approach, and single quotes allow for easy embedding of HTML, which prefers double quotes around tag attributes. 344 | 345 | ```javascript 346 | // good: 347 | var dog = 'dog'; 348 | var cat = 'cat'; 349 | 350 | // acceptable: 351 | var dog = "dog"; 352 | var cat = "cat"; 353 | 354 | // bad: 355 | var dog = 'dog'; 356 | var cat = "cat"; 357 | ``` 358 | 359 | 360 | ### HTML 361 | 362 | * Do not use ids for html elements. Use a class instead. 363 | 364 | ```html 365 | 366 | 367 | 368 | 369 | 370 | ``` 371 | 372 | * Do not include a `type=text/javascript"` attribute on script tags 373 | 374 | ```html 375 | 376 | 377 | 378 | 379 | 380 | ``` 381 | -------------------------------------------------------------------------------- /client/app/scripts/services/gitapi.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | angular.module('gitInsight.gitapi', []) 5 | .factory('GitApi', GitApi); 6 | 7 | GitApi.$inject = ['$q', '$http', '$resource', 'Auth']; 8 | function GitApi ($q, $http, $resource, Auth) { 9 | 10 | var gitApi = 'https://api.github.com/'; 11 | var usersRepos = {}; 12 | var repoFanS = []; 13 | var repoForks = []; 14 | var repoStars = []; 15 | 16 | return { 17 | reduceAllWeeklyData: reduceAllWeeklyData, 18 | getAllWeeklyData: getAllWeeklyData, 19 | getRepoWeeklyData: getRepoWeeklyData, 20 | getUserRepos: getUserRepos, 21 | getRepoFanS: getRepoFanS, 22 | getUserContact: getUserContact, 23 | gatherLanguageData: gatherLanguageData, 24 | getUserLanguages: getUserLanguages, 25 | getEventsData: getEventsData, 26 | getUserFollowers: getUserFollowers, 27 | getUserFollowers2: getUserFollowers2, 28 | followerObj: followerObj, 29 | initialFollowerChain: initialFollowerChain, 30 | followerCreation: followerCreation 31 | }; 32 | 33 | //a week is an array of objects 34 | //each object is in the form of {additions: #, deletions #, week:#(UNIX time)} 35 | //we extract the additions and deletions from each data object for each week, from each repo 36 | //we return an array of reduced week objects to graph the total additions/deletions 37 | function reduceAllWeeklyData (array, username) { 38 | var reduced = {}; 39 | console.log('Arraylength - ', array.length) 40 | array.forEach(function (result) { 41 | if(result !== undefined){ 42 | console.log('Result - ', result); 43 | result.weeks.forEach(function (data) { 44 | var week = data.w; 45 | for (var key in data) { 46 | reduced[week] = reduced[week] || {}; 47 | reduced[week][key] = (reduced[week][key] || 0) + data[key]; 48 | delete reduced[week].w; 49 | } 50 | }); 51 | } 52 | }); 53 | return reduced; 54 | } 55 | 56 | //returns data from each api call 57 | //after all successfully resolves 58 | function getAllWeeklyData (username) { 59 | return getUserRepos(username) 60 | .then(function (repos) { 61 | var promises = repos.map(function (repo) { 62 | return getRepoWeeklyData(repo, username); 63 | }); 64 | return $q.all(promises); 65 | }); 66 | } 67 | 68 | function get (url, params) { 69 | //Auth.getToken() retrieves the gitToken when a user authenticates with 70 | //firebase's Github Provider 71 | 72 | //perhaps extend params with given input 73 | params = params || {access_token: Auth.getToken()}; 74 | return $http({ 75 | method: 'GET', 76 | url: url, 77 | params: params 78 | }); 79 | } 80 | 81 | 82 | // David Testing Area!!! 83 | // Please beware 84 | 85 | function getEventsData (username) { 86 | var Events = $resource('https://api.github.com/users/:username/events?page=:number') 87 | var num = 1; 88 | var allEventData = []; 89 | 90 | var pageTraverse = function(num){ 91 | return Events.query({username: username, number: num}, function(data){ 92 | if(data.length < 30){ 93 | // data.forEach(function(singleEvent){ 94 | // allEventData.push(singleEvent); 95 | // }); 96 | // console.log('last page of results - ', allEventData); 97 | 98 | return; 99 | } 100 | num ++; 101 | console.log('num - ', num); 102 | // console.log('In query - ', data); 103 | // data.forEach(function(singleEvent){ 104 | // allEventData.push(singleEvent); 105 | // }) 106 | pageTraverse(num); 107 | }).$promise.then(function(someData){ 108 | someData.forEach(function(hubEvent){ 109 | allEventData.push(hubEvent); 110 | }); 111 | console.log('Some Data - ', allEventData); 112 | }) 113 | }; 114 | 115 | return pageTraverse(num); 116 | 117 | // console.log('what about me - ', allEventData) 118 | // return allEventData 119 | 120 | 121 | } 122 | 123 | // End of David Testing Area 124 | // Proceed normally 125 | 126 | 127 | //returns an array of additions/deletions and commits 128 | //made by a user for a given repo 129 | function getRepoWeeklyData (repo, username) { 130 | var contributors = repo.url + '/stats/contributors'; 131 | 132 | return get(contributors).then(function (res) { 133 | var numContributors = res.data.length; 134 | //if there are multiple contributors for this repo, 135 | //we need to find the one that matches the queried user 136 | for(var i = 0; i < numContributors; i++){ 137 | if(res.data[i].author.login === username) { 138 | var data = res.data[i]; 139 | //we attach some metadata that will help us with chaining these queries 140 | data.url = repo.url; 141 | data.numContributors = numContributors; 142 | return data; 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function getUserRepos (username) { 149 | //if cached, return repo list as promise 150 | if (usersRepos[username]) { 151 | return $q(function (resolve, reject) { 152 | return resolve(usersRepos[username]); 153 | }); 154 | } 155 | 156 | //else, fetch via api 157 | //currently only fetches repos owned by user 158 | //TODO: Fetch all repos user has contributed to 159 | var userRepos = gitApi + 'users/' + username + '/repos'; 160 | return get(userRepos).then(function (res){ 161 | var repos = res.data; 162 | var username = res.data[0].owner.login; 163 | usersRepos[username] = repos; 164 | return usersRepos[username]; 165 | }); 166 | } 167 | 168 | function getForks (username) { 169 | repoForks = []; 170 | var allRepos = usersRepos[username]; 171 | for (var i = allRepos.length-1; i >= 0; i--) { 172 | repoForks.push(allRepos[i].forks_count); 173 | } 174 | console.log('repoForks: ', repoForks); 175 | return repoForks; 176 | } 177 | 178 | function getStars (username) { 179 | repoStars = []; 180 | var allRepos = usersRepos[username]; 181 | for (var i = allRepos.length-1; i >= 0; i--) { 182 | repoStars.push(allRepos[i].stargazers_count); 183 | } 184 | console.log('stars: ', repoStars); 185 | return repoStars; 186 | } 187 | 188 | // gather repo name, stars and forks to send to chart 189 | function getRepoFanS (username) { 190 | repoFanS = []; 191 | var allRepos = usersRepos[username]; 192 | getForks(username); 193 | getStars(username); 194 | for (var i = allRepos.length-1; i >= 0; i--) { 195 | repoFanS.push([repoForks[i], repoStars[i], allRepos[i].name]); 196 | } 197 | return repoFanS; 198 | } 199 | 200 | function getUserContact (username) { 201 | var userContact = gitApi + "users/" + username; 202 | return get(userContact).then(function (res) { 203 | return res.data; 204 | }); 205 | } 206 | 207 | // In order to get an idea of the user's language use, 208 | // we first supply information about all repos the user has contributed to. 209 | 210 | // For each repo, we make at most two requests, 211 | // getLanguageStats gathers the language statstic for that repo, 212 | // if the user is the sole contributor for the repo, 213 | // we can add the language stat directly to the final result 214 | // else, getCodeFrequency gets the repo's data for weekly additions/deletions 215 | // the ratio between the user's and the repo's net additions is used to estimate 216 | // the portion the user has contributed to the repo in each language. 217 | 218 | // This approximation strives to reduce the number of api calls to Github 219 | // while giving a reasonable estimate of the user's language use. 220 | 221 | // Please let us know if there is a better way. 222 | 223 | function gatherLanguageData (data) { 224 | var promises = data.map(function (repo) { 225 | if (repo) { 226 | var requests = [repo]; 227 | requests[1] = getLanguageStats(repo); 228 | 229 | //only get code frequency if the repo has multiple contibutors 230 | //otherwise we can just add the languageStat directly. 231 | if(repo.numContributors > 1) { 232 | requests[2] = getCodeFrequency(repo); 233 | } 234 | 235 | return $q.all(requests); 236 | } else { 237 | return []; 238 | } 239 | }); 240 | //$q is angular's light version of the q promise library 241 | //each api call executes asynchronously, 242 | //we return only when all of them have resolved 243 | return $q.all(promises); 244 | } 245 | 246 | // Once all the requests have been resolved, we can sum the values 247 | // across all repos and get an estimate of the user's language use 248 | // based on the total number of bytes per language. 249 | 250 | function getUserLanguages (repos) { 251 | var squashed = {}; 252 | repos.forEach(function (repo) { 253 | var result = estimateUserContribution(repo); 254 | if (result) { 255 | for (var language in result) { 256 | if (squashed[language]) { 257 | squashed[language] += result[language]; 258 | } else { 259 | squashed[language] = result[language]; 260 | } 261 | } 262 | } 263 | }); 264 | return squashed; 265 | } 266 | 267 | //returns an object representing the number of bytes 268 | //each language used in this repo uses. 269 | function getLanguageStats (repo) { 270 | var repoLanguages = repo.url + '/languages' 271 | return get(repoLanguages).then(function (res) { 272 | return res.data; 273 | }); 274 | } 275 | 276 | //returns an array of arrays 277 | //each subarray contains information about the total number of additions/deletions 278 | //for a given week made in this repo 279 | function getCodeFrequency (repo) { 280 | var repoCodeFreq = repo.url + '/stats/code_frequency'; 281 | return get(repoCodeFreq).then(function (res) { 282 | return res.data; 283 | }); 284 | } 285 | 286 | function estimateUserContribution (repo) { 287 | var result = {}; 288 | 289 | // no data on repo 290 | if (repo.length === 0){ 291 | return null; 292 | } 293 | 294 | // no request for contributor data, 295 | // user is sole contributor 296 | // return entire languageStat 297 | if (!repo[2]) { 298 | return repo[1]; 299 | } 300 | 301 | var weeklyData = repo[0].weeks; 302 | var languageStats = repo[1]; 303 | var codeFreq = repo[2]; 304 | 305 | var userNetAdditions = 0; 306 | var repoNetAdditions = 0; 307 | 308 | //weeklyData is an array of week objects 309 | //with the format {additions:#, deletions:#, week:#(UNIX Timestamp)} 310 | weeklyData.forEach(function (week) { 311 | userNetAdditions += (week.a - week.d); 312 | }); 313 | 314 | //codeFreq is is an array of arrays 315 | //with the format [timestamp, additions, deletions] 316 | codeFreq.forEach(function (week) { 317 | repoNetAdditions += (week[1] - week[2]); 318 | }); 319 | 320 | var ratio = (userNetAdditions/repoNetAdditions); 321 | 322 | for (var key in languageStats) { 323 | result[key] = languageStats[key] * ratio; 324 | } 325 | 326 | return result; 327 | } 328 | 329 | function getUserFollowers (username) { 330 | var followers = gitApi + 'users/' + username + '/followers'; 331 | return get(followers).then(function (res) { 332 | return res.data; 333 | }); 334 | } 335 | 336 | function getUserFollowers2 (username) { 337 | var followers = gitApi + 'users/' + username + '/followers'; 338 | return get(followers).then(function (res) { 339 | return res.data; 340 | }); 341 | } 342 | 343 | function followerObj (username) { 344 | var followers = gitApi + 'users/' + username + '/followers'; 345 | var username = username; 346 | var tempData = { 347 | root: username, 348 | children: [] 349 | }; 350 | 351 | return getUserFollowers(username) 352 | .then(function (data) { 353 | var holder = []; 354 | //var temp = []; 355 | for (var i = 0; i < data.length; i++) { 356 | getUserFollowers(data[i]['login']) 357 | .then(function (data) { 358 | //console.log(data, 'this is data') 359 | var temp = holder.concat(data); 360 | //console.log(tempData, 'this is tempData'); 361 | tempData.children.push({ 362 | name: data[i]['login'], 363 | children: temp 364 | }); 365 | }); 366 | } 367 | return tempData; 368 | }); 369 | } 370 | 371 | function initialFollowerChain (array) { 372 | var testData = { 373 | name: 'Current User', 374 | children: [] 375 | } 376 | for (var i = 0; i < array.length; i++) { 377 | testData.children.push(array[i]); 378 | } 379 | return testData; 380 | } 381 | 382 | function followerCreation (obj) { 383 | for (var i = 0; i < obj.children.length; i++) { 384 | var newUser = { 385 | name: obj.children[i].login, 386 | children: [] 387 | } 388 | obj.children[i] = newUser; 389 | } 390 | return obj; 391 | } 392 | } 393 | })(); 394 | --------------------------------------------------------------------------------