├── log
└── .keep
├── app
├── mailers
│ └── .keep
├── assets
│ ├── images
│ │ ├── .keep
│ │ └── github.png
│ ├── stylesheets
│ │ ├── static.css.scss
│ │ ├── issues.css.scss
│ │ ├── webhooks.css.scss
│ │ ├── repositories.css.scss
│ │ ├── bounties.css.scss
│ │ ├── coders.css.scss
│ │ ├── application.css.scss
│ │ └── top4.css.scss
│ └── javascripts
│ │ ├── issues.js.coffee
│ │ ├── webhooks.js.coffee
│ │ ├── jquery.readyselector.js
│ │ ├── application.js
│ │ ├── bounties.js.coffee
│ │ ├── repositories.js.coffee
│ │ ├── coders.js.coffee
│ │ └── top4.js.coffee
├── models
│ ├── concerns
│ │ ├── .keep
│ │ ├── repo_filters.rb
│ │ ├── schwarm.rb
│ │ └── bounty_points.rb
│ ├── git_identity.rb
│ ├── issue.rb
│ ├── commit.rb
│ ├── coder.rb
│ ├── repository.rb
│ └── bounty.rb
├── controllers
│ ├── concerns
│ │ ├── .keep
│ │ ├── slack_webhook.rb
│ │ └── burndown_chart.rb
│ ├── application_controller.rb
│ ├── scoreboard_controller.rb
│ ├── coders_controller.rb
│ ├── repositories_controller.rb
│ ├── coders
│ │ └── omniauth_callbacks_controller.rb
│ ├── bounties_controller.rb
│ ├── top4_controller.rb
│ └── webhooks_controller.rb
├── helpers
│ ├── top4_helper.rb
│ ├── issues_helper.rb
│ ├── webhooks_helper.rb
│ ├── repositories_helper.rb
│ ├── coders_helper.rb
│ ├── bounties_helper.rb
│ └── application_helper.rb
├── views
│ ├── repositories
│ │ ├── index.html.erb
│ │ ├── _repository.html.erb
│ │ ├── _scoreboard.html.erb
│ │ └── show.html.erb
│ ├── scoreboard
│ │ └── index.html.erb
│ ├── top4
│ │ ├── _issue.html.erb
│ │ ├── _issue_closed.html.erb
│ │ └── show.html.erb
│ ├── coders
│ │ ├── _scoreboard.html.erb
│ │ ├── _coder.html.erb
│ │ └── show.html.erb
│ ├── bounties
│ │ ├── update_or_create.js.coffee
│ │ └── index.html.erb
│ ├── application
│ │ └── _flash.html.erb
│ ├── pages
│ │ └── faq.html.erb
│ └── layouts
│ │ └── application.html.erb
├── services
│ └── slack.rb
└── actions
│ └── publish_bounty.rb
├── lib
├── assets
│ └── .keep
├── tasks
│ └── .keep
├── capistrano
│ └── tasks
│ │ ├── logs.cap
│ │ └── webhooks.cap
└── active_record_extensions.rb
├── public
├── favicon.ico
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── .ruby-version
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ ├── stylesheets
│ ├── .keep
│ └── sb-admin.css
│ └── font-awesome
│ ├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
│ ├── less
│ ├── fixed-width.less
│ ├── core.less
│ ├── bordered-pulled.less
│ ├── rotated-flipped.less
│ ├── larger.less
│ ├── list.less
│ ├── font-awesome.less
│ ├── stacked.less
│ ├── path.less
│ ├── mixins.less
│ └── spinning.less
│ └── scss
│ ├── _fixed-width.scss
│ ├── _core.scss
│ ├── _bordered-pulled.scss
│ ├── _larger.scss
│ ├── _rotated-flipped.scss
│ ├── _list.scss
│ ├── font-awesome.scss
│ ├── _stacked.scss
│ ├── _path.scss
│ ├── _mixins.scss
│ └── _spinning.scss
├── .rspec
├── config
├── initializers
│ ├── high_voltage.rb
│ ├── default_url_options.rb
│ ├── cookies_serializer.rb
│ ├── github.rb
│ ├── mime_types.rb
│ ├── session_store.rb
│ ├── errbit.rb
│ ├── filter_parameter_logging.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── wrap_parameters.rb
│ ├── inflections.rb
│ └── friendly_id.rb
├── environment.rb
├── boot.rb
├── deploy
│ ├── old.rb
│ └── production.rb
├── locales
│ ├── en.yml
│ └── devise.en.yml
├── routes.rb
├── database.yml
├── deploy.rb
├── secrets.yml
├── environments
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
└── application.rb
├── bin
├── rake
├── bundle
├── rails
└── setup_db.sh
├── config.ru
├── Rakefile
├── db
├── migrate
│ ├── 20150829193440_make_bounty_columns_more_logical.rb
│ ├── 20140930231251_create_repositories.rb
│ ├── 20141203154945_create_git_identities.rb
│ ├── 20140901143444_create_bounties.rb
│ ├── 20141104172317_create_commits.rb
│ ├── 20140630213046_create_coders.rb
│ └── 20140902210829_create_issues.rb
├── seeds.rb
└── schema.rb
├── spec
├── support
│ └── matchers
│ │ └── almost_eq.rb
├── controllers
│ ├── top4_controller_spec.rb
│ ├── repositories_controller_spec.rb
│ └── bounties_controller_spec.rb
├── models
│ ├── repository_spec.rb
│ ├── commit_spec.rb
│ ├── issue_spec.rb
│ ├── bounty_spec.rb
│ └── coder_spec.rb
├── factories
│ ├── repositories.rb
│ ├── commits.rb
│ ├── bounties.rb
│ ├── coders.rb
│ └── issues.rb
├── action
│ └── publish_bounty_spec.rb
├── github_jsons
│ ├── ping.json
│ ├── repo_create.json
│ ├── push.json
│ ├── issue_open.json
│ └── issue_close.json
├── rails_helper.rb
├── requests
│ └── webhooks_spec.rb
└── spec_helper.rb
├── Capfile
├── README.md
├── .rubocop.yml
├── README.rdoc
├── .travis.yml
├── .gitignore
├── LICENSE
└── Gemfile
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.2.2
2 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/helpers/top4_helper.rb:
--------------------------------------------------------------------------------
1 | module Top4Helper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/issues_helper.rb:
--------------------------------------------------------------------------------
1 | module IssuesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/webhooks_helper.rb:
--------------------------------------------------------------------------------
1 | module WebhooksHelper
2 | end
3 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 | --require rails_helper
4 |
--------------------------------------------------------------------------------
/app/helpers/repositories_helper.rb:
--------------------------------------------------------------------------------
1 | module RepositoriesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/static.css.scss:
--------------------------------------------------------------------------------
1 | .faq {
2 | .inset {
3 | margin-left: 15px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/app/assets/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DV/gamification/master/app/assets/images/github.png
--------------------------------------------------------------------------------
/config/initializers/high_voltage.rb:
--------------------------------------------------------------------------------
1 | HighVoltage.configure do |config|
2 | config.routes = false
3 | end
4 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/config/initializers/default_url_options.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.default_url_options[:host] =
2 | Rails.configuration.x.host
3 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :marshal
4 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/initializers/github.rb:
--------------------------------------------------------------------------------
1 | # Github accessor
2 | Rails.configuration.x.github = Github.new(
3 | oauth_token: Rails.application.secrets.github_token,
4 | auto_pagination: true
5 | )
6 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/fixed-width.less:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .@{fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/app/views/repositories/index.html.erb:
--------------------------------------------------------------------------------
1 |
4 | <%= link_to repo.name, repo %>
5 | <%= link_to issue.title, issue.github_url %>
6 |
7 | <% if issue.assignee %>
8 | <%= link_to issue.assignee.github_name, issue.assignee %>
9 | <%end%>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/db/migrate/20140930231251_create_repositories.rb:
--------------------------------------------------------------------------------
1 | class CreateRepositories < ActiveRecord::Migration
2 | def change
3 | create_table :repositories do |t|
4 | t.string :name, index: true, unique: true, null: false
5 | t.string :github_url, null: false
6 | t.string :clone_url, null: false
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20141203154945_create_git_identities.rb:
--------------------------------------------------------------------------------
1 | class CreateGitIdentities < ActiveRecord::Migration
2 | def change
3 | create_table :git_identities do |t|
4 | t.string :name, null: false
5 | t.string :email, null: false
6 | t.references :coder, index: true
7 |
8 | t.timestamps
9 | end
10 | add_index :git_identities, [:name, :email], unique: true
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/config/deploy/old.rb:
--------------------------------------------------------------------------------
1 | server 'king.ugent.be', user: 'gamification', roles: %w(web app db),
2 | ssh_options: {
3 | forward_agent: true,
4 | auth_methods: ['publickey'],
5 | port: 2222
6 | }
7 |
8 | set :rails_env, 'production'
9 | set :default_env, 'RAILS_RELATIVE_URL_ROOT' => '/game'
10 |
--------------------------------------------------------------------------------
/app/models/git_identity.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: git_identities
4 | #
5 | # id :integer not null, primary key
6 | # name :string(255) not null
7 | # email :string(255) not null
8 | # coder_id :integer
9 | # created_at :datetime
10 | # updated_at :datetime
11 | #
12 |
13 | class GitIdentity < ActiveRecord::Base
14 | belongs_to :coder
15 | end
16 |
--------------------------------------------------------------------------------
/Capfile:
--------------------------------------------------------------------------------
1 | # Load DSL and Setup Up Stages
2 | require 'capistrano/setup'
3 |
4 | # Includes default deployment tasks
5 | require 'capistrano/deploy'
6 |
7 | require 'capistrano/rails'
8 | #require 'capistrano/rvm'
9 | require 'capistrano/rbenv'
10 | require 'capistrano/rails/collection'
11 |
12 | # Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
13 | Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
14 |
--------------------------------------------------------------------------------
/app/controllers/coders_controller.rb:
--------------------------------------------------------------------------------
1 | class CodersController < ApplicationController
2 | before_action :set_coder, only: [:show]
3 |
4 | def show
5 | @repositories = Repository.only_with_stats(
6 | :score, :commit_count, :additions, :deletions
7 | ).where(coder_id: @coder).order(score: :desc).run
8 | end
9 |
10 | private
11 |
12 | def set_coder
13 | @coder = Coder.friendly.find params[:id]
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Precompile additional assets.
7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are
8 | # already added.
9 | # Rails.application.config.assets.precompile += %w( search.js )
10 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/bordered-pulled.less:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em @fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .pull-right { float: right; }
11 | .pull-left { float: left; }
12 |
13 | .@{fa-css-prefix} {
14 | &.pull-left { margin-right: .3em; }
15 | &.pull-right { margin-left: .3em; }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/rotated-flipped.less:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
7 |
8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
10 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .pull-right { float: right; }
11 | .pull-left { float: left; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.pull-left { margin-right: .3em; }
15 | &.pull-right { margin-left: .3em; }
16 | }
17 |
--------------------------------------------------------------------------------
/app/views/repositories/_repository.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= repository_counter + 1 %>.
3 | <%= link_to repository.name, repository %>
4 | <%= format_score repository.score%>
5 | <%= format_score repository.commit_count %>
6 | <%= format_score repository.additions %>
7 | <%= format_score repository.deletions %>
8 |
9 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/larger.less:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .@{fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .@{fa-css-prefix}-2x { font-size: 2em; }
11 | .@{fa-css-prefix}-3x { font-size: 3em; }
12 | .@{fa-css-prefix}-4x { font-size: 4em; }
13 | .@{fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't
4 | # wish to see in your backtraces.
5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
6 |
7 | # You can also remove all the silencers if you're trying to debug a problem
8 | # that might stem from framework code.
9 | # Rails.backtrace_cleaner.remove_silencers!
10 |
--------------------------------------------------------------------------------
/spec/models/repository_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: repositories
4 | #
5 | # id :integer not null, primary key
6 | # name :string(255) not null
7 | # github_url :string(255) not null
8 | # clone_url :string(255) not null
9 | # created_at :datetime
10 | # updated_at :datetime
11 | #
12 |
13 | describe Repository do
14 | it 'has a valid factory' do
15 | expect(create :repository).to be_valid
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/list.less:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: @fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .@{fa-css-prefix}-li {
11 | position: absolute;
12 | left: -@fa-li-width;
13 | width: @fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.@{fa-css-prefix}-lg {
17 | left: -@fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Gamification [](https://travis-ci.org/ZeusWPI/gamification) [](https://coveralls.io/r/ZeusWPI/gamification) [](https://codeclimate.com/github/ZeusWPI/gamification)
2 | =======
3 |
4 | Gamification of Zeus member engagement (with GitHub integration)
5 |
--------------------------------------------------------------------------------
/config/deploy/production.rb:
--------------------------------------------------------------------------------
1 | server 'zeus.ugent.be', user: 'gamification', roles: %w(web app db),
2 | ssh_options: {
3 | forward_agent: true,
4 | auth_methods: ['publickey'],
5 | port: 2222
6 | }
7 |
8 | set :rails_env, 'production'
9 | set :rbenv_type, :system
10 | set :rbenv_ruby, File.read('.ruby-version').strip
11 | set :default_env, 'RAILS_RELATIVE_URL_ROOT' => '/game'
12 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/models/concerns/repo_filters.rb:
--------------------------------------------------------------------------------
1 | module RepoFilters
2 | def self.track?(repo)
3 | filters.all? { |k, v| FILTER_LAMBDAS[k].call(v, repo) }
4 | end
5 |
6 | private
7 |
8 | def self.filters
9 | Rails.application.config.repository_filters
10 | end
11 |
12 | FILTER_LAMBDAS = {
13 | only: ->(list, repo) { list.include? repo['name'] },
14 | except: ->(list, repo) { !list.include? repo['name'] },
15 | private: ->(bool, repo) { bool == repo['private'] }
16 | }
17 | end
18 |
--------------------------------------------------------------------------------
/app/assets/javascripts/jquery.readyselector.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | var ready = $.fn.ready;
3 | $.fn.ready = function (fn) {
4 | if (this.context === undefined) {
5 | // The $().ready(fn) case.
6 | ready(fn);
7 | } else if (this.selector) {
8 | ready($.proxy(function(){
9 | $(this.selector, this.context).each(fn);
10 | }, this));
11 | } else {
12 | ready($.proxy(function(){
13 | $(this).each(fn);
14 | }, this));
15 | }
16 | }
17 | })(jQuery);
18 |
--------------------------------------------------------------------------------
/lib/capistrano/tasks/webhooks.cap:
--------------------------------------------------------------------------------
1 | namespace :webhooks do
2 |
3 | desc 'Setup github webhooks'
4 | task :setup do
5 | on roles(:app) do
6 | within current_path do
7 | execute :rake, 'webhooks:create RAILS_ENV=production'
8 | end
9 | end
10 | end
11 |
12 | desc 'Clear github webhooks'
13 | task :delete do
14 | on roles(:app) do
15 | within current_path do
16 | execute :rake, 'webhooks:delete RAILS_ENV=production'
17 | end
18 | end
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/font-awesome.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "spinning";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/font-awesome.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "spinning";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 |
--------------------------------------------------------------------------------
/spec/controllers/repositories_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe RepositoriesController, type: :controller do
4 | describe 'GET index' do
5 | it 'returns http success' do
6 | get :index
7 | expect(response).to have_http_status(:success)
8 | end
9 | end
10 |
11 | describe 'GET show' do
12 | it 'returns http success' do
13 | @repo = create :repository
14 | get :show, id: @repo
15 | expect(response).to have_http_status(:success)
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | RunRailsCops: true
3 | Include:
4 | - Rakefile
5 | - config.ru
6 | Exclude:
7 | - db/schema.rb
8 | - repos/**/*
9 |
10 |
11 | # We right self-documenting code, right? Disabling for now.
12 | Style/Documentation:
13 | Enabled: false
14 |
15 | # Tests don't have to be awfully readable
16 | Metrics/LineLength:
17 | Exclude:
18 | - spec/**/*
19 |
20 | Metrics/AbcSize:
21 | Enabled: false
22 | Metrics/MethodLength:
23 | Max: 20
24 | Metrics/CyclomaticComplexity:
25 | Enabled: false
26 |
--------------------------------------------------------------------------------
/app/views/repositories/_scoreboard.html.erb:
--------------------------------------------------------------------------------
1 |
10 | plaatste <#{bounty.base_uri}|#{bounty.value} bountypunten> op
11 | <#{bounty.issue.repository.base_uri}|#{bounty.issue.repository.name}>
12 | #<#{bounty.issue.github_url}|#{bounty.issue.number}:
13 | #{bounty.issue.title}>
14 | EOF
15 |
16 | hook_url = Rails.application.secrets.slack_hook_url
17 | post(hook_url, body: JSON.dump(text: message)) if hook_url
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/views/coders/_coder.html.erb:
--------------------------------------------------------------------------------
1 | class="info"<% end %>>
2 | <%= coder_counter + 1 %>.
3 |
4 | <%= avatar coder %>
5 |
6 |
7 | <%= link_to coder.github_name, coder, class: "username" %>
8 |
9 | <% unless coder.full_name.blank? %>
10 | <%= coder.full_name %>
11 | <% end %>
12 |
13 | <%= format_score coder.score%>
14 | <%= format_score coder.commit_count %>
15 | <%= format_score coder.additions %>
16 | <%= format_score coder.deletions %>
17 |
18 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/bounties.css.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the bounties controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
5 | #bounties-table {
6 | td, th {
7 | vertical-align: middle;
8 | overflow: hidden;
9 |
10 | &.total-bounty {
11 | text-align: center;
12 | }
13 |
14 | &.my-bounty {
15 | text-align: center;
16 | min-width: 9em;
17 | padding-left: 0;
18 | input {
19 | max-width: 4em;
20 | }
21 | input[type=submit] {
22 | min-width: 4em;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/path.less:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
12 | font-weight: normal;
13 | font-style: normal;
14 | }
15 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root 'top4#show'
3 | get 'top4/show'
4 |
5 | scope path: 'scoreboard', as: 'scoreboard' do
6 | get '' => 'scoreboard#index'
7 | end
8 |
9 | post 'payload', to: 'webhooks#receive'
10 |
11 | resources :coders, only: [:show]
12 | resources :repositories, only: [:index, :show]
13 | resources :bounties, only: [:index] do
14 | post 'update_or_create', on: :collection
15 | end
16 |
17 | devise_for(:coders, controllers: {
18 | omniauth_callbacks: 'coders/omniauth_callbacks'
19 | })
20 | devise_scope :coder do
21 | get 'sign_out', to: 'devise/sessions#destroy', as: :destroy_session
22 | end
23 |
24 | get '/faq' => 'high_voltage/pages#show', :id => 'faq'
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/commits.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: commits
4 | #
5 | # id :integer not null, primary key
6 | # coder_id :integer
7 | # repository_id :integer
8 | # sha :string(255) not null
9 | # additions :integer default(0), not null
10 | # deletions :integer default(0), not null
11 | # date :datetime not null
12 | # created_at :datetime
13 | # updated_at :datetime
14 | #
15 |
16 | require 'faker'
17 |
18 | FactoryGirl.define do
19 | factory :commit do
20 | coder
21 | repository
22 | sha { Faker::Lorem.characters 30 }
23 | additions { rand 100 }
24 | deletions { rand 100 }
25 | date { Faker::Date.backward 30 }
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/models/concerns/schwarm.rb:
--------------------------------------------------------------------------------
1 | module Schwarm
2 | CommitFisch = Datenfisch.provider Commit do
3 | stat :additions, col(:additions).sum
4 | stat :deletions, col(:deletions).sum
5 | stat :count, count
6 | stat :addition_score, (
7 | ln(col(:additions) + 1) *
8 | -> { Rails.application.config.addition_score_factor }
9 | ).round.sum
10 | end
11 |
12 | BountyFisch = Datenfisch.provider Bounty do
13 | stat :claimed_value, col(:claimed_value).sum
14 | stat :absolute_bounty_value, col(:absolute_value).sum
15 |
16 | # rubocop:disable Style/Attr
17 | attr :repository_id, :repository_id, through: :issue
18 | attr :coder_id, :claimant_id
19 | attr :date, :claimed_at
20 | # rubocop:enable Style/Attr
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | development:
7 | adapter: mysql2
8 | username: gamification
9 | database: gamification
10 | password: hallo
11 | host: 127.0.0.1
12 | port: 3306
13 |
14 | # Warning: The database defined as "test" will be erased and
15 | # re-generated from your development database when you run "rake".
16 | # Do not set this db to the same as development or production.
17 | test:
18 | adapter: mysql2
19 | username: travis
20 | database: gamification_test
21 |
22 | production:
23 | adapter: mysql2
24 | database: gamification
25 | username: gamification
26 | password: #dev#
27 | host: 127.0.0.1
28 | port: 3306
29 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
12 | font-weight: normal;
13 | font-style: normal;
14 | }
15 |
--------------------------------------------------------------------------------
/db/migrate/20140902210829_create_issues.rb:
--------------------------------------------------------------------------------
1 | class CreateIssues < ActiveRecord::Migration
2 | def change
3 | create_table :issues do |t|
4 | t.string :github_url, null: false
5 | t.integer :number, null: false
6 | t.string :title, null: false, default: 'Untitled'
7 | t.references :issuer, null: false
8 | t.references :repository, null: false, index: true
9 | t.text :labels, null: false
10 | t.text :body
11 | t.integer :assignee_id
12 | t.string :milestone
13 |
14 | t.timestamp :opened_at, null: false
15 | t.timestamp :closed_at
16 |
17 | t.timestamps
18 | end
19 | add_index :issues, [:repository_id, :number], unique: true
20 | add_index :issues, :github_url, unique: true
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | .fa-icon-rotate(@degrees, @rotation) {
5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
6 | -webkit-transform: rotate(@degrees);
7 | -moz-transform: rotate(@degrees);
8 | -ms-transform: rotate(@degrees);
9 | -o-transform: rotate(@degrees);
10 | transform: rotate(@degrees);
11 | }
12 |
13 | .fa-icon-flip(@horiz, @vert, @rotation) {
14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1);
15 | -webkit-transform: scale(@horiz, @vert);
16 | -moz-transform: scale(@horiz, @vert);
17 | -ms-transform: scale(@horiz, @vert);
18 | -o-transform: scale(@horiz, @vert);
19 | transform: scale(@horiz, @vert);
20 | }
21 |
--------------------------------------------------------------------------------
/spec/factories/bounties.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: bounties
4 | #
5 | # id :integer not null, primary key
6 | # absolute_value :integer not null
7 | # issue_id :integer not null
8 | # issuer_id :integer not null
9 | # claimant_id :integer
10 | # claimed_value :integer
11 | # claimed_at :datetime
12 | # created_at :datetime
13 | # updated_at :datetime
14 | #
15 |
16 | FactoryGirl.define do
17 | factory :bounty do
18 | absolute_value { rand 100 }
19 | issue
20 | association :issuer, factory: :coder
21 |
22 | factory :claimed_bounty do
23 | association :claimant, factory: :coder
24 | claimed_at { Faker::Date.backward 30 }
25 | claimed_value { value }
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon-rotate($degrees, $rotation) {
5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);
6 | -webkit-transform: rotate($degrees);
7 | -moz-transform: rotate($degrees);
8 | -ms-transform: rotate($degrees);
9 | -o-transform: rotate($degrees);
10 | transform: rotate($degrees);
11 | }
12 |
13 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);
15 | -webkit-transform: scale($horiz, $vert);
16 | -moz-transform: scale($horiz, $vert);
17 | -ms-transform: scale($horiz, $vert);
18 | -o-transform: scale($horiz, $vert);
19 | transform: scale($horiz, $vert);
20 | }
21 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
18 | ActiveSupport::Inflector.inflections(:en) do |inflect|
19 | inflect.irregular 'bountypunt', 'bountypunten'
20 | end
21 |
--------------------------------------------------------------------------------
/spec/action/publish_bounty_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe PublishBounty do
4 |
5 | it "sends a message with Slack" do
6 | bounty = build :bounty
7 | expect(Slack).to receive(:send_text)
8 |
9 | PublishBounty.new(bounty).call
10 | end
11 |
12 | it "generates correct message for bounties with value 1" do
13 | bounty = build :bounty, value: 1
14 |
15 | expect(Slack).to receive(:send_text) do |text|
16 | expect(text).to include("1 bountypunt")
17 | end
18 |
19 | PublishBounty.new(bounty).call
20 | end
21 |
22 | it "generates correct message for bounties with plural value" do
23 | bounty = build :bounty, value: 2
24 |
25 | expect(Slack).to receive(:send_text) do |text|
26 | expect(text).to include("2 bountypunten")
27 | end
28 |
29 | PublishBounty.new(bounty).call
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def alert_class_for(flash_type)
3 | {
4 | success: 'alert-success',
5 | error: 'alert-danger',
6 | alert: 'alert-warning',
7 | warning: 'alert-warning',
8 | notice: 'alert-info'
9 | }[flash_type.to_sym] || flash_type.to_s
10 | end
11 |
12 | def format_score(score)
13 | number_with_delimiter(score, delimiter: ' '.html_safe)
14 | end
15 |
16 | def format_short_score(score)
17 | number_to_human(score, delimiter: ' '.html_safe, separator: '.',
18 | format: '%n %u'.html_safe,
19 | units: { thousand: 'K', million: 'M', billion: 'G' })
20 | end
21 |
22 | def navbar_item(html, target)
23 | class_str = current_page?(target) ? 'active' : ''
24 | content_tag(:li, link_to(html, target), class: class_str)
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/views/bounties/update_or_create.js.coffee:
--------------------------------------------------------------------------------
1 | $('#flash-messages').append '<%= escape_javascript render 'flash', locals: { flash: flash } %>'
2 |
3 | $tr = $('tr#issue-<%= @issue.id %>')
4 | $totalBountyCell = $tr.find('.total-bounty')
5 |
6 | # Remove previous styling
7 | $tr.removeClass('has-error has-success')
8 | $tr.find('.btn').removeClass('btn-danger btn-success btn-default')
9 |
10 | <% if flash[:error] %>
11 |
12 | # Scroll to error
13 | $(document).scrollTop(0)
14 | # Indicate the violating cell
15 | $tr.addClass('has-error')
16 | $tr.find('.btn').addClass('btn-danger')
17 |
18 | <% else %>
19 |
20 | # Update the total bounty value
21 | $totalBountyCell.text <%= @issue.total_bounty_value %>
22 | # Update the total bounty points that can be spend
23 | $('#remaining-points').text <%= current_coder.bounty_residual %>
24 | $tr.addClass('has-success')
25 | $tr.find('.btn').addClass('btn-success')
26 |
27 | <% end %>
28 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/less/spinning.less:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .@{fa-css-prefix}-spin {
5 | -webkit-animation: spin 2s infinite linear;
6 | -moz-animation: spin 2s infinite linear;
7 | -o-animation: spin 2s infinite linear;
8 | animation: spin 2s infinite linear;
9 | }
10 |
11 | @-moz-keyframes spin {
12 | 0% { -moz-transform: rotate(0deg); }
13 | 100% { -moz-transform: rotate(359deg); }
14 | }
15 | @-webkit-keyframes spin {
16 | 0% { -webkit-transform: rotate(0deg); }
17 | 100% { -webkit-transform: rotate(359deg); }
18 | }
19 | @-o-keyframes spin {
20 | 0% { -o-transform: rotate(0deg); }
21 | 100% { -o-transform: rotate(359deg); }
22 | }
23 | @-ms-keyframes spin {
24 | 0% { -ms-transform: rotate(0deg); }
25 | 100% { -ms-transform: rotate(359deg); }
26 | }
27 | @keyframes spin {
28 | 0% { transform: rotate(0deg); }
29 | 100% { transform: rotate(359deg); }
30 | }
31 |
--------------------------------------------------------------------------------
/vendor/assets/font-awesome/scss/_spinning.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: spin 2s infinite linear;
6 | -moz-animation: spin 2s infinite linear;
7 | -o-animation: spin 2s infinite linear;
8 | animation: spin 2s infinite linear;
9 | }
10 |
11 | @-moz-keyframes spin {
12 | 0% { -moz-transform: rotate(0deg); }
13 | 100% { -moz-transform: rotate(359deg); }
14 | }
15 | @-webkit-keyframes spin {
16 | 0% { -webkit-transform: rotate(0deg); }
17 | 100% { -webkit-transform: rotate(359deg); }
18 | }
19 | @-o-keyframes spin {
20 | 0% { -o-transform: rotate(0deg); }
21 | 100% { -o-transform: rotate(359deg); }
22 | }
23 | @-ms-keyframes spin {
24 | 0% { -ms-transform: rotate(0deg); }
25 | 100% { -ms-transform: rotate(359deg); }
26 | }
27 | @keyframes spin {
28 | 0% { transform: rotate(0deg); }
29 | 100% { transform: rotate(359deg); }
30 | }
31 |
--------------------------------------------------------------------------------
/spec/factories/coders.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: coders
4 | #
5 | # id :integer not null, primary key
6 | # github_name :string(255) not null
7 | # full_name :string(255) default(""), not null
8 | # avatar_url :string(255) not null
9 | # github_url :string(255) not null
10 | # reward_residual :integer default(0), not null
11 | # absolute_bounty_residual :integer default(0), not null
12 | # other_score :integer default(0), not null
13 | # created_at :datetime
14 | # updated_at :datetime
15 | #
16 |
17 | require 'faker'
18 |
19 | FactoryGirl.define do
20 | factory :coder do
21 | github_name { Faker::Internet.user_name }
22 | full_name { Faker::Name.name }
23 | avatar_url { Faker::Avatar.image }
24 | github_url { "example.com/#{github_name}" }
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery.turbolinks
15 | //= require jquery_ujs
16 | //= require dataTables/jquery.dataTables
17 | //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
18 | //= require turbolinks
19 | //= require jquery.readyselector
20 |
21 | //= require bootstrap-sprockets
22 | //= require d3
23 |
24 | //= require_self
25 | //= require_tree .
26 |
--------------------------------------------------------------------------------
/app/controllers/bounties_controller.rb:
--------------------------------------------------------------------------------
1 | class BountiesController < ApplicationController
2 | before_action :authenticate_coder!, only: [:update_or_create]
3 |
4 | respond_to :html, :coffee
5 |
6 | def index
7 | @issues = Issue.statted.where(closed_at: nil)
8 | .with_stats(:absolute_bounty_value)
9 | .includes(:repository, :unclaimed_bounties).run
10 | end
11 |
12 | def update_or_create
13 | new_value = bounty_params[:value]
14 | # Value must be a non-negative integer
15 | unless new_value =~ /^\d+$/
16 | flash.now[:error] = 'This value is not an integer.'
17 | return
18 | end
19 |
20 | @issue = Issue.find(bounty_params[:issue_id])
21 |
22 | begin
23 | Bounty.update_or_create(@issue, current_coder, new_value.to_i)
24 | rescue Bounty::Error => error
25 | flash.now[:error] = error.message
26 | end
27 | end
28 |
29 | private
30 |
31 | def bounty_params
32 | params.require(:bounty).permit(:issue_id, :value)
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **.orig
2 | *.gem
3 | *.rbc
4 | /.config
5 | /InstalledFiles
6 | /config/secrets.yml
7 | /coverage/
8 | /db/*.sqlite3
9 | /log
10 | /pkg/
11 | /public/system
12 | /spec/reports/
13 | /test/tmp/
14 | /test/version_tmp/
15 | /tmp/
16 | capybara-*.html
17 | pickle-email-*.html
18 | rerun.txt
19 |
20 | ## Specific to RubyMotion:
21 | .dat*
22 | .repl_history
23 | build/
24 |
25 | ## Documentation cache and generated files:
26 | /.yardoc/
27 | /_yardoc/
28 | /doc/
29 | /rdoc/
30 |
31 | ## Environment normalisation:
32 | /.bundle/
33 | /lib/bundler/man/
34 | /vendor/bundle
35 |
36 | # for a library or gem, you might want to ignore these files since the code is
37 | # intended to run in multiple environments; otherwise, check them in:
38 | # Gemfile.lock
39 | # .ruby-version
40 | # .ruby-gemset
41 |
42 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43 | .rvmrc
44 |
45 | # Ignore repository folder
46 | repos/*
47 |
48 | # Capistrano
49 | .capistrano
50 |
51 | # RSpec failure-file
52 | spec/.failures.txt
53 |
--------------------------------------------------------------------------------
/spec/factories/issues.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: issues
4 | #
5 | # id :integer not null, primary key
6 | # github_url :string(255) not null
7 | # number :integer not null
8 | # title :string(255) default("Untitled"), not null
9 | # issuer_id :integer not null
10 | # repository_id :integer not null
11 | # labels :text not null
12 | # body :text
13 | # assignee_id :integer
14 | # milestone :string(255)
15 | # opened_at :datetime not null
16 | # closed_at :datetime
17 | # created_at :datetime
18 | # updated_at :datetime
19 | #
20 |
21 | require 'faker'
22 |
23 | FactoryGirl.define do
24 | factory :issue do
25 | sequence :number
26 | title { Faker::Lorem.sentence }
27 | github_url { "example.org/issues/#{number}" }
28 | opened_at { Faker::Date.backward 30 }
29 | labels ''
30 |
31 | association :issuer, factory: :coder
32 | repository
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/actions/publish_bounty.rb:
--------------------------------------------------------------------------------
1 | class PublishBounty
2 | attr :bounty
3 |
4 | def initialize(bounty)
5 | @bounty = bounty
6 | end
7 |
8 | def call
9 | Slack.send_text(message)
10 | end
11 |
12 | private
13 |
14 | def message
15 | "#{author} plaatste #{score} op #{repository} ##{github_issue}: #{title}"
16 | end
17 |
18 | def author
19 | link_to(bounty.issuer.github_name, bounty.issuer)
20 | end
21 |
22 | def score
23 | [bounty.value, "bountypunt".pluralize(bounty.value)].join(" ")
24 | end
25 |
26 | def repository
27 | link_to(bounty.issue.repository.name, bounty.issue.repository)
28 | end
29 |
30 | def github_issue
31 | link_to(bounty.issue.number, bounty.issue.github_url)
32 | end
33 |
34 | def title
35 | bounty.issue.title
36 | end
37 |
38 | def link_to(text, object_or_uri)
39 | uri = ensure_uri(object_or_uri)
40 |
41 | "<#{uri}|#{text}>"
42 | end
43 |
44 | def ensure_uri(object_or_uri)
45 | if object_or_uri.respond_to?(:base_uri)
46 | object_or_uri.base_uri
47 | else
48 | object_or_uri
49 | end
50 | end
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/coders.css.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Coder controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
5 | #profile_pic {
6 | border-radius: 50%;
7 | box-shadow: 0 0 0 3px #fff, 0 0 0 4px #999, 0 2px 5px 4px rgba(0,0,0,.2);
8 | }
9 | .number {
10 | font-weight:lighter;
11 | font-size: 200%;
12 | }
13 | .metric {
14 | font-size: 18pt;
15 | }
16 | .score-table {
17 | text-align: center;
18 | }
19 |
20 | .scoreboard {
21 | .rank {
22 | font-size: 20pt;
23 | text-align: right;
24 | width: 45px;
25 | height: 45px;
26 | vertical-align: middle;
27 | }
28 | .avatar {
29 | width: 45px;
30 | }
31 | .name {
32 | vertical-align: middle;
33 | }
34 | .username {
35 | font-weight: bold;
36 | }
37 | .score, .commits, .additions, .deletions {
38 | vertical-align: middle;
39 | text-align: right;
40 | }
41 | }
42 |
43 | .avatar {
44 | img {
45 | border-radius: 50%;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Zeus WPI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/config/deploy.rb:
--------------------------------------------------------------------------------
1 | set :application, 'gamification'
2 | set :repo_url, 'git@github.com:ZeusWPI/gamification.git'
3 |
4 | # Default branch is :master
5 | # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }
6 |
7 | # Default deploy_to directory is /var/www/my_app
8 | set :branch, 'master'
9 | set :deploy_to, '/home/gamification/production'
10 |
11 | # Default value for :linked_files is []
12 | set :linked_files, %w(config/database.yml config/secrets.yml)
13 |
14 | # Default value for linked_dirs is []
15 | set :linked_dirs, %w(log repos tmp/pids tmp/cache tmp/sockets vendor/bundle
16 | public/system)
17 |
18 | # Default value for default_env is {}
19 | # set :default_env, { path: "/opt/ruby/bin:$PATH" }
20 | #
21 | set :log_level, :debug
22 |
23 | # Default value for keep_releases is 5
24 | # set :keep_releases, 5
25 |
26 | namespace :deploy do
27 | desc 'Restart application'
28 | task :restart do
29 | on roles(:app) do
30 | with rails_env: fetch(:rails_env) do
31 | execute "touch #{current_path}/tmp/restart.txt"
32 | end
33 | end
34 | end
35 |
36 | after :publishing, :restart
37 | end
38 |
--------------------------------------------------------------------------------
/app/controllers/top4_controller.rb:
--------------------------------------------------------------------------------
1 | class Top4Controller < ApplicationController
2 | include Schwarm
3 | def show
4 | @coders = Coder.with_stats(:score)
5 | .where(date: 1.week.ago..Time.current).order(score: :desc).take(4)
6 |
7 | @top_repos = Repository.with_stats(:score)
8 | .where(date: 1.week.ago..Time.current)
9 | .order(score: :desc).take(4)
10 |
11 | @repo_contributors = @top_repos.map do |repo|
12 | Coder.only_with_stats(:score)
13 | .where(repository: repo, date: 1.weeks.ago..Time.current)
14 | .order(score: :desc).run
15 | end
16 |
17 | @new_issues = Issue.with_stats(:absolute_bounty_value)
18 | .includes(:repository)
19 | .order(opened_at: :desc).take(4)
20 |
21 | @closed_issues = Issue.where.not(closed_at: nil)
22 | .includes(:repository, :assignee)
23 | .order(closed_at: :desc).take(4)
24 |
25 | @top_issues = Issue.with_stats(:absolute_bounty_value)
26 | .includes(:repository)
27 | .order(absolute_bounty_value: :desc).take(4)
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/models/concerns/bounty_points.rb:
--------------------------------------------------------------------------------
1 | module BountyPoints
2 | def self.bounty_points_from_abs(value)
3 | (value * bounty_factor).round
4 | end
5 |
6 | def self.bounty_points_to_abs(value)
7 | (value / bounty_factor).round
8 | end
9 |
10 | def self.bounty_factor
11 | if total_bounty_points < Rails.application.config.total_bounty_value
12 | 1
13 | else
14 | Rails.application.config.total_bounty_value.to_f / total_bounty_points
15 | end
16 | end
17 |
18 | def self.total_bounty_points
19 | coder_bounty_points + assigned_bounty_points
20 | end
21 |
22 | def self.coder_bounty_points
23 | Rails.cache.fetch('stats_coder_bounty_points') do
24 | Coder.sum(:absolute_bounty_residual)
25 | end
26 | end
27 |
28 | def self.assigned_bounty_points
29 | Rails.cache.fetch('stats_assigned_bounty_points') do
30 | Bounty.sum(:absolute_value)
31 | end
32 | end
33 |
34 | def self.expire_assigned_bounty_points
35 | Rails.cache.delete('stats_assigned_bounty_points')
36 | end
37 |
38 | def self.expire_coder_bounty_points
39 | Rails.cache.delete('stats_coder_bounty_points')
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/app/views/application/_flash.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <% if flash[:error] %>
3 |
4 | ×
5 | Error! <%= flash[:error] %>
6 |
7 | <% end %>
8 | <% if flash[:success] %>
9 |
10 | ×
11 | Success! <%= flash[:success] %>
12 |
13 | <% end %>
14 | <% if flash[:notice] %>
15 |
16 | ×
17 | Notice! <%= flash[:notice] %>
18 |
19 | <% end %>
20 | <% if flash[:warning] %>
21 |
22 | ×
23 | Warning! <%= flash[:warning] %>
24 |
25 | <% end %>
26 |
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/bounties.js.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
5 | $('body.bounties.index').ready ->
6 |
7 | # Added a parser for the my-bounty input fields
8 | $.fn.dataTable.ext.order['dom-input-numeric'] = (_settings, col) ->
9 | return this.api().column(col, {order: 'index'}).nodes().map (td, _i) ->
10 | return $('input#bounty_value', td).val() * 1
11 |
12 | $('#bounties-table').DataTable
13 | order: [[2, 'desc'], [0, 'asc'], [1, 'asc']]
14 | columnDefs: [
15 | targets: ['total-bounty', 'my-bounty']
16 | orderSequence: ['desc', 'asc']
17 | render: (data, type, _row, _meta) ->
18 | if type is 'display'
19 | return data
20 | else
21 | return data.replace(/[^\d]/g, '')
22 | ,
23 | targets: ['my-bounty']
24 | orderDataType: 'dom-input-numeric'
25 | type: 'numeric'
26 | ]
27 | autoWidth: false
28 |
--------------------------------------------------------------------------------
/app/views/repositories/show.html.erb:
--------------------------------------------------------------------------------
1 | <% if @repository.blank? %>
2 |
3 | This repository does not exist.
4 |
5 | <% else %>
6 |
7 |
8 |
15 |
16 |
20 |
21 |
22 |
23 |
24 | <%= render partial: 'coders/scoreboard' %>
25 |
26 |
27 |
28 |
29 | <%= render_chart(@chart, 'chart') %>
30 |
31 |
32 |
33 |
34 | <% end %>
35 |
--------------------------------------------------------------------------------
/app/assets/javascripts/repositories.js.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
5 | $('body.repositories.index').ready ->
6 | table = $('#top-repos').DataTable
7 | order: [[2, 'desc']]
8 | columnDefs: [
9 | targets: 'rank'
10 | orderable: false
11 | searchable: false
12 | ,
13 | targets: ['score', 'commits', 'additions', 'deletions']
14 | orderSequence: ['desc', 'asc']
15 | render: (data, type, _row, _meta) ->
16 | if type is 'display'
17 | return data
18 | else
19 | return data.replace(/[^\d]/g, '')
20 | ]
21 | bFilter: false
22 | paging: false
23 | autoWidth: false
24 |
25 | # Recalculate rank column when table changes, so it stays fixed
26 | table.on 'order.dt search.dt', ->
27 | table.column '.rank',
28 | search: 'applied'
29 | order: 'applied'
30 | .nodes().each (cell, i) ->
31 | cell.innerHTML = (i + 1) + '.'
32 |
--------------------------------------------------------------------------------
/app/assets/javascripts/coders.js.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
5 | $('body.scoreboard.index').ready ->
6 | table = $('#scoreboard').DataTable
7 | order: [[3, 'desc']]
8 | columnDefs: [
9 | targets: ['rank', 'avatar']
10 | orderable: false
11 | searchable: false
12 | ,
13 | targets: ['score', 'commits', 'additions', 'deletions']
14 | orderSequence: ['desc', 'asc']
15 | render: (data, type, _row, _meta) ->
16 | if type is 'display'
17 | return data
18 | else
19 | return data.replace(/[^\d]/g, '')
20 | ]
21 | bFilter: false
22 | paging: false
23 | autoWidth: false
24 |
25 | # Recalculate rank column when table changes, so it stays fixed
26 | table.on 'order.dt search.dt', ->
27 | table.column '.rank',
28 | search: 'applied'
29 | order: 'applied'
30 | .nodes().each (cell, i) ->
31 | cell.innerHTML = (i + 1) + '.'
32 |
--------------------------------------------------------------------------------
/spec/models/issue_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: issues
4 | #
5 | # id :integer not null, primary key
6 | # github_url :string(255) not null
7 | # number :integer not null
8 | # title :string(255) default("Untitled"), not null
9 | # issuer_id :integer not null
10 | # repository_id :integer not null
11 | # labels :text not null
12 | # body :text
13 | # assignee_id :integer
14 | # milestone :string(255)
15 | # opened_at :datetime not null
16 | # closed_at :datetime
17 | # created_at :datetime
18 | # updated_at :datetime
19 | #
20 |
21 | describe Issue do
22 | before :each do
23 | @issue = create :issue
24 | end
25 |
26 | it 'has a valid factory' do
27 | expect(create :issue).to be_valid
28 | end
29 |
30 | context 'with bounties' do
31 | before :each do
32 | @claimant = create :coder
33 | @bounty = create :bounty, issue: @issue
34 | @issue.assignee = @claimant
35 | end
36 |
37 | it 'claims bounties when closed' do
38 | @issue.close!
39 | @bounty.reload
40 | expect(@bounty.claimed_at).to be
41 | expect(@bounty.claimant).to eq(@claimant)
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/app/controllers/concerns/burndown_chart.rb:
--------------------------------------------------------------------------------
1 | class BurndownChart
2 | def initialize(issues)
3 | @issues = issues
4 | end
5 |
6 | def timeline
7 | option = {
8 | width: 1000,
9 | height: 440,
10 | colors: %w(orange green blue)
11 | }
12 |
13 | GoogleVisualr::Interactive::AnnotatedTimeLine.new(data_table, option)
14 | end
15 |
16 | private
17 |
18 | def data_table
19 | data_table = GoogleVisualr::DataTable.new
20 |
21 | data_table.new_column('date', 'Date')
22 | data_table.new_column('number', 'New issues')
23 | data_table.new_column('number', 'Closed issues')
24 | data_table.new_column('number', 'Open issues')
25 |
26 | data_table.add_rows(data)
27 |
28 | data_table
29 | end
30 |
31 | def full_date_range
32 | start_date = Time.zone.now.to_date
33 | unless @issues.order(:number).first.nil?
34 | start_date = @issues.order(:number).first.opened_at.to_date
35 | end
36 | start_date..Time.zone.now.to_date
37 | end
38 |
39 | def data
40 | open_issues = @issues.group('DATE(opened_at)').count
41 | closed_issues = @issues.closed.group('DATE(closed_at)').count
42 |
43 | data = []
44 | open = 0
45 | full_date_range.each do |d|
46 | open += (open_issues[d] || 0) - (closed_issues[d] || 0)
47 | data << [d, open_issues[d] || 0, closed_issues[d] || 0, open]
48 | end
49 |
50 | data
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 | *= require_self
12 | *= require_tree .
13 | *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
14 | */
15 | @import "bootstrap-sprockets";
16 | @import "bootstrap";
17 | /*@import "font-awesome-sprockets";*/
18 | @import "font-awesome";
19 |
20 | #page-wrapper {
21 | margin-top: 50px;
22 | }
23 |
24 | .magnificent-top-padding{
25 | padding-top: 30px;
26 | }
27 |
28 | .table:last-child {
29 | margin-bottom: 0px;
30 | }
31 |
32 | // DataTables tweaks:
33 | // Remove option to select amount of entries per page
34 | // and pull search box to the left
35 | .dataTables_wrapper {
36 | .col-xs-6:nth-child(1){
37 | display: none;
38 | }
39 | }
40 | .dataTables_filter {
41 | float: left;
42 | margin-left: 20px;
43 | margin-top: 10px;
44 |
45 | input {
46 | margin-left: 10px;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 964dfc965862690bd1575f8b8b4ae33f4aa88251dd642cc00173a0de0936879b78cdd142c6478602b8576b597b143e99c1c30522d1d18ff2af1a172b93c448b2
15 | github_token: #dev#
16 | github_app_id: '651692db8ef2d40913ff'
17 | github_app_secret: '85a54d91e1d436483930b0e00d3b21de73c6b016'
18 |
19 | test:
20 | secret_key_base: 9aaf264a8c9fe9d98e2892ae1adb8a46e294fd433f06b0230b3f1eece45761bdcb0e1abb37d8949ddb82e4b6a0eb46fc19960be8fb22893f2cfacfd3cd351694
21 | github_token: <%= ENV["GITHUB_TOKEN"] %>
22 | github_app_id: '651692db8ef2d40913ff'
23 | github_app_secret: '85a54d91e1d436483930b0e00d3b21de73c6b016'
24 |
25 | # Do not keep production secrets in the repository,
26 | # instead read values from the environment.
27 | production:
28 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
29 | github_token: #prod#
30 | github_app_id: #prod#
31 | github_app_secret: #prod#
32 |
--------------------------------------------------------------------------------
/app/views/pages/faq.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
What is gamification?
3 |
Gamification is a webapp that will reward you for contributing to
4 | <%=Rails.application.config.organisation%>'s projects.
5 |
6 |
How do I get points?
7 |
When you push your commits to GitHub, Gamification will process
8 | your commits and reward you based on how many lines you added.
9 | You can also fix issues to get points (see Bounties).
10 |
11 |
What are bounties?
12 |
You can post a reward (a bounty) on GitHub issues you'd like to be fixed.
13 | When this issue is resolved, the assignee will automatically claim all associated
14 | bounties.
15 |
16 |
How do these points work?
17 |
We have two kinds of points: reward points and bounty points.
18 | When you commit or claim a bounty, you get rewarded equally
19 | in both types of points.
20 |
21 |
Reward points
22 |
These points can be traded in for goodies.
23 |
24 |
Bounty points
25 |
These points can be used to put bounties on issues you want to see fixed.
26 |
27 |
28 |
Why do bounty points degrade over time?
29 |
This is because there is a global limit of
30 | <%=Rails.application.config.total_bounty_value%> bounty points.
31 | When this limit is exceeded, all amounts of bounty points are rescaled.
32 |
33 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
We're sorry, but something went wrong.
54 |
55 | If you are the application owner check the logs for more information.
56 |
57 |
58 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The change you wanted was rejected.
54 |
Maybe you tried to change something you didn't have access to.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The page you were looking for doesn't exist.
54 |
You may have mistyped the address or the page may have moved.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/views/coders/show.html.erb:
--------------------------------------------------------------------------------
1 | <% if @coder.blank? %>
2 |
3 | This person does not exist.
4 |
5 | <% else %>
6 |
7 |
8 |
15 |
16 |
17 |
18 | <%= avatar @coder, size: '184' %>
19 |
20 |
21 |
<%= format_score @coder.score %> points
22 |
23 |
<%= format_short_score @coder.commits.count %> commits
24 |
<%= format_short_score @coder.additions %> additions
25 |
<%= format_short_score @coder.deletions %> deletions
26 |
27 |
28 |
29 |
30 |
31 |
32 | <%= render partial: 'repositories/scoreboard' %>
33 |
34 |
35 | <% end %>
36 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in
3 | # config/application.rb.
4 |
5 | # Host, to be used for routes and Action Mailer.
6 | config.x.host = 'localhost:3000'
7 |
8 | # In the development environment your application's code is reloaded on
9 | # every request. This slows down response time but is perfect for development
10 | # since you don't have to restart the web server when you make code changes.
11 | config.cache_classes = false
12 |
13 | # Do not eager load code on boot.
14 | config.eager_load = false
15 |
16 | # Show full error reports and disable caching.
17 | config.consider_all_requests_local = true
18 | config.action_controller.perform_caching = false
19 |
20 | # Don't care if the mailer can't send.
21 | config.action_mailer.raise_delivery_errors = false
22 | config.action_mailer.default_url_options = { host: config.x.host }
23 |
24 | # Print deprecation notices to the Rails logger.
25 | config.active_support.deprecation = :log
26 |
27 | # Raise an error on page load if there are pending migrations.
28 | config.active_record.migration_error = :page_load
29 |
30 | # Debug mode disables concatenation and preprocessing of assets.
31 | # This option may cause significant delays in view rendering with a large
32 | # number of complex assets.
33 | config.assets.debug = true
34 |
35 | # Adds additional error checking when serving assets at runtime.
36 | # Checks for improperly declared sprockets dependencies.
37 | # Raises helpful error messages.
38 | config.assets.raise_runtime_errors = true
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in
3 | # config/application.rb.
4 |
5 | # Host, to be used for routes and Action Mailer.
6 | config.x.host = 'localhost:3000'
7 |
8 | # The test environment is used exclusively to run your application's
9 | # test suite. You never need to work with it otherwise. Remember that
10 | # your test database is "scratch space" for the test suite and is wiped
11 | # and recreated between test runs. Don't rely on the data there!
12 | config.cache_classes = true
13 |
14 | # Do not eager load code on boot. This avoids loading your whole application
15 | # just for the purpose of running a single test. If you are using a tool that
16 | # preloads Rails for running tests, you may have to set it to true.
17 | config.eager_load = false
18 |
19 | # Configure static asset server for tests with Cache-Control for performance.
20 | config.serve_static_assets = true
21 | config.static_cache_control = 'public, max-age=3600'
22 |
23 | # Show full error reports and disable caching.
24 | config.consider_all_requests_local = true
25 | config.action_controller.perform_caching = false
26 |
27 | # Raise exceptions instead of rendering exception templates.
28 | config.action_dispatch.show_exceptions = false
29 |
30 | # Disable request forgery protection in test environment.
31 | config.action_controller.allow_forgery_protection = false
32 |
33 | # Tell Action Mailer not to deliver emails to the real world.
34 | # The :test delivery method accumulates sent emails in the
35 | # ActionMailer::Base.deliveries array.
36 | config.action_mailer.delivery_method = :test
37 | config.action_mailer.default_url_options = { host: config.x.host }
38 |
39 | # Print deprecation notices to the stderr.
40 | config.active_support.deprecation = :stderr
41 |
42 | # Raises error for missing translations
43 | # config.action_view.raise_on_missing_translations = true
44 | end
45 |
--------------------------------------------------------------------------------
/app/controllers/webhooks_controller.rb:
--------------------------------------------------------------------------------
1 | class WebhooksController < ApplicationController
2 | skip_before_action :verify_authenticity_token
3 |
4 | def receive
5 | event = request.headers['X-Github-Event']
6 | return head :bad_request unless event
7 |
8 | case event
9 | when 'push'
10 | push(request.request_parameters)
11 | when 'issues'
12 | issues(request.request_parameters)
13 | when 'repository'
14 | repository(request.request_parameters)
15 | else
16 | head :ok
17 | end
18 | end
19 |
20 | private
21 |
22 | def push(json)
23 | @owner = json['repository']['owner']['name']
24 | repo = Repository.find_by(name: json['repository']['name'])
25 | return head :ok unless repo && valid_owner?
26 |
27 | repo.pull_or_clone
28 |
29 | if json['commits']
30 | json['commits'].each do |commit|
31 | Commit.register_from_sha(repo, commit['id'])
32 | end
33 | end
34 | head :created
35 | end
36 |
37 | def issues(json)
38 | @owner = json['repository']['owner']['login']
39 | repo = Repository.find_by(name: json['repository']['name'])
40 | return head :ok unless repo && valid_owner?
41 |
42 | issue = Issue.find_or_create_from_hash(json['issue'], repo)
43 |
44 | case json['action']
45 | when 'opened', 'reopened'
46 | issue.update!(closed_at: nil)
47 | when 'closed'
48 | issue.close!(time: DateTime.rfc3339(json['issue']['closed_at']))
49 | when 'assigned'
50 | assignee = Coder.find_by(github_name: json['issue']['assignee']['login'])
51 | issue.update!(assignee: assignee)
52 | when 'unassigned'
53 | issue.update!(assignee: nil)
54 | else
55 | return head :ok
56 | end
57 | head :created
58 | end
59 |
60 | def repository(json)
61 | repo = json['repository']
62 | @owner = repo['owner']['login']
63 | return head :ok unless RepoFilters.track?(repo) && valid_owner?
64 |
65 | case json['action']
66 | when 'created'
67 | Repository.create_or_update(repo)
68 | return head :created
69 | end
70 |
71 | head :ok
72 | end
73 |
74 | def valid_owner?
75 | @owner == Rails.application.config.organisation
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/app/views/bounties/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% if current_coder %>
4 |
5 |
6 | Remaining points
7 |
8 | <%= current_coder.bounty_residual %>
9 |
10 |
11 |
12 | <% end %>
13 |
14 |
15 |
16 |
17 | Repo
18 | Issue
19 | Total bounty
20 | <% if current_coder %>My bounty <% end %>
21 |
22 |
23 |
24 | <% @issues.each do |issue| %>
25 | <% repo = issue.repository %>
26 |
27 | <%= link_to repo.name, repo %>
28 | <%= link_to issue.title, issue.github_url %>
29 | <%= format_score issue.total_bounty_value %>
30 | <% if current_coder %>
31 |
32 |
33 | <%= form_for find_bounty(issue, current_coder),
34 | url: {action: 'update_or_create'},
35 | method: :post,
36 | remote: true do |f| %>
37 | <%= f.hidden_field :issue_id %>
38 | <%= f.text_field :value,
39 | autocomplete: 'off', class: 'form-control text-right' %>
40 |
41 | <%= f.submit(value: 'Put', data: {disable_with: 'Saving'},
42 | class: 'btn btn-default') %>
43 |
44 | <% end %>
45 |
46 |
47 | <% end %>
48 |
49 | <% end %>
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/spec/github_jsons/ping.json:
--------------------------------------------------------------------------------
1 | {
2 | "zen": "Mind your words, they are important.",
3 | "hook_id": 4502899,
4 | "hook": {
5 | "url": "https://api.github.com/orgs/ZeusWPI/hooks/4502899",
6 | "ping_url": "https://api.github.com/orgs/ZeusWPI/hooks/4502899/pings",
7 | "id": 4502899,
8 | "name": "web",
9 | "active": true,
10 | "events": [
11 | "create",
12 | "issues",
13 | "push"
14 | ],
15 | "config": {
16 | "url": "https://zeus.ugent.be/game/payload",
17 | "content_type": "json",
18 | "insecure_ssl": "0",
19 | "secret": ""
20 | },
21 | "updated_at": "2015-04-03T13:56:13Z",
22 | "created_at": "2015-04-03T13:56:13Z"
23 | },
24 | "organization": {
25 | "login": "ZeusWPI",
26 | "id": 331750,
27 | "url": "https://api.github.com/orgs/ZeusWPI",
28 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos",
29 | "events_url": "https://api.github.com/orgs/ZeusWPI/events",
30 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}",
31 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}",
32 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
33 | "description": null
34 | },
35 | "sender": {
36 | "login": "Iasoon",
37 | "id": 6320308,
38 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
39 | "gravatar_id": "",
40 | "url": "https://api.github.com/users/Iasoon",
41 | "html_url": "https://github.com/Iasoon",
42 | "followers_url": "https://api.github.com/users/Iasoon/followers",
43 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
44 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
45 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
46 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
47 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
48 | "repos_url": "https://api.github.com/users/Iasoon/repos",
49 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
50 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
51 | "type": "User",
52 | "site_admin": false
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV['RAILS_ENV'] ||= 'test'
3 | require 'spec_helper'
4 | require File.expand_path('../../config/environment', __FILE__)
5 | require 'rspec/rails'
6 | # Add additional requires below this line. Rails is not loaded until this point!
7 |
8 | # Requires supporting ruby files with custom matchers and macros, etc, in
9 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
10 | # run as spec files by default. This means that files in spec/support that end
11 | # in _spec.rb will both be required and run as specs, causing the specs to be
12 | # run twice. It is recommended that you do not name files matching this glob to
13 | # end with _spec.rb. You can configure this pattern with the --pattern
14 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
15 | #
16 | # The following line is provided for convenience purposes. It has the downside
17 | # of increasing the boot-up time by auto-requiring all files in the support
18 | # directory. Alternatively, in the individual `*_spec.rb` files, manually
19 | # require only the support files necessary.
20 | #
21 | # Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
22 |
23 | # Checks for pending migrations before tests are run.
24 | # If you are not using ActiveRecord, you can remove this line.
25 | ActiveRecord::Migration.maintain_test_schema!
26 |
27 | RSpec.configure do |config|
28 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
29 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
30 |
31 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
32 | # examples within a transaction, remove the following line or assign false
33 | # instead of true.
34 | config.use_transactional_fixtures = true
35 |
36 | # RSpec Rails can automatically mix in different behaviours to your tests
37 | # based on their file location, for example enabling you to call `get` and
38 | # `post` in specs under `spec/controllers`.
39 | #
40 | # You can disable this behaviour by removing the line below, and instead
41 | # explicitly tag your specs with their type, e.g.:
42 | #
43 | # RSpec.describe UsersController, :type => :controller do
44 | # # ...
45 | # end
46 | #
47 | # The different available types are documented in the features, such as in
48 | # https://relishapp.com/rspec/rspec-rails/docs
49 | config.infer_spec_type_from_file_location!
50 | end
51 |
--------------------------------------------------------------------------------
/app/models/issue.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: issues
4 | #
5 | # id :integer not null, primary key
6 | # github_url :string(255) not null
7 | # number :integer not null
8 | # title :string(255) default("Untitled"), not null
9 | # issuer_id :integer not null
10 | # repository_id :integer not null
11 | # labels :text not null
12 | # body :text
13 | # assignee_id :integer
14 | # milestone :string(255)
15 | # opened_at :datetime not null
16 | # closed_at :datetime
17 | # created_at :datetime
18 | # updated_at :datetime
19 | #
20 |
21 | class Issue < ActiveRecord::Base
22 | extend Datenfisch::Model
23 | belongs_to :issuer, class_name: :Coder,
24 | inverse_of: :created_issues,
25 | foreign_key: 'issuer_id'
26 | belongs_to :assignee, inverse_of: :assigned_issues,
27 | class_name: 'Coder'
28 | belongs_to :repository
29 | has_many :bounties
30 | has_many :unclaimed_bounties, -> { where claimed_at: nil },
31 | class_name: 'Bounty'
32 |
33 | scope :open, -> { where(closed_at: nil) }
34 | scope :closed, -> { where.not(closed_at: nil) }
35 |
36 | serialize :labels
37 |
38 | include Schwarm
39 | stat :absolute_bounty_value, BountyFisch.absolute_bounty_value
40 |
41 | def total_bounty_value
42 | BountyPoints.bounty_points_from_abs(absolute_bounty_value)
43 | end
44 |
45 | def close!(time: Time.zone.now)
46 | bounties.where(claimed_at: nil).find_each(&:claim!)
47 | update!(closed_at: time)
48 | save!
49 | end
50 |
51 | def self.find_or_create_from_hash(json, repo)
52 | issuer = Coder.find_or_create_by_github_name(json['user']['login'])
53 | Issue.find_or_create_by(number: json['number'], repository: repo) do |issue|
54 | issue.github_url = json['html_url']
55 | issue.number = json['number']
56 | issue.title = json['title']
57 | issue.body = json['body']
58 | issue.issuer = issuer
59 | issue.labels = (json['labels'] || []).map { |label| label['name'] }
60 | issue.milestone = json['milestone'].try(:[], :title)
61 | issue.opened_at = DateTime.rfc3339(json['created_at'])
62 |
63 | closed_at = json['closed_at']
64 | issue.closed_at = DateTime.rfc3339(closed_at) if closed_at
65 |
66 | unless json['assignee'].blank?
67 | issue.assignee =
68 | Coder.find_or_create_by_github_name(json['assignee']['login'])
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/top4.css.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the top4 controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
5 | .top4.toprow {
6 | height: 300px;
7 | }
8 |
9 | div.top4 {
10 | margin: auto;
11 | .panel {
12 | height: 100%;
13 | }
14 |
15 | #top-coders {
16 | td {
17 | vertical-align: middle;
18 | }
19 |
20 | .rank {
21 | font-size: 20pt;
22 | text-align: right;
23 | width: 45px;
24 | height: 45px;
25 | }
26 | .avatar {
27 | width: 45px;
28 | img {
29 | border-radius: 50%;
30 | }
31 | }
32 | .username {
33 | font-weight: bold;
34 | }
35 | .score {
36 | text-align: right;
37 | font-size: 20pt;
38 | }
39 |
40 | tr:first-child {
41 | width: 90px;
42 | height: 90px;
43 | .name {
44 | font-size: 15pt;
45 | }
46 | .username {
47 | font-size: 23pt;
48 | }
49 | .avatar {
50 | width: 120px;
51 |
52 | img {
53 | position: absolute;
54 | top: -40px;
55 | left: 30px;
56 | border-radius: 50%;
57 | box-shadow: 0 0 0 3px #fff, 0 0 0 4px #999, 0 2px 5px 4px rgba(0,0,0,.2);
58 | }
59 | }
60 | .score {
61 | font-size: 25pt;
62 | }
63 | }
64 |
65 | }
66 |
67 | #top-repos {
68 | width: 100%;
69 | max-height: 100%;
70 | display: block;
71 | margin: auto;
72 | padding: 10px;
73 |
74 | vertical-align: bottom;
75 | shape-rendering: crisp-edges;
76 | image-rendering: crisp-edges;
77 |
78 | .repo-bar {
79 | fill: #1565c0;
80 | }
81 |
82 | .repo-label {
83 | text-anchor: middle;
84 | font: 16px sans-serif;
85 | fill: black;
86 | }
87 |
88 | .contributor-name {
89 | text-anchor: middle;
90 | fill: white;
91 | }
92 |
93 | .axis {
94 | text {
95 | font: 16px sans-serif;
96 | fill: black;
97 | }
98 | }
99 | }
100 | }
101 |
102 | td.truncatable {
103 | text-overflow: ellipsis;
104 | overflow: hidden;
105 | max-width: 1px;
106 | }
107 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Gamification
6 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
7 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
8 | <%# Google charts dependency %>
9 | <%= javascript_include_tag "//www.google.com/jsapi" %>
10 | <%= csrf_meta_tags %>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
27 |
28 |
29 | <%= navbar_item fa_icon('bar-chart', text: 'Scoreboard'), scoreboard_path%>
30 | <%= navbar_item fa_icon('code', text: 'Repos'), repositories_path %>
31 | <%= navbar_item fa_icon('money', text: 'Bounties'), bounties_path %>
32 | <%= navbar_item fa_icon('question', text: 'FAQ'), faq_path %>
33 |
34 |
35 |
36 | <% if coder_signed_in? %>
37 | <%= link_to raw(' ' +
38 | current_coder.github_name), current_coder %>
39 | <%= link_to raw(' '),
40 | destroy_session_path, :title => "Log out" %>
41 | <% else %>
42 | <%= link_to raw(' '),
43 | coder_omniauth_authorize_path(:github), :title => "Log in" %>
44 | <% end %>
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | <% flash.each do |type, message| %>
53 |
54 | ×
55 | <%= message %>
56 |
57 | <% end %>
58 |
59 |
60 | <%= yield %>
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/models/commit.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: commits
4 | #
5 | # id :integer not null, primary key
6 | # coder_id :integer
7 | # repository_id :integer
8 | # sha :string(255) not null
9 | # additions :integer default(0), not null
10 | # deletions :integer default(0), not null
11 | # date :datetime not null
12 | # created_at :datetime
13 | # updated_at :datetime
14 | #
15 |
16 | class Commit < ActiveRecord::Base
17 | belongs_to :coder
18 | belongs_to :repository
19 |
20 | require 'rugged'
21 |
22 | scope :additions, -> { sum :additions }
23 | scope :deletions, -> { sum :deletions }
24 |
25 | def reward!
26 | coder.reward_commit!(self)
27 | end
28 |
29 | def self.register_from_sha(repo, sha, **opts)
30 | r_commit = repo.rugged_repo.lookup(sha)
31 | register_rugged(repo, r_commit, **opts)
32 | end
33 |
34 | def self.register_rugged(repo, r_commit, reward: true)
35 | # if committer can't be determined, fuck this shit
36 | committer = get_committer(repo, r_commit)
37 | return nil unless committer
38 | Commit.find_or_create_by(repository: repo,
39 | sha: r_commit.oid,
40 | coder: committer) do |commit|
41 | commit.initialize_from_rugged(r_commit)
42 | commit.reward! if reward
43 | end
44 | end
45 |
46 | def self.get_committer(repo, r_commit)
47 | identity = GitIdentity.find_by(name: r_commit.committer[:name],
48 | email: r_commit.committer[:email])
49 | unless identity
50 | coder = get_committer_from_github(repo, r_commit)
51 | return nil unless coder
52 | identity = GitIdentity.create(name: r_commit.committer[:name],
53 | email: r_commit.committer[:email],
54 | coder: coder)
55 | end
56 | identity.coder
57 | end
58 |
59 | def self.get_committer_from_github(repo, r_commit)
60 | github = Rails.configuration.x.github
61 | commit = github.repos.commits.get(Rails.application.config.organisation,
62 | repo.name, r_commit.oid)
63 | if commit.committer
64 | Coder.find_or_create_by_github_name(commit.committer.login)
65 | end
66 | end
67 |
68 | def initialize_from_rugged(r_commit)
69 | self.date = r_commit.time
70 | set_stats(r_commit)
71 | end
72 |
73 | private
74 |
75 | def set_stats(r_commit) # rubocop:disable Style/AccessorMethodName
76 | if r_commit.parents.size >= 2
77 | # Do not reward merges
78 | self.additions = self.deletions = 0
79 | else
80 | if r_commit.parents.empty?
81 | # First commit
82 | diff = r_commit.diff
83 | else
84 | diff = r_commit.diff(r_commit.parents.first)
85 | end
86 | self.additions = diff.stat[2]
87 | self.deletions = diff.stat[1]
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/app/models/coder.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: coders
4 | #
5 | # id :integer not null, primary key
6 | # github_name :string(255) not null
7 | # full_name :string(255) default(""), not null
8 | # avatar_url :string(255) not null
9 | # github_url :string(255) not null
10 | # reward_residual :integer default(0), not null
11 | # absolute_bounty_residual :integer default(0), not null
12 | # other_score :integer default(0), not null
13 | # created_at :datetime
14 | # updated_at :datetime
15 | #
16 |
17 | class Coder < ActiveRecord::Base
18 | extend FriendlyId
19 | extend Datenfisch::Model
20 | friendly_id :github_name
21 |
22 | devise :omniauthable, omniauth_providers: [:github]
23 |
24 | has_many :created_issues, class_name: 'Issue', foreign_key: 'issuer_id'
25 | has_many :assigned_issues, class_name: 'Issue', foreign_key: 'assignee_id'
26 | has_many :claimed_bounties, class_name: 'Bounty', foreign_key: 'claimant_id'
27 | has_many :bounties
28 | has_many :commits
29 |
30 | after_commit :clear_caches
31 | after_rollback :clear_caches
32 |
33 | include Schwarm
34 | stat :additions, CommitFisch.additions
35 | stat :deletions, CommitFisch.deletions
36 | stat :commit_count, CommitFisch.count
37 | stat :changed_lines, additions + deletions
38 | stat :claimed_value, BountyFisch.claimed_value
39 | stat :addition_score, CommitFisch.addition_score
40 | stat :score, addition_score + claimed_value
41 |
42 | def reward_bounty!(bounty)
43 | self.bounty_residual += bounty.value
44 | self.reward_residual += bounty.value
45 | save!
46 | end
47 |
48 | def refund_bounty!(bounty)
49 | self.bounty_residual += bounty.value
50 | save!
51 | end
52 |
53 | def reward_commit!(commit)
54 | addition_score = (Math.log(commit.additions + 1) *
55 | Rails.application.config.addition_score_factor).round
56 | self.reward_residual += addition_score
57 | self.bounty_residual += addition_score
58 | save!
59 | end
60 |
61 | def bounty_residual
62 | BountyPoints.bounty_points_from_abs(absolute_bounty_residual)
63 | end
64 |
65 | def bounty_residual=(new_value)
66 | self.absolute_bounty_residual =
67 | BountyPoints.bounty_points_to_abs(new_value)
68 | end
69 |
70 | def self.from_omniauth(auth)
71 | find_by_github_name(auth.info.nickname)
72 | end
73 |
74 | def self.find_or_create_by_github_name(name)
75 | Coder.find_or_create_by(github_name: name) do |coder|
76 | # Fetch data from github
77 | data = Rails.configuration.x.github.users.get(user: name)
78 | coder.full_name = data.try(:name) || ''
79 | coder.avatar_url = data.avatar_url
80 | coder.github_url = data.html_url
81 | end
82 | end
83 |
84 | private
85 |
86 | def clear_caches
87 | BountyPoints.expire_coder_bounty_points
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module Gamification
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified
12 | # here. Application configuration should go into files in
13 | # config/initializers -- all .rb files in that directory are automatically
14 | # loaded.
15 |
16 | # Set Time.zone default to the specified zone and make Active Record
17 | # auto-convert to this zone. Run "rake -D time" for a list of tasks for
18 | # finding time zone names. Default is UTC.
19 | config.time_zone = 'Brussels'
20 | # autoload lib files
21 | config.autoload_paths << Rails.root.join('lib')
22 |
23 | # The default locale is :en and all translations from
24 | # config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path +=
26 | # Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
27 | # config.i18n.default_locale = :de
28 |
29 | # Organisation to track
30 | config.organisation = 'ZeusWPI'
31 |
32 | config.repository_filters = {
33 | # only: [ 'gamification', 'Haldis' ]
34 | except: [
35 | 'glowing-octo-dubstep',
36 | 'VPW-voorbereiding-2015',
37 | 'VPW-voorbereiding-2014',
38 | 'contests',
39 | 'Bestuurstaakjes',
40 | 'SumoRoboComp',
41 | 'kaggle-rta',
42 | 'manage-user',
43 | 'website-manage',
44 | 'errbit'
45 | ]
46 | # private: false
47 | }
48 |
49 | # Total bounty value
50 | config.total_bounty_value = 5000
51 |
52 | # Addition score factor
53 | config.addition_score_factor = 10
54 |
55 | config.generators do |g|
56 | g.test_framework :rspec,
57 | fixtures: true,
58 | view_specs: false,
59 | helper_specs: false,
60 | routing_specs: false,
61 | controller_specs: true,
62 | request_specs: true
63 | g.fixture_replacement :factory_girl, dir: 'spec/factories'
64 | end
65 |
66 | # Backport from Rails 4.2: custom configurations
67 | # Just remove this block when upgrading from 4.1.8 to 4.2
68 | if Rails.version == '4.1.8'
69 | class Custom
70 | def initialize
71 | @configurations = {}
72 | end
73 |
74 | def method_missing(method, *args)
75 | if method =~ /=$/
76 | @configurations[$`.to_sym] = args.first
77 | else
78 | @configurations.fetch(method) do
79 | @configurations[method] = ActiveSupport::OrderedOptions.new
80 | end
81 | end
82 | end
83 | end
84 |
85 | config.x = Custom.new
86 | else
87 | warn('Remove this backport at config/application:62-84')
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/app/models/repository.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: repositories
4 | #
5 | # id :integer not null, primary key
6 | # name :string(255) not null
7 | # github_url :string(255) not null
8 | # clone_url :string(255) not null
9 | # created_at :datetime
10 | # updated_at :datetime
11 | #
12 |
13 | class Repository < ActiveRecord::Base
14 | extend FriendlyId
15 | extend Datenfisch::Model
16 | friendly_id :name
17 |
18 | has_many :issues
19 | has_many :commits
20 | has_many :bounties, through: :issues
21 | has_many :coders, -> { uniq }, through: :commits
22 |
23 | validates :name, presence: true
24 |
25 | include Schwarm
26 | stat :additions, CommitFisch.additions
27 | stat :deletions, CommitFisch.deletions
28 | stat :commit_count, CommitFisch.count
29 | stat :changed_lines, additions + deletions
30 | stat :bounty_score, BountyFisch.claimed_value
31 | stat :score, CommitFisch.addition_score + bounty_score
32 |
33 | require 'rugged'
34 |
35 | def pull_or_clone
36 | if Dir.exist?(path)
37 | ensure_correct_remote_url
38 |
39 | logger.info("Fetching #{name}...")
40 | `cd #{path} && git fetch && git reset --hard origin/master`
41 | else
42 | logger.info("Cloning #{name}...")
43 | `mkdir -p #{path} && git clone #{authenticated_clone_url} #{path}`
44 | end
45 | end
46 |
47 | def register_commits!
48 | r_repo = rugged_repo
49 | walker = Rugged::Walker.new(r_repo)
50 | # Push all heads
51 | r_repo.branches.each { |b| walker.push b.target_id }
52 | walker.push(r_repo.last_commit)
53 | walker.each do |commit|
54 | Commit.register_rugged(self, commit, reward: false)
55 | end
56 | end
57 |
58 | def fetch_issues!
59 | github = Rails.configuration.x.github
60 | github.issues.list(user: Rails.application.config.organisation,
61 | repo: name, state: 'all', filter: 'all').each do |hash|
62 | Issue.find_or_create_from_hash(hash, self)
63 | end
64 | end
65 |
66 | require 'rugged'
67 | def rugged_repo
68 | Rugged::Repository.new(path)
69 | end
70 |
71 | def full_name
72 | "#{Rails.application.config.organisation}/#{name}"
73 | end
74 |
75 | def self.create_or_update(repo_hash)
76 | repo = Repository.find_or_create_by(name: repo_hash['name']) do |r|
77 | r.github_url = repo_hash['html_url']
78 | r.clone_url = repo_hash['clone_url']
79 | end
80 | repo.pull_or_clone
81 | repo.register_commits!
82 | repo.fetch_issues!
83 | end
84 |
85 | private
86 |
87 | def path
88 | "#{Rails.root}/repos/#{name}"
89 | end
90 |
91 | def authenticated_clone_url
92 | clone_url.sub('https://') do
93 | $& + Rails.application.secrets.github_token + '@'
94 | end
95 | end
96 |
97 | def ensure_correct_remote_url
98 | logger.info("Checking remote url of #{name}...")
99 | remote_url = `cd #{path} && git remote -v`.split[1]
100 | return if remote_url == authenticated_clone_url
101 |
102 | logger.info('Setting new remote url...')
103 | `cd #{path} && git remote set-url origin #{authenticated_clone_url}`
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
4 | gem 'rails', '~> 4.1'
5 |
6 | # Use SCSS for stylesheets
7 | gem 'sass-rails', '~> 4.0.0'
8 |
9 | # Use Bootstrap for styling
10 | gem 'bootstrap-sass', '~> 3.3.1'
11 |
12 | # Use Font Awesome for icons
13 | gem 'font-awesome-rails'
14 |
15 | # Use Uglifier as compressor for JavaScript assets
16 | gem 'uglifier', '>= 1.3.0'
17 |
18 | # Use CoffeeScript for .js.coffee assets and views
19 | gem 'coffee-rails', '~> 4.0.0'
20 |
21 | # Airbrake
22 | gem 'airbrake'
23 |
24 | # Coveralls!
25 | gem 'coveralls', require: false
26 |
27 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes
28 | # gem 'therubyracer', platforms: :ruby
29 | # Use jquery as the JavaScript library
30 | gem 'jquery-rails'
31 |
32 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
33 | gem 'turbolinks'
34 |
35 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
36 | gem 'jbuilder', '~> 1.2'
37 |
38 | # Build google charts
39 | gem 'google_visualr'
40 |
41 | group :doc do
42 | # bundle exec rake doc:rails generates the API under doc/api.
43 | gem 'sdoc', require: false
44 | end
45 |
46 | group :test, :development do
47 | gem 'pry-byebug'
48 | end
49 |
50 | group :test do
51 | gem 'rack-test'
52 | gem 'rspec-rails'
53 | gem 'factory_girl_rails'
54 | gem 'faker'
55 | end
56 |
57 | group :development do
58 | gem 'capistrano'
59 | gem 'capistrano-rails'
60 | gem 'capistrano-passenger'
61 | gem 'capistrano-rvm'
62 | gem 'capistrano-rbenv'
63 | gem 'capistrano-rails-collection'
64 |
65 | gem 'annotate'
66 |
67 | gem 'rubocop', github: 'bbatsov/rubocop'
68 | end
69 |
70 | group :production do
71 | gem 'mysql2'
72 | end
73 |
74 | # Use ActiveModel has_secure_password
75 | # gem 'bcrypt-ruby', '~> 3.1.2'
76 |
77 | # Use unicorn as the app server
78 | # gem 'unicorn'
79 |
80 | # Use debugger
81 | # gem 'debugger', group: [:development, :test]
82 |
83 | # Use GitHub API (https://github.com/peter-murach/github)
84 | gem 'github_api'
85 | # Use Rugged
86 | gem 'rugged'
87 |
88 | # Use Devise (https://github.com/plataformatec/devise)
89 | gem 'devise'
90 |
91 | # Use OmniAuth (https://github.com/intridea/omniauth)
92 | gem 'omniauth'
93 | # Use GitHub provider for OmniAuth (https://github.com/intridea/omniauth-github)
94 | gem 'omniauth-github'
95 |
96 | # Use FriendlyId to have nice-looking URLs
97 | # (https://github.com/norman/friendly_id)
98 | gem 'friendly_id'
99 |
100 | # Use Turbolinks in a sane way (https://github.com/kossnocorp/jquery.turbolinks)
101 | gem 'jquery-turbolinks'
102 |
103 | # Use datenfisch (https://github.com/Iasoon/datenfisch.git)
104 | gem 'datenfisch', git: 'git://github.com/Iasoon/datenfisch.git',
105 | ref: '4f39bb3686b5facfb2552fe186d568ce3d259993'
106 |
107 | # Use jQuery plugin datatables (https://github.com/DataTables/DataTables)
108 | gem 'jquery-datatables-rails'
109 |
110 | # Use d3 for fancy visualisations
111 | gem 'd3-rails'
112 |
113 | # High voltage static pages
114 | gem 'high_voltage'
115 |
116 | # Use HTTParty for easier HTTP requests
117 | gem 'httparty'
118 |
--------------------------------------------------------------------------------
/config/initializers/friendly_id.rb:
--------------------------------------------------------------------------------
1 | # FriendlyId Global Configuration
2 | #
3 | # Use this to set up shared configuration options for your entire application.
4 | # Any of the configuration options shown here can also be applied to single
5 | # models by passing arguments to the `friendly_id` class method or defining
6 | # methods in your model.
7 | #
8 | # To learn more, check out the guide:
9 | #
10 | # http://norman.github.io/friendly_id/file.Guide.html
11 |
12 | FriendlyId.defaults do |config|
13 | # ## Reserved Words
14 | #
15 | # Some words could conflict with Rails's routes when used as slugs, or are
16 | # undesirable to allow as slugs. Edit this list as needed for your app.
17 | config.use :reserved
18 |
19 | config.reserved_words = %w(new edit index session login logout users admin
20 | stylesheets assets javascripts images)
21 |
22 | # ## Friendly Finders
23 | #
24 | # Uncomment this to use friendly finders in all models. By default, if
25 | # you wish to find a record by its friendly id, you must do:
26 | #
27 | # MyModel.friendly.find('foo')
28 | #
29 | # If you uncomment this, you can do:
30 | #
31 | # MyModel.find('foo')
32 | #
33 | # This is significantly more convenient but may not be appropriate for
34 | # all applications, so you must explicity opt-in to this behavior. You can
35 | # always also configure it on a per-model basis if you prefer.
36 | #
37 | # Something else to consider is that using the :finders addon boosts
38 | # performance because it will avoid Rails-internal code that makes runtime
39 | # calls to `Module.extend`.
40 | #
41 | # config.use :finders
42 | #
43 | # ## Slugs
44 | #
45 | # Most applications will use the :slugged module everywhere. If you wish
46 | # to do so, uncomment the following line.
47 | #
48 | # config.use :slugged
49 | #
50 | # By default, FriendlyId's :slugged addon expects the slug column to be named
51 | # 'slug', but you can change it if you wish.
52 | #
53 | # config.slug_column = 'slug'
54 | #
55 | # When FriendlyId can not generate a unique ID from your base method, it
56 | # appends a UUID, separated by a single dash. You can configure the character
57 | # used as the separator. If you're upgrading from FriendlyId 4, you may wish
58 | # to replace this with two dashes.
59 | #
60 | # config.sequence_separator = '-'
61 | #
62 | # ## Tips and Tricks
63 | #
64 | # ### Controlling when slugs are generated
65 | #
66 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is
67 | # nil, but if you're using a column as your base method can change this
68 | # behavior by overriding the `should_generate_new_friendly_id` method that
69 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
70 | # more like 4.0.
71 | #
72 | # config.use Module.new {
73 | # def should_generate_new_friendly_id?
74 | # slug.blank? || _changed?
75 | # end
76 | # }
77 | #
78 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for
79 | # languages that don't use the Roman alphabet, that's not usually suffient.
80 | # Here we use the Babosa library to transliterate Russian Cyrillic slugs to
81 | # ASCII. If you use this, don't forget to add "babosa" to your Gemfile.
82 | #
83 | # config.use Module.new {
84 | # def normalize_friendly_id(text)
85 | # text.to_slug.normalize! :transliterations => [:russian, :latin]
86 | # end
87 | # }
88 | end
89 |
--------------------------------------------------------------------------------
/app/views/top4/show.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | <% @coders.each_with_index do |coder, index| %>
14 |
15 |
16 | <% if index != 0 %>
17 | <%= index + 1 %>.
18 | <% end %>
19 |
20 |
21 | <%= avatar coder, size: (index == 0 ? '120' : '45') %>
22 |
23 |
24 | <%= link_to coder.github_name, coder_path(coder), class: "username" %>
25 |
26 | <% unless coder.full_name.blank? %>
27 | <%= coder.full_name %>
28 | <% end %>
29 |
30 | <%= format_score coder.score %>
31 |
32 | <% end %>
33 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
New issues
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | <%= render partial: 'issue', collection: @new_issues %>
56 |
57 |
58 |
59 |
60 |
61 |
Largest bounties
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | <%= render partial: 'issue', collection: @top_issues %>
71 |
72 |
73 |
74 |
75 |
76 |
Recently closed
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | <%= render partial: 'issue_closed', collection: @closed_issues %>
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | <%= javascript_tag do %>
96 | var repos = <%= raw @top_repos.to_json(methods: :base_uri) %>,
97 | contributors = <%= raw @repo_contributors.to_json %>;
98 | <% end %>
99 |
--------------------------------------------------------------------------------
/spec/requests/webhooks_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe 'Webhooks', type: :request do
2 | def post_payload(json, type)
3 | post '/payload', json, 'Content-Type' => 'application/json',
4 | 'X-Github-Event' => type
5 | end
6 |
7 | it 'should respond to ping' do
8 | json = File.read('spec/github_jsons/ping.json')
9 | post_payload json, 'ping'
10 | end
11 |
12 | context 'with tracked repository' do
13 | before :all do
14 | Rails.application.config.organisation = 'ZeusWPI'
15 | Rails.application.config.repository_filters = {}
16 | end
17 |
18 | context 'received created-repo webhook' do
19 | before :each do
20 | json = File.read('spec/github_jsons/repo_create.json')
21 | post_payload json, 'repository'
22 | end
23 |
24 | it 'creates a repository' do
25 | expect(Repository.count).to eq(1)
26 | end
27 |
28 | it 'sets correct name' do
29 | expect(Repository.find_by name: 'glowing-octo-dubstep').to be
30 | end
31 | end
32 |
33 | # Fake rugged implementation
34 | class FakeRepo
35 | def lookup(sha)
36 | FakeCommit.new sha
37 | end
38 | end
39 |
40 | class FakeCommit
41 | attr_reader :oid, :time, :parents
42 |
43 | def initialize(sha)
44 | @oid = sha
45 | @parents = ['lol']
46 | @time = Time.zone.now
47 | end
48 |
49 | def diff(_arg = nil)
50 | FakeDiff.new
51 | end
52 | end
53 |
54 | class FakeDiff
55 | def stat
56 | [0, 10, 20]
57 | end
58 | end
59 |
60 | context 'received push webhook' do
61 | before :each do
62 | @god = create :repository, name: 'glowing-octo-dubstep'
63 | @iasoon = create :coder, github_name: 'Iasoon'
64 | @sha = 'be981ec9e53ef639361ffebbf88845c711fb07bd' # From json
65 |
66 | RSpec::Mocks.with_temporary_scope do
67 | allow(Commit).to receive(:get_committer) { @iasoon }
68 | allow_any_instance_of(Repository).to receive(:rugged_repo) { FakeRepo.new }
69 | allow_any_instance_of(Repository).to receive(:pull_or_clone)
70 | json = File.read('spec/github_jsons/push.json')
71 | post_payload json, 'push'
72 | end
73 | end
74 |
75 | it 'registers the commit' do
76 | expect(Commit.find_by repository: @god, sha: @sha).to be
77 | end
78 |
79 | it 'rewarded the commit' do
80 | @iasoon.reload
81 | expect(@iasoon.reward_residual).to be > 0
82 | end
83 | end
84 |
85 | context 'received issue webhook' do
86 | before :each do
87 | @god = create :repository, name: 'glowing-octo-dubstep'
88 | json = File.read('spec/github_jsons/issue_open.json')
89 | post_payload json, 'issues'
90 | end
91 |
92 | it 'creates a new issue' do
93 | expect(@god.issues.count).to eq(1)
94 | end
95 |
96 | context 'closed' do
97 | before :each do
98 | json = File.read('spec/github_jsons/issue_close.json')
99 | post_payload json, 'issues'
100 | end
101 |
102 | it 'closes the issue' do
103 | expect(@god.issues.where.not(closed_at: nil).count).to eq(1)
104 | end
105 | end
106 | end
107 | end
108 |
109 | context 'with untracked repository' do
110 | before :all do
111 | Rails.application.config.organisation = 'Derps Inc'
112 | end
113 |
114 | context 'received created-repo webhook' do
115 | before :each do
116 | json = File.read('spec/github_jsons/repo_create.json')
117 | post_payload json, 'repository'
118 | end
119 |
120 | it 'did not create a repository' do
121 | expect(Repository.count).to eq(0)
122 | end
123 | end
124 | end
125 | end
126 |
--------------------------------------------------------------------------------
/app/models/bounty.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: bounties
4 | #
5 | # id :integer not null, primary key
6 | # absolute_value :integer not null
7 | # issue_id :integer not null
8 | # issuer_id :integer not null
9 | # claimant_id :integer
10 | # claimed_value :integer
11 | # claimed_at :datetime
12 | # created_at :datetime
13 | # updated_at :datetime
14 | #
15 |
16 | class Bounty < ActiveRecord::Base
17 | belongs_to :issue
18 | has_one :repository, through: :issue
19 | belongs_to :issuer, class_name: 'Coder', foreign_key: 'issuer_id'
20 | belongs_to :claimant, class_name: 'Coder', foreign_key: 'claimant_id'
21 |
22 | validates :issue, presence: true
23 | validates :issuer, presence: true
24 | validates :absolute_value, presence: true, numericality: {
25 | only_integer: true,
26 | greater_than_or_equal_to: 0
27 | }
28 |
29 | after_commit :clear_caches
30 | after_rollback :clear_caches
31 |
32 | delegate :to_s, to: :value
33 |
34 | def self.update_or_create(issue, coder, new_value)
35 | # Find the bounty for this issue if it already exists, or make a new one
36 | bounty = Bounty.find_or_create_by(issue: issue,
37 | issuer: coder,
38 | claimed_at: nil) do |b|
39 | b.absolute_value = 0
40 | end
41 |
42 | bounty.update_value!(new_value)
43 | end
44 |
45 | def update_value!(new_value)
46 | # Check whether the user has got enought points to spend
47 | delta = new_value - value
48 | if delta > issuer.bounty_residual
49 | fail Error, "You don\'t have enough bounty points to put a bounty of"\
50 | ' this amount.'
51 | end
52 |
53 | self.value += delta
54 |
55 | issuer.bounty_residual -= delta
56 |
57 | # Try to save the bounty, update the remaining bounty points, and return
58 | # some possibly updated records
59 | # It is important that this happens at the same time, so
60 | # BountyPoints.total_bounty_points stays the same!
61 | transaction do
62 | unless save
63 | fail Error, 'There occured an error while trying to save your bounty'\
64 | " (#{bounty.errors.full_messages})"
65 | end
66 |
67 | unless issuer.save
68 | fail Error, 'There occured an error while trying to adjust your'\
69 | ' remaining bounty points'\
70 | " (#{bounty.errors.full_messages})"
71 | end
72 | end
73 |
74 | PublishBounty.new(self).call
75 | end
76 |
77 | def claim!(time: Time.zone.now)
78 | return if claimed_at # This bounty has already been claimed
79 | if issue.assignee && issue.assignee != issuer
80 | # Reward assignee
81 | pinpoint_value!(coder: issue.assignee, time: time)
82 | issue.assignee.reward_bounty!(self)
83 | else
84 | # Refund bounty points
85 | issuer.refund_bounty!(self)
86 | # This bounty is of no use; destroy it.
87 | destroy!
88 | end
89 | end
90 |
91 | def value
92 | claimed_value || BountyPoints.bounty_points_from_abs(absolute_value)
93 | end
94 |
95 | def value=(new_value)
96 | if claimed_at
97 | fail Error, 'Trying to set a new value to an already claimed bounty!'
98 | end
99 | self.absolute_value = BountyPoints.bounty_points_to_abs(new_value)
100 | end
101 |
102 | def pinpoint_value!(coder: nil, time: Time.current)
103 | self.claimant = coder
104 | self.claimed_at = time
105 | self.claimed_value = self.value
106 | self.absolute_value = 0
107 | save!
108 | end
109 |
110 | class Error < StandardError
111 | end
112 |
113 | private
114 |
115 | def clear_caches
116 | BountyPoints.expire_assigned_bounty_points
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/sb-admin.css:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Start Bootstrap - http://startbootstrap.com
3 | 'SB Admin' HTML Template by Start Bootstrap
4 |
5 | All Start Bootstrap themes are licensed under Apache 2.0.
6 | For more info and more free Bootstrap 3 HTML themes, visit http://startbootstrap.com!
7 | */
8 |
9 | /* ATTN: This is mobile first CSS - to update 786px and up screen width use the media query near the bottom of the document! */
10 |
11 | /* Global Styles */
12 |
13 | body {
14 | margin-top: 50px;
15 | }
16 |
17 | #wrapper {
18 | padding-left: 0;
19 | }
20 |
21 | #page-wrapper {
22 | width: 100%;
23 | padding: 5px 15px;
24 | }
25 |
26 | /* Nav Messages */
27 |
28 | .messages-dropdown .dropdown-menu .message-preview .avatar,
29 | .messages-dropdown .dropdown-menu .message-preview .name,
30 | .messages-dropdown .dropdown-menu .message-preview .message,
31 | .messages-dropdown .dropdown-menu .message-preview .time {
32 | display: block;
33 | }
34 |
35 | .messages-dropdown .dropdown-menu .message-preview .avatar {
36 | float: left;
37 | margin-right: 15px;
38 | }
39 |
40 | .messages-dropdown .dropdown-menu .message-preview .name {
41 | font-weight: bold;
42 | }
43 |
44 | .messages-dropdown .dropdown-menu .message-preview .message {
45 | font-size: 12px;
46 | }
47 |
48 | .messages-dropdown .dropdown-menu .message-preview .time {
49 | font-size: 12px;
50 | }
51 |
52 |
53 | /* Nav Announcements */
54 |
55 | .announcement-heading {
56 | font-size: 50px;
57 | margin: 0;
58 | }
59 |
60 | .announcement-text {
61 | margin: 0;
62 | }
63 |
64 | /* Table Headers */
65 |
66 | table.tablesorter thead {
67 | cursor: pointer;
68 | }
69 |
70 | table.tablesorter thead tr th:hover {
71 | background-color: #f5f5f5;
72 | }
73 |
74 | /* Flot Chart Containers */
75 |
76 | .flot-chart {
77 | display: block;
78 | height: 400px;
79 | }
80 |
81 | .flot-chart-content {
82 | width: 100%;
83 | height: 100%;
84 | }
85 |
86 | /* Edit Below to Customize Widths > 768px */
87 | @media (min-width:768px) {
88 |
89 | /* Wrappers */
90 |
91 | #wrapper {
92 | padding-left: 225px;
93 | }
94 |
95 | #page-wrapper {
96 | padding: 15px 25px;
97 | }
98 |
99 | /* Side Nav */
100 |
101 | .side-nav {
102 | margin-left: -225px;
103 | left: 225px;
104 | width: 225px;
105 | position: fixed;
106 | top: 50px;
107 | height: 100%;
108 | border-radius: 0;
109 | border: none;
110 | background-color: #222222;
111 | overflow-y: auto;
112 | }
113 |
114 | /* Bootstrap Default Overrides - Customized Dropdowns for the Side Nav */
115 |
116 | .side-nav>li.dropdown>ul.dropdown-menu {
117 | position: relative;
118 | min-width: 225px;
119 | margin: 0;
120 | padding: 0;
121 | border: none;
122 | border-radius: 0;
123 | background-color: transparent;
124 | box-shadow: none;
125 | -webkit-box-shadow: none;
126 | }
127 |
128 | .side-nav>li.dropdown>ul.dropdown-menu>li>a {
129 | color: #999999;
130 | padding: 15px 15px 15px 25px;
131 | }
132 |
133 | .side-nav>li.dropdown>ul.dropdown-menu>li>a:hover,
134 | .side-nav>li.dropdown>ul.dropdown-menu>li>a.active,
135 | .side-nav>li.dropdown>ul.dropdown-menu>li>a:focus {
136 | color: #fff;
137 | background-color: #080808;
138 | }
139 |
140 | .side-nav>li>a {
141 | width: 225px;
142 | }
143 |
144 | .navbar-inverse .navbar-nav>li>a:hover,
145 | .navbar-inverse .navbar-nav>li>a:focus {
146 | background-color: #080808;
147 | }
148 |
149 | /* Nav Messages */
150 |
151 | .messages-dropdown .dropdown-menu {
152 | min-width: 300px;
153 | }
154 |
155 | .messages-dropdown .dropdown-menu li a {
156 | white-space: normal;
157 | }
158 |
159 | .navbar-collapse {
160 | padding-left: 15px !important;
161 | padding-right: 15px !important;
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in
3 | # config/application.rb.
4 |
5 | # Host, to be used for routes and Action Mailer.
6 | config.x.host = 'zeus.ugent.be'
7 | config.relative_url_root = '/game'
8 |
9 | # Code is not reloaded between requests.
10 | config.cache_classes = true
11 |
12 | # Eager load code on boot. This eager loads most of Rails and
13 | # your application in memory, allowing both threaded web servers
14 | # and those relying on copy on write to perform better.
15 | # Rake tasks automatically ignore this option for performance.
16 | config.eager_load = true
17 |
18 | # Full error reports are disabled and caching is turned on.
19 | config.consider_all_requests_local = false
20 | config.action_controller.perform_caching = true
21 |
22 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
23 | # Add `rack-cache` to your Gemfile before enabling this.
24 | # For large-scale production use, consider using a caching reverse proxy like
25 | # nginx, varnish or squid.
26 | # config.action_dispatch.rack_cache = true
27 |
28 | # Disable Rails's static asset server (Apache or nginx will already do this).
29 | config.serve_static_assets = false
30 |
31 | # Compress JavaScripts and CSS.
32 | config.assets.js_compressor = :uglifier
33 | # config.assets.css_compressor = :sass
34 |
35 | # Do not fallback to assets pipeline if a precompiled asset is missed.
36 | config.assets.compile = false
37 |
38 | # Generate digests for assets URLs.
39 | config.assets.digest = true
40 |
41 | # `config.assets.precompile` has moved to config/initializers/assets.rb
42 |
43 | # Specifies the header that your server uses for sending files.
44 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
45 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
46 |
47 | # Force all access to the app over SSL, use Strict-Transport-Security, and
48 | # use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Set to :debug to see everything in the log.
52 | config.log_level = :info
53 |
54 | # Prepend all log lines with the following tags.
55 | # config.log_tags = [ :subdomain, :uuid ]
56 |
57 | # Use a different logger for distributed setups.
58 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
59 |
60 | # Use a different cache store in production.
61 | # config.cache_store = :mem_cache_store
62 |
63 | # Enable serving of images, stylesheets, and JavaScripts from an asset
64 | # server.
65 | # config.action_controller.asset_host = "http://assets.example.com"
66 |
67 | # Precompile additional assets. application.js, application.css, and all
68 | # non-JS/CSS in app/assets folder are already added.
69 | # config.assets.precompile += %w( search.js )
70 |
71 | # Ignore bad email addresses and do not raise email delivery errors.
72 | # Set this to true and configure the email server for immediate delivery to
73 | # raise delivery errors.
74 | # config.action_mailer.raise_delivery_errors = false
75 | config.action_mailer.default_url_options = {
76 | host: config.x.host,
77 | script_name: config.relative_url_root
78 | }
79 |
80 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
81 | # the I18n.default_locale when a translation cannot be found).
82 | config.i18n.fallbacks = true
83 |
84 | # Send deprecation notices to registered listeners.
85 | config.active_support.deprecation = :notify
86 |
87 | # Disable automatic flushing of the log to improve performance.
88 | # config.autoflush_log = false
89 |
90 | # Use default logging formatter so that PID and timestamp are not suppressed.
91 | config.log_formatter = ::Logger::Formatter.new
92 |
93 | # Do not dump schema after migrations.
94 | config.active_record.dump_schema_after_migration = false
95 | end
96 |
97 | # Remove me when updated to > 4.2
98 | Rails.application.routes.default_url_options[:script_name] = '/game'
99 |
--------------------------------------------------------------------------------
/spec/controllers/bounties_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe BountiesController, type: :controller do
2 | # include Devise::TestHelpers
3 |
4 | before :each do
5 | @issue = create :issue
6 | @coder = create :coder, absolute_bounty_residual: 100
7 | sign_in @coder
8 | end
9 |
10 | it 'correctly sets current user' do
11 | expect(subject.current_coder).to eq(@coder)
12 | end
13 |
14 | context 'bounty' do
15 | before :each do
16 | # Place a bounty
17 | put :update_or_create,
18 | bounty: { issue_id: @issue, value: 10 },
19 | format: :coffee
20 | end
21 |
22 | it 'is placed' do
23 | expect(@issue.bounties.count).to eq(1)
24 | end
25 |
26 | it 'has correct bounty value' do
27 | expect(@issue.total_bounty_value).to eq(10)
28 | end
29 |
30 | it 'was paid for by issuer' do
31 | @coder.reload
32 | expect(@issue.total_bounty_value).to eq(10)
33 | expect(@coder.bounty_residual).to eq(90)
34 | end
35 |
36 | context 'when updated' do
37 | before :each do
38 | put :update_or_create,
39 | bounty: { issue_id: @issue, value: 20 },
40 | format: :coffee
41 | end
42 |
43 | it 'does not create a new bounty' do
44 | expect(@issue.bounties.count).to eq(1)
45 | end
46 |
47 | it 'updates value' do
48 | expect(@issue.total_bounty_value).to eq(20)
49 | end
50 | end
51 | end
52 |
53 | context 'dumb coder' do
54 | it 'cannot place bounties with insufficient bounty points' do
55 | put :update_or_create,
56 | bounty: { issue_id: @issue, value: 110 },
57 | format: :coffee
58 | expect(@coder.bounty_residual).to eq(100)
59 | expect(@issue.total_bounty_value).to eq(0)
60 | end
61 |
62 | it 'cannot place bounties with negative value' do
63 | put :update_or_create,
64 | bounty: { issue_id: @issue, value: -10 },
65 | format: :coffee
66 | expect(@coder.bounty_residual).to eq(100)
67 | expect(@issue.total_bounty_value).to eq(0)
68 | end
69 |
70 | it 'cannot place bounties with non-numeric value' do
71 | put :update_or_create,
72 | bounty: { issue_id: @issue, value: 'A' },
73 | format: :coffee
74 | expect(@coder.bounty_residual).to eq(100)
75 | expect(@issue.total_bounty_value).to eq(0)
76 | end
77 | end
78 |
79 | context 'claimed issue' do
80 | before :each do
81 | # First claim
82 | @first_claimant = create :coder
83 | @issue.assignee = @first_claimant
84 | put :update_or_create,
85 | bounty: { issue_id: @issue, value: 20 },
86 | format: :coffee
87 | @issue.close!
88 | end
89 |
90 | it 'has a claimed bounty' do
91 | expect(
92 | Bounty.where(issue: @issue).where.not(claimed_at: nil).count
93 | ).to eq(1)
94 | end
95 |
96 | context 'when reopened' do
97 | before :each do
98 | # Reopen and put a second bounty
99 | @issue.closed_at = nil
100 | put :update_or_create,
101 | bounty: { issue_id: @issue, value: 20 },
102 | format: :coffee
103 | end
104 |
105 | it 'has a claimed bounty' do
106 | expect(@issue.bounties.where.not(claimed_at: nil).count).to eq(1)
107 | end
108 |
109 | it 'has an unclaimed bounty' do
110 | expect(@issue.bounties.where(claimed_at: nil).count).to eq(1)
111 | end
112 |
113 | context 'and claimed again' do
114 | before :each do
115 | @second_claimant = create :coder
116 | @issue.assignee = @second_claimant
117 | @issue.close!
118 | end
119 |
120 | it 'rewarded first claimant' do
121 | expect(@first_claimant.claimed_value).to eq(20)
122 | end
123 |
124 | it 'rewarded second claimant' do
125 | expect(@second_claimant.claimed_value).to eq(20)
126 | end
127 | end
128 | end
129 | end
130 | end
131 |
--------------------------------------------------------------------------------
/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your account was successfully confirmed."
7 | send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid email or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account will be locked."
15 | not_found_in_database: "Invalid email or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your account before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock Instructions"
26 | omniauth_callbacks:
27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
28 | success: "Successfully authenticated from %{kind} account."
29 | passwords:
30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
31 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
33 | updated: "Your password was changed successfully. You are now signed in."
34 | updated_not_active: "Your password was changed successfully."
35 | registrations:
36 | destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon."
37 | signed_up: "Welcome! You have signed up successfully."
38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address."
42 | updated: "You updated your account successfully."
43 | sessions:
44 | signed_in: "Signed in successfully."
45 | signed_out: "Signed out successfully."
46 | unlocks:
47 | send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes."
48 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes."
49 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
50 | errors:
51 | messages:
52 | already_confirmed: "was already confirmed, please try signing in"
53 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
54 | expired: "has expired, please request a new one"
55 | not_found: "not found"
56 | not_locked: "was not locked"
57 | not_saved:
58 | one: "1 error prohibited this %{resource} from being saved:"
59 | other: "%{count} errors prohibited this %{resource} from being saved:"
60 |
--------------------------------------------------------------------------------
/spec/models/bounty_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: bounties
4 | #
5 | # id :integer not null, primary key
6 | # absolute_value :integer not null
7 | # issue_id :integer not null
8 | # issuer_id :integer not null
9 | # claimant_id :integer
10 | # claimed_value :integer
11 | # claimed_at :datetime
12 | # created_at :datetime
13 | # updated_at :datetime
14 | #
15 |
16 | describe Bounty do
17 | before :each do
18 | @issuer = create :coder
19 | @issue = create :issue
20 | @bounty = create :bounty, issue: @issue, issuer: @issuer, absolute_value: 100
21 | end
22 |
23 | it 'has a valid factory' do
24 | expect(@bounty).to be_valid
25 | end
26 |
27 | it 'refunds bounty points when issue was not claimed' do
28 | @bounty.claim!
29 | expect(@issuer.bounty_residual).to eq(100)
30 | end
31 |
32 | context 'claimed by issuer' do
33 | before :each do
34 | @issue.assignee = @issuer
35 | @bounty.claim!
36 | end
37 |
38 | it 'refunds bounty points' do
39 | expect(@issuer.bounty_residual).to eq(100)
40 | end
41 |
42 | it 'deletes itself' do
43 | expect(@bounty.destroyed?).to be_truthy
44 | end
45 | end
46 |
47 | context 'when claimed' do
48 | before :each do
49 | @claimant = create :coder
50 | @issue.assignee = @claimant
51 | @bounty.claim!
52 | end
53 |
54 | it 'sets claimed time' do
55 | expect(@bounty.claimed_at).to be
56 | end
57 |
58 | it 'sets claimed value' do
59 | expect(@bounty.claimed_value).to eq(100)
60 | end
61 |
62 | it 'sets claimant' do
63 | expect(@bounty.claimant).to eq(@claimant)
64 | end
65 |
66 | it 'cannot be claimed again' do
67 | contender = create :coder
68 | @issue.assignee = contender
69 | @bounty.claim!
70 | expect(@bounty.claimant).to eq(@claimant)
71 | end
72 |
73 | it 'clears its bounty value' do
74 | expect(@bounty.absolute_value).to eq(0)
75 | end
76 | end
77 |
78 | context 'with scaled value' do
79 | before :each do
80 | @limit = Rails.application.config.total_bounty_value
81 | @bounty.update!(absolute_value: @limit * 2)
82 | @assignee = create :coder
83 | @bounty.issue.assignee = @assignee
84 | end
85 |
86 | it 'has a scaled value' do
87 | expect(@bounty.absolute_value).to eq(2 * @limit)
88 | expect(@bounty.value).to eq(@limit)
89 | end
90 |
91 | it 'rewards a scaled value' do
92 | @bounty.claim!
93 | expect(@assignee.reward_residual).to eq(@limit)
94 | end
95 |
96 | it 'rewards a scaled amount of bounty points' do
97 | @bounty.claim!
98 | expect(@assignee.absolute_bounty_residual).to eq(2 * @limit)
99 | expect(@assignee.bounty_residual).to eq(@limit)
100 | end
101 | end
102 |
103 | context 'with other bounty points in the running' do
104 | before :each do
105 | @limit = Rails.application.config.total_bounty_value
106 | @issuer.update!(absolute_bounty_residual: @limit - 100)
107 | @other_coder = create :coder, absolute_bounty_residual: 2 * @limit
108 | end
109 |
110 | it 'has a rescaled value' do
111 | expect(@bounty.absolute_value).to eq(100)
112 | expect(@bounty.value).to eq(33)
113 | end
114 |
115 | context 'when claimed' do
116 | before :each do
117 | @issue.assignee = @other_coder
118 | @bounty.claim!
119 | end
120 |
121 | it 'has a claimed value, but no absolute_value' do
122 | expect(@bounty.absolute_value).to eq(0)
123 | expect(@bounty.claimed_value).to eq(33)
124 | expect(@bounty.value).to eq(33)
125 | end
126 |
127 | context 'and when other bounties come to exist' do
128 | before :each do
129 | @other_bounty = create :bounty, absolute_value: 1000
130 | end
131 |
132 | it 'has a claimed value, but no absolute_value' do
133 | expect(@bounty.absolute_value).to eq(0)
134 | expect(@bounty.claimed_value).to eq(33)
135 | expect(@bounty.value).to eq(33)
136 | end
137 | end
138 | end
139 | end
140 | end
141 |
--------------------------------------------------------------------------------
/app/assets/javascripts/top4.js.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
5 |
6 | $('body.top4.show').ready ->
7 | # `repos` and `contributors` are defined before this gets executed
8 |
9 | for [repo, repo_contributors] in d3.zip(repos, contributors)
10 | do ([repo, repo_contributors]) ->
11 | # Only select the top 3 contributors and put the rest in and 'other' user
12 | repo.contributors = repo_contributors[0..2]
13 | if repo_contributors.length > 3
14 | repo.contributors +=
15 | github_name: 'other'
16 | score: d3.sum(repo_contributors[3..], (d) -> d.score)
17 |
18 | # Calculate offset for contributor bars
19 | sum = 0
20 | for contributor in repo.contributors by -1
21 | do (contributor) ->
22 | contributor.y0 = sum
23 | sum += contributor.score
24 |
25 | margin = {top: 20, right: 0, bottom: 30, left: 0}
26 | width = 600 - margin.right - margin.left
27 | height = 300 - margin.top - margin.bottom
28 |
29 | x = d3.scale.ordinal()
30 | .rangeRoundBands([0, width], .1)
31 | .domain(repos.map((d) -> d.name))
32 |
33 | y = d3.scale.linear()
34 | .range([0, height])
35 | .domain([0, d3.max(repos, (d) -> d.score)])
36 |
37 | colors = d3.scale.ordinal()
38 | .range(['#1976d2','#2196f3', '#64b5f6','#bbdefb'])
39 | .domain([0,1,2,3])
40 |
41 | chart = d3.select('#top-repos')
42 | .append('g')
43 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
44 |
45 | repo_groups = chart.selectAll('g')
46 | .data(repos)
47 | .enter().append('g')
48 | .attr 'transform', (d) ->
49 | 'translate(' + x(d.name) + ',' + (height - y(d.score)) + ')'
50 |
51 | repo_groups.append('rect')
52 | .attr('class', 'repo-bar')
53 | .attr('y', (d) -> y(d.score))
54 | .attr('height', 0)
55 | .attr('width', x.rangeBand())
56 | .transition().duration(1000)
57 | .attr('y', 0)
58 | .attr('height', (d) -> y(d.score))
59 |
60 | repo_groups.append('text')
61 | .attr('class', 'repo-label')
62 | .attr('x', x.rangeBand() / 2)
63 | .attr('y', '.5em')
64 | .attr('opacity', 0)
65 | .text((d) -> d.score)
66 |
67 | contributor_groups = repo_groups.selectAll('.contributor')
68 | .data((d) -> d.contributors)
69 | .enter().append('g')
70 | .attr('class', 'contributor')
71 | .attr('transform', 'translate(0,' + height + ')')
72 |
73 | # Collapse groups
74 | repo_groups.each (d) ->
75 | d3.select(this)
76 | .selectAll('.contributor')
77 | .attr('transform', 'translate(0,' + y(d.score) + ')')
78 |
79 | contributor_groups
80 | .append('rect')
81 | .attr('class', 'contributor-bar')
82 | .attr('height', 0)
83 | .attr('width', x.rangeBand())
84 | .style('fill', (d, i) -> colors(i))
85 |
86 | contributor_groups
87 | .filter((d) -> y(d.score) > 15)
88 | .append('text')
89 | .attr('class', 'contributor-name')
90 | .attr('x', x.rangeBand() / 2)
91 | .attr('y', '1em')
92 | .text((d) -> d.github_name)
93 |
94 | repo_groups
95 | .on 'mouseenter', (d) ->
96 | t = d3.select(this).transition().duration(500)
97 | # Breakdown
98 | t.selectAll('.contributor')
99 | .attr('transform', (d) -> 'translate(0,' + y(d.y0) + ')')
100 | t.selectAll('.contributor-bar')
101 | .attr('height', (d) -> y(d.score))
102 | # Label
103 | t.select('.repo-label')
104 | .attr('y', '-.25em')
105 | .attr('opacity', 1)
106 | .on 'mouseleave', (d) ->
107 | t = d3.select(this).transition().duration(500)
108 | # Breakdown
109 | t.selectAll('.contributor')
110 | .attr('transform', 'translate(0,' + y(d.score) + ')')
111 | t.selectAll('.contributor-bar')
112 | .attr('height', 0)
113 | # Label
114 | t.select('.repo-label')
115 | .attr('y', '.5em')
116 | .attr('opacity', 0)
117 |
118 | xAxis = d3.svg.axis()
119 | .scale(x)
120 | .orient('bottom')
121 |
122 | repo_name_nodes = chart.append('g')
123 | .attr('class', 'axis xaxis')
124 | .attr('transform', 'translate(0,' + height + ')')
125 | .call(xAxis)
126 | .selectAll('text')
127 | .attr('dy', '1em')
128 |
129 | # Add links to repo names in x axis
130 | d3.zip(repo_name_nodes[0], repos).forEach (tuple) ->
131 | text_node = $(tuple[0])
132 | repo = tuple[1]
133 | text_node
134 | .css('cursor', 'pointer')
135 | .hover(
136 | -> $(this).css('text-decoration', 'underline')
137 | -> $(this).css('text-decoration', 'inherit')
138 | )
139 | .click (event) ->
140 | if event.ctrlKey
141 | window.open(repo.base_uri, '_blank')
142 | else
143 | document.location.href = repo.base_uri
144 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20150829193440) do
15 |
16 | create_table "bounties", force: true do |t|
17 | t.integer "absolute_value", null: false
18 | t.integer "issue_id", null: false
19 | t.integer "issuer_id", null: false
20 | t.integer "claimant_id"
21 | t.integer "claimed_value"
22 | t.datetime "claimed_at"
23 | t.datetime "created_at"
24 | t.datetime "updated_at"
25 | end
26 |
27 | add_index "bounties", ["claimant_id"], name: "index_bounties_on_claimant_id", using: :btree
28 | add_index "bounties", ["issue_id", "issuer_id"], name: "index_bounties_on_issue_id_and_issuer_id", using: :btree
29 | add_index "bounties", ["issue_id"], name: "index_bounties_on_issue_id", using: :btree
30 | add_index "bounties", ["issuer_id"], name: "index_bounties_on_issuer_id", using: :btree
31 |
32 | create_table "coders", force: true do |t|
33 | t.string "github_name", null: false
34 | t.string "full_name", default: "", null: false
35 | t.string "avatar_url", null: false
36 | t.string "github_url", null: false
37 | t.integer "reward_residual", default: 0, null: false
38 | t.integer "absolute_bounty_residual", default: 0, null: false
39 | t.integer "other_score", default: 0, null: false
40 | t.datetime "created_at"
41 | t.datetime "updated_at"
42 | end
43 |
44 | add_index "coders", ["github_name"], name: "index_coders_on_github_name", unique: true, using: :btree
45 | add_index "coders", ["github_url"], name: "index_coders_on_github_url", unique: true, using: :btree
46 |
47 | create_table "commits", force: true do |t|
48 | t.integer "coder_id"
49 | t.integer "repository_id"
50 | t.string "sha", null: false
51 | t.integer "additions", default: 0, null: false
52 | t.integer "deletions", default: 0, null: false
53 | t.datetime "date", null: false
54 | t.datetime "created_at"
55 | t.datetime "updated_at"
56 | end
57 |
58 | add_index "commits", ["coder_id"], name: "index_commits_on_coder_id", using: :btree
59 | add_index "commits", ["repository_id", "sha"], name: "index_commits_on_repository_id_and_sha", unique: true, using: :btree
60 | add_index "commits", ["repository_id"], name: "index_commits_on_repository_id", using: :btree
61 |
62 | create_table "git_identities", force: true do |t|
63 | t.string "name", null: false
64 | t.string "email", null: false
65 | t.integer "coder_id"
66 | t.datetime "created_at"
67 | t.datetime "updated_at"
68 | end
69 |
70 | add_index "git_identities", ["coder_id"], name: "index_git_identities_on_coder_id", using: :btree
71 | add_index "git_identities", ["name", "email"], name: "index_git_identities_on_name_and_email", unique: true, using: :btree
72 |
73 | create_table "issues", force: true do |t|
74 | t.string "github_url", null: false
75 | t.integer "number", null: false
76 | t.string "title", default: "Untitled", null: false
77 | t.integer "issuer_id", null: false
78 | t.integer "repository_id", null: false
79 | t.text "labels", null: false
80 | t.text "body"
81 | t.integer "assignee_id"
82 | t.string "milestone"
83 | t.datetime "opened_at", null: false
84 | t.datetime "closed_at"
85 | t.datetime "created_at"
86 | t.datetime "updated_at"
87 | end
88 |
89 | add_index "issues", ["github_url"], name: "index_issues_on_github_url", unique: true, using: :btree
90 | add_index "issues", ["repository_id", "number"], name: "index_issues_on_repository_id_and_number", unique: true, using: :btree
91 | add_index "issues", ["repository_id"], name: "index_issues_on_repository_id", using: :btree
92 |
93 | create_table "repositories", force: true do |t|
94 | t.string "name", null: false
95 | t.string "github_url", null: false
96 | t.string "clone_url", null: false
97 | t.datetime "created_at"
98 | t.datetime "updated_at"
99 | end
100 |
101 | end
102 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'coveralls'
2 | Coveralls.wear!
3 |
4 | # This file was generated by the `rails generate rspec:install` command.
5 | # Conventionally, all specs live under a `spec` directory, which RSpec adds to
6 | # the `$LOAD_PATH`. The generated `.rspec` file contains `--require
7 | # spec_helper` which will cause this file to always be loaded, without a need
8 | # to explicitly require it in any files.
9 | #
10 | # Given that it is always loaded, you are encouraged to keep this file as
11 | # light-weight as possible. Requiring heavyweight dependencies from this file
12 | # will add to the boot time of your test suite on EVERY test run, even for an
13 | # individual file that may not need all of that loaded. Instead, consider
14 | # making a separate helper file that requires the additional dependencies and
15 | # performs the additional setup, and require it from the spec files that
16 | # actually need it.
17 | #
18 | # The `.rspec` file also contains a few flags that are not defaults but that
19 | # users commonly want.
20 | #
21 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22 |
23 | require 'factory_girl'
24 | require 'devise'
25 | # Requrire all files in spec/support/
26 | Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each do |f|
27 | require f
28 | end
29 |
30 | RSpec.configure do |config|
31 | # use FactoryGirl
32 | config.include FactoryGirl::Syntax::Methods
33 | config.include Devise::TestHelpers, type: :controller
34 | # rspec-expectations config goes here. You can use an alternate
35 | # assertion/expectation library such as wrong or the stdlib/minitest
36 | # assertions if you prefer.
37 | config.expect_with :rspec do |expectations|
38 | # This option will default to `true` in RSpec 4. It makes the `description`
39 | # and `failure_message` of custom matchers include text for helper methods
40 | # defined using `chain`, e.g.:
41 | # be_bigger_than(2).and_smaller_than(4).description
42 | # # => "be bigger than 2 and smaller than 4"
43 | # ...rather than:
44 | # # => "be bigger than 2"
45 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
46 | end
47 |
48 | # rspec-mocks config goes here. You can use an alternate test double
49 | # library (such as bogus or mocha) by changing the `mock_with` option here.
50 | config.mock_with :rspec do |mocks|
51 | # Prevents you from mocking or stubbing a method that does not exist on
52 | # a real object. This is generally recommended, and will default to
53 | # `true` in RSpec 4.
54 | mocks.verify_partial_doubles = true
55 | end
56 |
57 | # Let RSpec remember failures, so we can run '--only-failures' next time.
58 | config.example_status_persistence_file_path = './spec/.failures.txt'
59 |
60 | # The settings below are suggested to provide a good initial experience
61 | # with RSpec, but feel free to customize to your heart's content.
62 | # # These two settings work together to allow you to limit a spec run
63 | # # to individual examples or groups you care about by tagging them with
64 | # # `:focus` metadata. When nothing is tagged with `:focus`, all examples
65 | # # get run.
66 | # config.filter_run :focus
67 | # config.run_all_when_everything_filtered = true
68 | #
69 | # # Limits the available syntax to the non-monkey patched syntax that is
70 | # # recommended.
71 | # # For more details, see:
72 | # # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
73 | # # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
74 | # # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
75 | # config.disable_monkey_patching!
76 | #
77 | # # Many RSpec users commonly either run the entire suite or an individual
78 | # # file, and it's useful to allow more verbose output when running an
79 | # # individual spec file.
80 | # if config.files_to_run.one?
81 | # # Use the documentation formatter for detailed output,
82 | # # unless a formatter has already been configured
83 | # # (e.g. via a command-line flag).
84 | # config.default_formatter = 'doc'
85 | # end
86 | #
87 | # # Print the 10 slowest examples and example groups at the
88 | # # end of the spec run, to help surface which specs are running
89 | # # particularly slow.
90 | # config.profile_examples = 10
91 | #
92 | # # Run specs in random order to surface order dependencies. If you find an
93 | # # order dependency and want to debug it, you can fix the order by
94 | # # providing the seed, which is printed after each run.
95 | # # --seed 1234
96 | # config.order = :random
97 | #
98 | # # Seed global randomization in this process using the `--seed` CLI
99 | # # option. Setting this allows you to use `--seed` to deterministically
100 | # # reproduce test failures related to randomization by passing the same
101 | # # `--seed` value as the one that triggered the failure.
102 | # Kernel.srand config.seed
103 | end
104 |
--------------------------------------------------------------------------------
/spec/models/coder_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: coders
4 | #
5 | # id :integer not null, primary key
6 | # github_name :string(255) not null
7 | # full_name :string(255) default(""), not null
8 | # avatar_url :string(255) not null
9 | # github_url :string(255) not null
10 | # reward_residual :integer default(0), not null
11 | # absolute_bounty_residual :integer default(0), not null
12 | # other_score :integer default(0), not null
13 | # created_at :datetime
14 | # updated_at :datetime
15 | #
16 |
17 | describe Coder do
18 | before :each do
19 | @coder = create :coder
20 | end
21 |
22 | it 'has a valid factory' do
23 | expect(@coder).to be_valid
24 | end
25 |
26 | context 'with commits' do
27 | before :each do
28 | create :commit, coder: @coder, additions: 10, deletions: 4
29 | create :commit, coder: @coder, additions: 20, deletions: 8
30 | create :commit, coder: @coder, additions: 12, deletions: 7
31 |
32 | @addition_score = [10, 20, 12].map do |n|
33 | Rails.application.config.addition_score_factor *
34 | Math.log(n + 1)
35 | end.map(&:round).sum
36 |
37 | @coder.commits.each do |commit|
38 | @coder.reward_commit! commit
39 | end
40 | end
41 |
42 | it 'has a correct addition count' do
43 | expect(@coder.additions).to eq(42)
44 | end
45 |
46 | it 'has a correct deletion count' do
47 | expect(@coder.deletions).to eq(19)
48 | end
49 |
50 | it 'was granted reward points' do
51 | expect(@coder.reward_residual).to eq(@addition_score)
52 | end
53 |
54 | it 'was granted bounty points' do
55 | expect(@coder.bounty_residual).to eq(@addition_score)
56 | end
57 |
58 | it 'has a correct score' do
59 | expect(@coder.score).to eq(@addition_score)
60 | end
61 | end
62 |
63 | context 'with claimed bounty' do
64 | before :each do
65 | @issue = create :issue, assignee: @coder
66 | @bounty = create :bounty, issue: @issue, absolute_value: 100
67 | @bounty.claim!
68 | end
69 |
70 | it 'has a corrent claimed_value stat' do
71 | expect(@coder.claimed_value).to eq(100)
72 | end
73 |
74 | it 'has a correct score' do
75 | expect(@coder.score).to eq(100)
76 | end
77 |
78 | it 'was granted reward points' do
79 | expect(@coder.reward_residual).to eq(100)
80 | end
81 |
82 | it 'was granted bounty points' do
83 | expect(@coder.bounty_residual).to eq(100)
84 | end
85 | end
86 |
87 | context 'with other bounty points in the running' do
88 | before :each do
89 | @limit = Rails.application.config.total_bounty_value
90 | @coder.update!(absolute_bounty_residual: @limit)
91 | @other_coder = create :coder, absolute_bounty_residual: 2 * @limit
92 | end
93 |
94 | it 'ensures a correct total amount of bounty points' do
95 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit)
96 | end
97 |
98 | it 'has a rescaled bounty residual' do
99 | expect(@coder.absolute_bounty_residual).to eq(@limit)
100 | expect(@other_coder.absolute_bounty_residual).to eq(2 * @limit)
101 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round)
102 | expect(@other_coder.bounty_residual).to eq((2 * @limit.to_f / 3).round)
103 | end
104 |
105 | context 'with a bounty placed' do
106 | before :each do
107 | @issue = create :issue
108 | @bounty = create :bounty, issue: @issue,
109 | absolute_value: 0,
110 | issuer: @coder
111 | @bounty.update_value!(100)
112 | end
113 |
114 | it 'ensures total amount of bounty points stays the same' do
115 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit)
116 | end
117 |
118 | it 'has a rescaled bounty residual' do
119 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round - 100)
120 | expect(@coder.absolute_bounty_residual).to almost_eq(((@limit.to_f / 3).round - 100) * 3)
121 | end
122 |
123 | context 'and self-claimed' do
124 | before :each do
125 | @issue.assignee = @coder
126 | @bounty.claim!
127 | end
128 |
129 | it 'ensures total amount of bounty points stays the same' do
130 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit)
131 | end
132 |
133 | it 'has his bounty points refunded' do
134 | expect(@coder.absolute_bounty_residual).to almost_eq(@limit)
135 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round)
136 | end
137 | end
138 |
139 | context 'and claimed by someone else' do
140 | before :each do
141 | @issue.assignee = @other_coder
142 | @bounty.claim!
143 | end
144 |
145 | it 'has attributed reward and bounty points to the other coder' do
146 | expect(@other_coder.bounty_residual).to eq((2 * @limit.to_f / 3).round + 100)
147 | expect(@other_coder.absolute_bounty_residual).to almost_eq(((2 * @limit.to_f / 3).round + 100) * 3)
148 | expect(@other_coder.reward_residual).to eq(100)
149 | end
150 | end
151 | end
152 | end
153 | end
154 |
--------------------------------------------------------------------------------
/spec/github_jsons/repo_create.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "created",
3 | "repository": {
4 | "id": 33015564,
5 | "name": "glowing-octo-dubstep",
6 | "full_name": "ZeusWPI/glowing-octo-dubstep",
7 | "owner": {
8 | "login": "ZeusWPI",
9 | "id": 331750,
10 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
11 | "gravatar_id": "",
12 | "url": "https://api.github.com/users/ZeusWPI",
13 | "html_url": "https://github.com/ZeusWPI",
14 | "followers_url": "https://api.github.com/users/ZeusWPI/followers",
15 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}",
16 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}",
17 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}",
18 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions",
19 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs",
20 | "repos_url": "https://api.github.com/users/ZeusWPI/repos",
21 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}",
22 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events",
23 | "type": "Organization",
24 | "site_admin": false
25 | },
26 | "private": true,
27 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
28 | "description": "",
29 | "fork": false,
30 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep",
31 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks",
32 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}",
33 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}",
34 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams",
35 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks",
36 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}",
37 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events",
38 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}",
39 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}",
40 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags",
41 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}",
42 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}",
43 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}",
44 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}",
45 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}",
46 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages",
47 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers",
48 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors",
49 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers",
50 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription",
51 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}",
52 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}",
53 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}",
54 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}",
55 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}",
56 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}",
57 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges",
58 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}",
59 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads",
60 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}",
61 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}",
62 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}",
63 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}",
64 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}",
65 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}",
66 | "created_at": "2015-03-28T00:35:00Z",
67 | "updated_at": "2015-03-28T00:35:00Z",
68 | "pushed_at": "2015-03-28T00:35:00Z",
69 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git",
70 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git",
71 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git",
72 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
73 | "homepage": null,
74 | "size": 0,
75 | "stargazers_count": 0,
76 | "watchers_count": 0,
77 | "language": null,
78 | "has_issues": true,
79 | "has_downloads": true,
80 | "has_wiki": true,
81 | "has_pages": false,
82 | "forks_count": 0,
83 | "mirror_url": null,
84 | "open_issues_count": 0,
85 | "forks": 0,
86 | "open_issues": 0,
87 | "watchers": 0,
88 | "default_branch": "master"
89 | },
90 | "organization": {
91 | "login": "ZeusWPI",
92 | "id": 331750,
93 | "url": "https://api.github.com/orgs/ZeusWPI",
94 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos",
95 | "events_url": "https://api.github.com/orgs/ZeusWPI/events",
96 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}",
97 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}",
98 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
99 | "description": null
100 | },
101 | "sender": {
102 | "login": "Iasoon",
103 | "id": 6320308,
104 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
105 | "gravatar_id": "",
106 | "url": "https://api.github.com/users/Iasoon",
107 | "html_url": "https://github.com/Iasoon",
108 | "followers_url": "https://api.github.com/users/Iasoon/followers",
109 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
110 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
111 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
112 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
113 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
114 | "repos_url": "https://api.github.com/users/Iasoon/repos",
115 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
116 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
117 | "type": "User",
118 | "site_admin": false
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/spec/github_jsons/push.json:
--------------------------------------------------------------------------------
1 | {
2 | "ref": "refs/heads/master",
3 | "before": "afbd137b4ebab456e4e0add4b158c6e6ed0adce6",
4 | "after": "be981ec9e53ef639361ffebbf88845c711fb07bd",
5 | "created": false,
6 | "deleted": false,
7 | "forced": false,
8 | "base_ref": null,
9 | "compare": "https://github.com/ZeusWPI/glowing-octo-dubstep/compare/afbd137b4eba...be981ec9e53e",
10 | "commits": [
11 | {
12 | "id": "be981ec9e53ef639361ffebbf88845c711fb07bd",
13 | "distinct": true,
14 | "message": "Belangrijke informatie toegevoegd",
15 | "timestamp": "2015-04-04T02:37:06+02:00",
16 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep/commit/be981ec9e53ef639361ffebbf88845c711fb07bd",
17 | "author": {
18 | "name": "Ilion Beyst",
19 | "email": "ilion.beyst@gmail.com",
20 | "username": "Iasoon"
21 | },
22 | "committer": {
23 | "name": "Ilion Beyst",
24 | "email": "ilion.beyst@gmail.com",
25 | "username": "Iasoon"
26 | },
27 | "added": [
28 |
29 | ],
30 | "removed": [
31 |
32 | ],
33 | "modified": [
34 | "README.md"
35 | ]
36 | }
37 | ],
38 | "head_commit": {
39 | "id": "be981ec9e53ef639361ffebbf88845c711fb07bd",
40 | "distinct": true,
41 | "message": "Belangrijke informatie toegevoegd",
42 | "timestamp": "2015-04-04T02:37:06+02:00",
43 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep/commit/be981ec9e53ef639361ffebbf88845c711fb07bd",
44 | "author": {
45 | "name": "Ilion Beyst",
46 | "email": "ilion.beyst@gmail.com",
47 | "username": "Iasoon"
48 | },
49 | "committer": {
50 | "name": "Ilion Beyst",
51 | "email": "ilion.beyst@gmail.com",
52 | "username": "Iasoon"
53 | },
54 | "added": [
55 |
56 | ],
57 | "removed": [
58 |
59 | ],
60 | "modified": [
61 | "README.md"
62 | ]
63 | },
64 | "repository": {
65 | "id": 33015564,
66 | "name": "glowing-octo-dubstep",
67 | "full_name": "ZeusWPI/glowing-octo-dubstep",
68 | "owner": {
69 | "name": "ZeusWPI",
70 | "email": null
71 | },
72 | "private": true,
73 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
74 | "description": "Top8",
75 | "fork": false,
76 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
77 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks",
78 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}",
79 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}",
80 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams",
81 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks",
82 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}",
83 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events",
84 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}",
85 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}",
86 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags",
87 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}",
88 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}",
89 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}",
90 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}",
91 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}",
92 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages",
93 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers",
94 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors",
95 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers",
96 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription",
97 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}",
98 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}",
99 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}",
100 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}",
101 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}",
102 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}",
103 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges",
104 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}",
105 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads",
106 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}",
107 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}",
108 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}",
109 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}",
110 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}",
111 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}",
112 | "created_at": 1427502900,
113 | "updated_at": "2015-03-28T02:25:20Z",
114 | "pushed_at": 1428107829,
115 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git",
116 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git",
117 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git",
118 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
119 | "homepage": "",
120 | "size": 0,
121 | "stargazers_count": 0,
122 | "watchers_count": 0,
123 | "language": null,
124 | "has_issues": true,
125 | "has_downloads": true,
126 | "has_wiki": true,
127 | "has_pages": false,
128 | "forks_count": 0,
129 | "mirror_url": null,
130 | "open_issues_count": 0,
131 | "forks": 0,
132 | "open_issues": 0,
133 | "watchers": 0,
134 | "default_branch": "master",
135 | "stargazers": 0,
136 | "master_branch": "master",
137 | "organization": "ZeusWPI"
138 | },
139 | "pusher": {
140 | "name": "Iasoon",
141 | "email": "ilion.beyst@gmail.com"
142 | },
143 | "organization": {
144 | "login": "ZeusWPI",
145 | "id": 331750,
146 | "url": "https://api.github.com/orgs/ZeusWPI",
147 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos",
148 | "events_url": "https://api.github.com/orgs/ZeusWPI/events",
149 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}",
150 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}",
151 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
152 | "description": null
153 | },
154 | "sender": {
155 | "login": "Iasoon",
156 | "id": 6320308,
157 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
158 | "gravatar_id": "",
159 | "url": "https://api.github.com/users/Iasoon",
160 | "html_url": "https://github.com/Iasoon",
161 | "followers_url": "https://api.github.com/users/Iasoon/followers",
162 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
163 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
164 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
165 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
166 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
167 | "repos_url": "https://api.github.com/users/Iasoon/repos",
168 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
169 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
170 | "type": "User",
171 | "site_admin": false
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/spec/github_jsons/issue_open.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "opened",
3 | "issue": {
4 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1",
5 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/labels{/name}",
6 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/comments",
7 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/events",
8 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep/issues/1",
9 | "id": 64936749,
10 | "number": 1,
11 | "title": "Problemen!",
12 | "user": {
13 | "login": "Iasoon",
14 | "id": 6320308,
15 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
16 | "gravatar_id": "",
17 | "url": "https://api.github.com/users/Iasoon",
18 | "html_url": "https://github.com/Iasoon",
19 | "followers_url": "https://api.github.com/users/Iasoon/followers",
20 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
21 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
22 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
23 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
24 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
25 | "repos_url": "https://api.github.com/users/Iasoon/repos",
26 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
27 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
28 | "type": "User",
29 | "site_admin": false
30 | },
31 | "labels": [
32 |
33 | ],
34 | "state": "open",
35 | "locked": false,
36 | "assignee": null,
37 | "milestone": null,
38 | "comments": 0,
39 | "created_at": "2015-03-28T12:28:07Z",
40 | "updated_at": "2015-03-28T12:28:07Z",
41 | "closed_at": null,
42 | "body": ""
43 | },
44 | "repository": {
45 | "id": 33015564,
46 | "name": "glowing-octo-dubstep",
47 | "full_name": "ZeusWPI/glowing-octo-dubstep",
48 | "owner": {
49 | "login": "ZeusWPI",
50 | "id": 331750,
51 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
52 | "gravatar_id": "",
53 | "url": "https://api.github.com/users/ZeusWPI",
54 | "html_url": "https://github.com/ZeusWPI",
55 | "followers_url": "https://api.github.com/users/ZeusWPI/followers",
56 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}",
57 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}",
58 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}",
59 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions",
60 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs",
61 | "repos_url": "https://api.github.com/users/ZeusWPI/repos",
62 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}",
63 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events",
64 | "type": "Organization",
65 | "site_admin": false
66 | },
67 | "private": true,
68 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
69 | "description": "Top8",
70 | "fork": false,
71 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep",
72 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks",
73 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}",
74 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}",
75 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams",
76 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks",
77 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}",
78 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events",
79 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}",
80 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}",
81 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags",
82 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}",
83 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}",
84 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}",
85 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}",
86 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}",
87 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages",
88 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers",
89 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors",
90 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers",
91 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription",
92 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}",
93 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}",
94 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}",
95 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}",
96 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}",
97 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}",
98 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges",
99 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}",
100 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads",
101 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}",
102 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}",
103 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}",
104 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}",
105 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}",
106 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}",
107 | "created_at": "2015-03-28T00:35:00Z",
108 | "updated_at": "2015-03-28T02:25:20Z",
109 | "pushed_at": "2015-03-28T02:33:02Z",
110 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git",
111 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git",
112 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git",
113 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
114 | "homepage": "",
115 | "size": 0,
116 | "stargazers_count": 0,
117 | "watchers_count": 0,
118 | "language": null,
119 | "has_issues": true,
120 | "has_downloads": true,
121 | "has_wiki": true,
122 | "has_pages": false,
123 | "forks_count": 0,
124 | "mirror_url": null,
125 | "open_issues_count": 1,
126 | "forks": 0,
127 | "open_issues": 1,
128 | "watchers": 0,
129 | "default_branch": "master"
130 | },
131 | "organization": {
132 | "login": "ZeusWPI",
133 | "id": 331750,
134 | "url": "https://api.github.com/orgs/ZeusWPI",
135 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos",
136 | "events_url": "https://api.github.com/orgs/ZeusWPI/events",
137 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}",
138 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}",
139 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
140 | "description": null
141 | },
142 | "sender": {
143 | "login": "Iasoon",
144 | "id": 6320308,
145 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
146 | "gravatar_id": "",
147 | "url": "https://api.github.com/users/Iasoon",
148 | "html_url": "https://github.com/Iasoon",
149 | "followers_url": "https://api.github.com/users/Iasoon/followers",
150 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
151 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
152 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
153 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
154 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
155 | "repos_url": "https://api.github.com/users/Iasoon/repos",
156 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
157 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
158 | "type": "User",
159 | "site_admin": false
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/spec/github_jsons/issue_close.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "closed",
3 | "issue": {
4 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1",
5 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/labels{/name}",
6 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/comments",
7 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/events",
8 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep/issues/1",
9 | "id": 64936749,
10 | "number": 1,
11 | "title": "Problemen!",
12 | "user": {
13 | "login": "Iasoon",
14 | "id": 6320308,
15 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
16 | "gravatar_id": "",
17 | "url": "https://api.github.com/users/Iasoon",
18 | "html_url": "https://github.com/Iasoon",
19 | "followers_url": "https://api.github.com/users/Iasoon/followers",
20 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
21 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
22 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
23 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
24 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
25 | "repos_url": "https://api.github.com/users/Iasoon/repos",
26 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
27 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
28 | "type": "User",
29 | "site_admin": false
30 | },
31 | "labels": [
32 |
33 | ],
34 | "state": "closed",
35 | "locked": false,
36 | "assignee": null,
37 | "milestone": null,
38 | "comments": 1,
39 | "created_at": "2015-03-28T12:28:07Z",
40 | "updated_at": "2015-03-28T13:03:14Z",
41 | "closed_at": "2015-03-28T13:03:14Z",
42 | "body": ""
43 | },
44 | "repository": {
45 | "id": 33015564,
46 | "name": "glowing-octo-dubstep",
47 | "full_name": "ZeusWPI/glowing-octo-dubstep",
48 | "owner": {
49 | "login": "ZeusWPI",
50 | "id": 331750,
51 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
52 | "gravatar_id": "",
53 | "url": "https://api.github.com/users/ZeusWPI",
54 | "html_url": "https://github.com/ZeusWPI",
55 | "followers_url": "https://api.github.com/users/ZeusWPI/followers",
56 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}",
57 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}",
58 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}",
59 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions",
60 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs",
61 | "repos_url": "https://api.github.com/users/ZeusWPI/repos",
62 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}",
63 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events",
64 | "type": "Organization",
65 | "site_admin": false
66 | },
67 | "private": true,
68 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
69 | "description": "Top8",
70 | "fork": false,
71 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep",
72 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks",
73 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}",
74 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}",
75 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams",
76 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks",
77 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}",
78 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events",
79 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}",
80 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}",
81 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags",
82 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}",
83 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}",
84 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}",
85 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}",
86 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}",
87 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages",
88 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers",
89 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors",
90 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers",
91 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription",
92 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}",
93 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}",
94 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}",
95 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}",
96 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}",
97 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}",
98 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges",
99 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}",
100 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads",
101 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}",
102 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}",
103 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}",
104 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}",
105 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}",
106 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}",
107 | "created_at": "2015-03-28T00:35:00Z",
108 | "updated_at": "2015-03-28T02:25:20Z",
109 | "pushed_at": "2015-03-28T02:33:02Z",
110 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git",
111 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git",
112 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git",
113 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep",
114 | "homepage": "",
115 | "size": 0,
116 | "stargazers_count": 0,
117 | "watchers_count": 0,
118 | "language": null,
119 | "has_issues": true,
120 | "has_downloads": true,
121 | "has_wiki": true,
122 | "has_pages": false,
123 | "forks_count": 0,
124 | "mirror_url": null,
125 | "open_issues_count": 0,
126 | "forks": 0,
127 | "open_issues": 0,
128 | "watchers": 0,
129 | "default_branch": "master"
130 | },
131 | "organization": {
132 | "login": "ZeusWPI",
133 | "id": 331750,
134 | "url": "https://api.github.com/orgs/ZeusWPI",
135 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos",
136 | "events_url": "https://api.github.com/orgs/ZeusWPI/events",
137 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}",
138 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}",
139 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3",
140 | "description": null
141 | },
142 | "sender": {
143 | "login": "Iasoon",
144 | "id": 6320308,
145 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3",
146 | "gravatar_id": "",
147 | "url": "https://api.github.com/users/Iasoon",
148 | "html_url": "https://github.com/Iasoon",
149 | "followers_url": "https://api.github.com/users/Iasoon/followers",
150 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}",
151 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}",
152 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}",
153 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions",
154 | "organizations_url": "https://api.github.com/users/Iasoon/orgs",
155 | "repos_url": "https://api.github.com/users/Iasoon/repos",
156 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}",
157 | "received_events_url": "https://api.github.com/users/Iasoon/received_events",
158 | "type": "User",
159 | "site_admin": false
160 | }
161 | }
162 |
--------------------------------------------------------------------------------