├── log
└── .keep
├── .rspec
├── app
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── concerns
│ │ └── .keep
│ ├── project.rb
│ ├── story.rb
│ ├── vote.rb
│ └── user.rb
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── favicon.ico
│ │ ├── pivotal-tracker.png
│ │ └── pivotal-tracker.svg
│ ├── javascripts
│ │ ├── sessions.js.coffee
│ │ ├── application.js
│ │ ├── dashboard.js.coffee
│ │ └── common.js.coffee
│ └── stylesheets
│ │ ├── unauthenticated.css.scss
│ │ └── authenticated.css.scss
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── sessions_controller.rb
│ ├── application_controller.rb
│ └── dashboard_controller.rb
├── helpers
│ ├── sessions_helper.rb
│ ├── application_helper.rb
│ └── dashboard_helper.rb
└── views
│ ├── sessions
│ ├── new.html.erb
│ └── _form.html.erb
│ ├── dashboard
│ ├── stories
│ │ ├── _estimate.html.erb
│ │ ├── _votes.html.erb
│ │ ├── _vote.html.erb
│ │ ├── _cards.html.erb
│ │ ├── _list.html.erb
│ │ ├── _project.html.erb
│ │ ├── _info.html.erb
│ │ └── _story.html.erb
│ ├── ajax
│ │ ├── reveal.js.erb
│ │ ├── select.js.erb
│ │ ├── vote.js.erb
│ │ ├── reset.js.erb
│ │ ├── project.js.erb
│ │ ├── detail.js.erb
│ │ └── update.js.erb
│ └── index.html.erb
│ ├── application
│ ├── _overlay.html.erb
│ ├── _notification.html.erb
│ ├── _unauthenticated.html.erb
│ ├── _authenticated.html.erb
│ └── _menu.html.erb
│ └── layouts
│ └── application.html.erb
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── .ruby-version
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── Procfile
├── spec
├── models
│ ├── project_spec.rb
│ ├── story_spec.rb
│ ├── vote_spec.rb
│ └── user_spec.rb
├── helpers
│ ├── sessions_helper_spec.rb
│ ├── application_helper_spec.rb
│ └── dashboard_helper_spec.rb
├── support
│ ├── helpers
│ │ ├── application_helper.rb
│ │ ├── dashboard_helper.rb
│ │ ├── views_helper.rb
│ │ └── sessions_helper.rb
│ └── fixtures
│ │ ├── basic_fixture.rb
│ │ ├── project_fixture.rb
│ │ └── story_fixture.rb
├── features
│ ├── sessions
│ │ ├── logout_spec.rb
│ │ ├── login_spec.rb
│ │ └── exceptions_spec.rb
│ └── dashboard
│ │ └── projects_spec.rb
├── factories.rb
├── spec_helper.rb
└── controllers
│ ├── sessions_controller_spec.rb
│ └── dashboard_controller_spec.rb
├── public
├── favicon.ico
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── config
├── initializers
│ ├── pivotal_tracker.rb
│ ├── cookies_serializer.rb
│ ├── session_store.rb
│ ├── filter_parameter_logging.rb
│ ├── mime_types.rb
│ ├── wrap_parameters.rb
│ ├── backtrace_silencers.rb
│ ├── assets.rb
│ └── inflections.rb
├── environment.rb
├── boot.rb
├── database.yml
├── secrets.yml
├── routes.rb
├── application.rb
├── locales
│ └── en.yml
├── environments
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
├── mongoid.yml
└── newrelic.yml
├── bin
├── rake
├── bundle
├── rails
├── rspec
├── autospec
└── setup
├── faye.ru
├── config.ru
├── Rakefile
├── db
└── seeds.rb
├── .gitignore
├── Gemfile
├── README.md
└── Gemfile.lock
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
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/assets/javascripts/sessions.js.coffee:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/project.rb:
--------------------------------------------------------------------------------
1 | class Project
2 | end
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bundle exec rails s -p $PORT
2 |
--------------------------------------------------------------------------------
/app/helpers/sessions_helper.rb:
--------------------------------------------------------------------------------
1 | module SessionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/models/project_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Project do
4 | end
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bimovidia/planning-poker/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/spec/helpers/sessions_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe SessionsHelper do
4 | end
--------------------------------------------------------------------------------
/config/initializers/pivotal_tracker.rb:
--------------------------------------------------------------------------------
1 | # Enable SSL support
2 | PivotalTracker::Client.use_ssl = true
3 |
--------------------------------------------------------------------------------
/app/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bimovidia/planning-poker/HEAD/app/assets/images/favicon.ico
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/app/assets/images/pivotal-tracker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bimovidia/planning-poker/HEAD/app/assets/images/pivotal-tracker.png
--------------------------------------------------------------------------------
/app/views/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pivotal Tracker
4 |
5 | <%= render 'form' %>
6 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/faye.ru:
--------------------------------------------------------------------------------
1 | require 'faye'
2 |
3 | Faye::WebSocket.load_adapter('thin')
4 |
5 | bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
6 | run bayeux
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_estimate.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= raw estimation(story) if show_estimation_icons?(story) %>
3 |
4 | <%= estimate(story) %>
--------------------------------------------------------------------------------
/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/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 = :json
--------------------------------------------------------------------------------
/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/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' # Set up gems listed in the Gemfile.
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_planning-poker-rails_session'
--------------------------------------------------------------------------------
/spec/support/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Helpers
3 | module ApplicationHelper
4 |
5 | def t(locale)
6 | I18n.translate(locale)
7 | end
8 |
9 | end
10 | end
11 | end
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
--------------------------------------------------------------------------------
/app/models/story.rb:
--------------------------------------------------------------------------------
1 | class Story
2 | class << self
3 |
4 | def update(params)
5 | story = PivotalTracker::Story.find(
6 | params[:story_id],
7 | params[:project_id]
8 | )
9 |
10 | story.update(params[:story])
11 | end
12 |
13 | end
14 | end
--------------------------------------------------------------------------------
/app/views/application/_overlay.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/views/application/_notification.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <% flash.each do |name, msg| %>
3 |
4 |
5 | <%= msg %>
6 |
7 |
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_votes.html.erb:
--------------------------------------------------------------------------------
1 | <% votes = Vote.where(story_id: story_id) %>
2 |
3 |
4 | <% if votes.present? %>
5 | <% votes.each do |vote| %>
6 | <%= render 'dashboard/stories/vote', story_id: story_id, vote: (reveal ? vote.vote : voting(vote)), user: vote.user, reveal: reveal %>
7 | <% end %>
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
--------------------------------------------------------------------------------
/spec/support/helpers/dashboard_helper.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Helpers
3 | module DashboardHelper
4 |
5 | def encoded_user
6 | Base64.strict_encode64(
7 | Forgery(:internet).email_address
8 | )
9 | end
10 |
11 | def decoded_user(user)
12 | Base64.strict_decode64(user)
13 | end
14 |
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/spec/support/helpers/views_helper.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Helpers
3 | module ViewsHelper
4 |
5 | def project_nodes
6 | page.all('#menu li')
7 | end
8 |
9 | def story_nodes
10 | page.all('#stories .story')
11 | end
12 |
13 | def story_node(id)
14 | find("#story-#{id}")
15 | end
16 |
17 | end
18 | end
19 | end
--------------------------------------------------------------------------------
/spec/features/sessions/logout_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Sessions::Logout' do
4 | before { skip_authentication(DashboardController) }
5 |
6 | context 'success' do
7 | before do
8 | stub_projects
9 | visit root_path
10 | logout
11 | end
12 |
13 | specify { page.should have_content t('flashes.sessions.destroy') }
14 | end
15 |
16 | end
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/reveal.js.erb:
--------------------------------------------------------------------------------
1 | <% broadcast '/planning-poker/story/reveal-votes' do %>
2 | $('#cards-<%= @resource[:story_id] %>').html(
3 | "<%= j render('dashboard/stories/votes', story_id: @resource[:story_id], reveal: true) %>"
4 | );
5 |
6 | $('.btn-votes a').removeClass('active');
7 | $('#form-<%= @resource[:story_id] %>').find('input#estimate').val('');
8 |
9 | window.selectCard();
10 | <% end %>
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/select.js.erb:
--------------------------------------------------------------------------------
1 | <% broadcast '/planning-poker/story/select-vote' do %>
2 |
3 | var vote = $('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= @resource[:username] %>"] span');
4 |
5 | $('.card-revealed').removeClass('card-selected')
6 |
7 | vote.addClass('card-selected').closest('form').find('input#estimate').prop(
8 | 'value',
9 | vote.find('.vote-value').text().trim()
10 | )
11 |
12 | <% end %>
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'rspec' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('rspec-core', 'rspec')
17 |
--------------------------------------------------------------------------------
/app/views/dashboard/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= t('story').pluralize %>
4 |
5 |
6 | <%= render 'dashboard/stories/list', project: @projects.first unless @projects.blank? %>
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/views/sessions/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_tag login_path, as: :session do %>
2 |
3 |
4 | <%= text_field_tag :username, nil, class: 'form-control', placeholder: 'Email / Username' %>
5 |
6 |
7 |
8 | <%= password_field_tag :password, nil, class: 'form-control', placeholder: 'Password' %>
9 |
10 |
11 | <%= submit_tag t('signin').upcase, class: 'btn btn-success' %>
12 | <% end %>
--------------------------------------------------------------------------------
/bin/autospec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'autospec' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('rspec-core', 'autospec')
17 |
--------------------------------------------------------------------------------
/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 wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/factories.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 |
3 | factory :user do
4 | username { Forgery::Internet.email_address }
5 | password { Forgery::Basic.encrypt }
6 | salt { Forgery::Basic.encrypt }
7 | token { Forgery::Basic.encrypt }
8 | end
9 |
10 | factory :vote do
11 | user Forgery::Internet.email_address
12 | story_id Forgery::Basic.number(at_least: 1, at_most: 20)
13 | vote Forgery::Basic.number(at_least: 1, at_most: 5)
14 | end
15 |
16 | end
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_vote.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <% if vote %>
5 | <%= vote %>
6 | <% else %>
7 | <% if vote.nil? %>
8 |
9 | <% else %>
10 | ?
11 | <% end %>
12 | <% end %>
13 |
14 |
15 | <%= nickname user %>
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile = %w( search.js )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | !/log/.keep
17 | /tmp
18 |
19 | .DS_Store
20 |
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/vote.js.erb:
--------------------------------------------------------------------------------
1 | <% broadcast '/planning-poker/story/add-vote' do %>
2 |
3 | if ($('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= @resource[:user] %>"]').length) {
4 | $('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= @resource[:user] %>"]').remove();
5 | }
6 |
7 | $('#cards-<%= @resource[:story_id] %> ul').append(
8 | "<%= j render('dashboard/stories/vote', story_id: @resource[:story_id],
9 | vote: false, user: @resource[:user], reveal: false) %>"
10 | );
11 |
12 | $('#vote-<%= @resource[:story_id] %> .reveal').removeAttr('disabled');
13 |
14 | <% end %>
--------------------------------------------------------------------------------
/app/views/application/_unauthenticated.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 | <%= yield %>
19 |
20 |
21 |
--------------------------------------------------------------------------------
/spec/support/fixtures/basic_fixture.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Fixtures
3 | module BasicFixture
4 |
5 | def stub_id
6 | rand(1000000)
7 | end
8 |
9 | def stub_text
10 | Forgery::Basic.text
11 | end
12 |
13 | def stub_words(n = 5)
14 | Forgery::LoremIpsum.words(n)
15 | end
16 |
17 | def stub_name
18 | Forgery::Name.full_name
19 | end
20 |
21 | def init_object(attrs = {})
22 | OpenStruct.new(attrs)
23 | end
24 |
25 | def stub_user(user = nil)
26 | User.stubs(:authenticate).returns(user)
27 | end
28 |
29 | end
30 | end
31 | end
--------------------------------------------------------------------------------
/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 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
--------------------------------------------------------------------------------
/spec/helpers/application_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ApplicationHelper do
4 |
5 | context '#layout_class' do
6 | it 'should return authenticated' do
7 | helper.stubs(:user_signed_in?).returns(true)
8 | helper.layout_class.should eq 'authenticated'
9 | end
10 |
11 | it 'should return unauthenticated' do
12 | helper.stubs(:user_signed_in?).returns(false)
13 | helper.layout_class.should eq 'unauthenticated'
14 | end
15 | end
16 |
17 | context '#comma_separated' do
18 | it 'should return comma separated words' do
19 | helper.comma_separated((1..5).to_a).should eq '1, 2, 3, 4, 5'
20 | end
21 | end
22 | end
--------------------------------------------------------------------------------
/spec/features/sessions/login_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Sessions::Login' do
4 | let(:user) { FactoryGirl.create(:user) }
5 |
6 | context 'success' do
7 | before do
8 | stub_user(user)
9 | stub_projects
10 | login
11 | end
12 |
13 | specify { page.should have_content t('flashes.sessions.success') }
14 | end
15 |
16 | context 'failure' do
17 | before do
18 | stub_user
19 | stub_projects
20 | end
21 |
22 | context 'login' do
23 | before { login }
24 |
25 | specify { current_path.should eq login_path }
26 | specify { page.should have_content t('flashes.sessions.failed') }
27 | end
28 | end
29 |
30 | end
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/reset.js.erb:
--------------------------------------------------------------------------------
1 | <% broadcast '/planning-poker/story/reset-vote' do %>
2 |
3 | if ($('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= current_user['username'] %>"]').length) {
4 | $('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= current_user['username'] %>"]').remove();
5 | }
6 |
7 | $('#cards-<%= @resource[:story_id] %> ul').append(
8 | "<%= j render('dashboard/stories/vote', story_id: @resource[:story_id],
9 | vote: nil, user: @resource[:user], reveal: false) %>"
10 | );
11 |
12 | $('#form-<%= @resource[:story_id] %>').find('input#estimate').val('');
13 |
14 | $('#vote-<%= @resource[:story_id] %> .reveal').attr('disabled','disabled');
15 |
16 | <% end %>
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_cards.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= render 'dashboard/stories/votes', story_id: story.id, reveal: false %>
3 |
4 |
5 |
6 | <% project.point_scale.split(',').each do |point| %>
7 | <%= link_to point, vote_path(story.id, point, encoded_user), class: 'btn btn-default', remote: true %>
8 | <% end %>
9 |
10 | <%= link_to t('reset'), reset_path(story.id, encoded_user), class: 'btn btn-warning action-vote', remote: true %>
11 | <%= link_to t('reveal'), reveal_path(story.id), class: 'btn btn-info pull-right action-vote reveal', remote: true, disabled: !current_user_has_voted?(story) %>
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/spec/support/fixtures/project_fixture.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Fixtures
3 | module ProjectFixture
4 |
5 | def project_params
6 | {
7 | id: stub_id,
8 | name: stub_text,
9 | velocity_scheme: stub_text,
10 | labels: stub_labels,
11 | point_scale: stub_point_scale
12 | }
13 | end
14 |
15 | def stub_projects(projects = [])
16 | PivotalTracker::Project.stubs(:all).returns(projects)
17 | end
18 |
19 | def stub_labels(n = 5)
20 | (1..n).map { |index| "label#{index}" }.join(',')
21 | end
22 |
23 | def stub_point_scale(n = 5)
24 | (1..n).to_a.join(',')
25 | end
26 |
27 | end
28 | end
29 | end
--------------------------------------------------------------------------------
/app/controllers/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class SessionsController < ApplicationController
2 |
3 | def new
4 | redirect_to :root if user_signed_in?
5 | end
6 |
7 | def create
8 | user = User.authenticate(session_params)
9 |
10 | if user
11 | session[:user] = {
12 | username: user.username,
13 | token: user.token
14 | }
15 |
16 | redirect_to :root, notice: t('flashes.sessions.success')
17 | else
18 | redirect_to :login, alert: t('flashes.sessions.failed')
19 | end
20 | end
21 |
22 | def destroy
23 | reset_session
24 | redirect_to :login, notice: t('flashes.sessions.destroy')
25 | end
26 |
27 | private
28 |
29 | def session_params
30 | params.permit(
31 | :username, :password
32 | )
33 | end
34 | end
--------------------------------------------------------------------------------
/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_ujs
15 | //= require bootstrap
16 | //= require turbolinks
17 | //= require backtop
18 | //= require_tree .
19 |
--------------------------------------------------------------------------------
/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: your-secret-token
15 |
16 | test:
17 | secret_key_base: your-secret-token
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
--------------------------------------------------------------------------------
/spec/support/fixtures/story_fixture.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Fixtures
3 | module StoryFixture
4 |
5 | def story_params
6 | {
7 | id: stub_id,
8 | name: stub_text,
9 | current_state: stub_state,
10 | description: stub_words,
11 | story_type: stub_text,
12 | requested_by: stub_name,
13 | owned_by: stub_name
14 | }
15 | end
16 |
17 | def stub_stories(project, n = 10)
18 | project.stubs(:stories).returns(
19 | stub(
20 | all: (1..n).map { |i| init_object(story_params) }
21 | )
22 | )
23 | end
24 |
25 | def stub_state
26 | ['accepted', 'delivered' , 'started', 'rejected'].sample
27 | end
28 |
29 | end
30 | end
31 | end
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: 'dashboard#index'
3 |
4 | get 'favicon.ico', controller: :application, action: :favicon
5 |
6 | controller :sessions do
7 | get :login, action: :new
8 | post :login, action: :create
9 | delete :logout, action: :destroy
10 | end
11 |
12 | resources :dashboard, only: [:index]
13 |
14 | controller :dashboard do
15 | get 'project/:id', action: :project, as: :project
16 | get 'story/:story_id/:vote/:user', action: :vote, as: :vote
17 | get 'reset/:story_id/:user', action: :reset, as: :reset
18 | get 'detail/:story_id/:toggle/:user', action: :detail, as: :detail
19 | get 'reveal/:story_id', action: :reveal, as: :reveal
20 | post 'select-vote', action: :select, as: :select
21 | post 'update-story', action: :update, as: :update
22 | end
23 |
24 | end
--------------------------------------------------------------------------------
/app/models/vote.rb:
--------------------------------------------------------------------------------
1 | class Vote
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | field :story_id, type: Integer
6 | field :vote, type: Integer
7 | field :user, type: String
8 |
9 | class << self
10 |
11 | def set(params)
12 | vote = find_by(
13 | user: params[:user],
14 | story_id: params[:story_id]
15 | )
16 |
17 | if vote
18 | vote.update(vote: params[:vote])
19 | else
20 | create(
21 | user: params[:user],
22 | story_id: params[:story_id],
23 | vote: params[:vote]
24 | )
25 | end
26 | end
27 |
28 | def reset(params)
29 | vote = find_by(
30 | user: params[:user],
31 | story_id: params[:story_id]
32 | )
33 |
34 | vote.delete if vote
35 | end
36 |
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/assets/javascripts/dashboard.js.coffee:
--------------------------------------------------------------------------------
1 | filterStories = ->
2 | $('#sub-menu li').on 'click', ->
3 | $(this).addClass('selected').siblings().removeClass('selected')
4 | filterState(this)
5 |
6 | filterState = (selector) ->
7 | switch $(selector).prop('id')
8 | when 'filter-unestimated'
9 | $('.story').not('.story-unestimated').hide()
10 | $('.story-unestimated').show()
11 | else $('.story').show()
12 |
13 | selectCard = ->
14 | $('.card-revealed').on 'click', ->
15 | vote = $(this).parent().data('vote').split('-')
16 |
17 | $.ajax
18 | type: 'POST'
19 | url: '/select-vote'
20 | data: { story_id: vote[0], username: vote[1] }
21 | dataType: 'script'
22 |
23 | window.filterState = filterState
24 | window.selectCard = selectCard
25 |
26 | $(document).ready(filterStories)
27 | $(document).ready(selectCard)
--------------------------------------------------------------------------------
/spec/models/story_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Story do
4 |
5 | context '.class' do
6 |
7 | context '#update' do
8 | let(:story) { OpenStruct.new }
9 |
10 | let(:params) {{
11 | story: 'story',
12 | story_id: 123,
13 | project_id: 312
14 | }}
15 |
16 | before do
17 | PivotalTracker::Story.stubs(:find).returns(story)
18 | story.stubs(:update)
19 | end
20 |
21 | it 'should call find on PivotalTracker::Story' do
22 | PivotalTracker::Story.expects(:find).with(
23 | params[:story_id], params[:project_id]
24 | ).returns(story)
25 |
26 | Story.update(params)
27 | end
28 |
29 | it 'should call update on story' do
30 | story.expects(:update).with(params[:story])
31 | Story.update(params)
32 | end
33 | end
34 |
35 | end
36 | end
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
--------------------------------------------------------------------------------
/app/assets/stylesheets/unauthenticated.css.scss:
--------------------------------------------------------------------------------
1 | #unauthenticated {
2 | background-color: #3E934C;
3 | color: #888;
4 |
5 | #header {
6 | background: #FFF;
7 | padding: 5px 40px;
8 |
9 | h1 {
10 | font-family: 'Rokkitt', serif;
11 | margin: 0;
12 | padding: 0;
13 | font-size: 36px;
14 | color: #3E934C;
15 |
16 | span {
17 | color: #C72543;
18 |
19 | &.logo {
20 | font-size: 20px;
21 | }
22 | }
23 | }
24 | }
25 |
26 | #signin-box {
27 | background-color: #E9E9E9;
28 | padding: 25px 30px;
29 | margin-top: 10%;
30 |
31 | h1 {
32 | background-image: asset-url('pivotal-tracker.svg');
33 | background-position: 0 0;
34 | background-repeat: no-repeat;
35 | display: block;
36 | width: 280px;
37 | height: 45px;
38 | margin: 20px 0;
39 | text-indent: -999em;
40 | overflow: hidden;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/project.js.erb:
--------------------------------------------------------------------------------
1 | $('#stories').html("<%= j render('dashboard/stories/list', project: @project) %>");
2 | $('#sidebar').html("<%= j render('dashboard/stories/project', project: @project) %>");
3 |
4 | $('#project-<%= @project.id %>').addClass('active');
5 | $('.projects-list').not('#project-<%= @project.id %>').removeClass('active');
6 |
7 | if ($('#menu .active').parents('ul').prop('id') != 'menu') {
8 | $('#more-projects').addClass('active');
9 | $('#more-projects .dropdown-toggle').html('<%= @project.name %> ');
10 | } else {
11 | $('#more-projects').removeClass('active');
12 | $('#more-projects .dropdown-toggle').html('<%= t("projects.more") %> ');
13 | }
14 |
15 | $('#menu-compact').html('<%= @project.name %> ');
16 | $('.project-dropdown').removeClass('open')
17 |
18 | window.hideOverlay();
19 | window.activeVote();
20 | window.filterState('#sub-menu li.selected');
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/detail.js.erb:
--------------------------------------------------------------------------------
1 | <% if @resource[:toggle] == 'expand' %>
2 | $('#expand-<%= @resource[:story_id] %>').closest('.story-overview').hide().siblings().show();
3 | <% else %>
4 | $('#collapse-<%= @resource[:story_id] %>').closest('.story-detail').hide().siblings().show();
5 | <% end %>
6 |
7 | <% broadcast '/planning-poker/story/toggle-voters' do %>
8 | <% if @resource[:toggle] == 'expand' %>
9 | if (!$('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= @resource[:user] %>"]').length) {
10 | $('#cards-<%= @resource[:story_id] %> ul').append(
11 | "<%= j render('dashboard/stories/vote', story_id: @resource[:story_id],
12 | vote: nil, user: @resource[:user], reveal: false) %>"
13 | );
14 | }
15 | <% else %>
16 | if ($('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= current_user['username'] %>"] .card-blank').length) {
17 | $('.user-vote[data-vote="<%= @resource[:story_id] %>-<%= current_user['username'] %>"]').remove();
18 | }
19 | <% end %>
20 | <% end %>
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_list.html.erb:
--------------------------------------------------------------------------------
1 | <% project.stories.all.each do |story| %>
2 |
3 |
4 |
5 | <%= link_to detail_path(story.id, 'expand', encoded_user), id: "expand-#{story.id}", remote: true do %>
6 |
7 | <% end %>
8 |
9 |
10 | <%= raw estimation(story) %>
11 |
12 |
13 |
14 | <%= story.name %>
15 |
16 | <%= story.current_state %>
17 |
18 |
19 |
20 |
21 | <%= render 'dashboard/stories/story', project: project, story: story %>
22 |
23 | <% end %>
--------------------------------------------------------------------------------
/spec/features/sessions/exceptions_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Sessions::Exceptions' do
4 | let(:user) { FactoryGirl.create(:user) }
5 |
6 | context 'unauthenticated' do
7 | before { visit root_path }
8 |
9 | specify { current_path.should eq login_path }
10 | specify { page.should have_content t('flashes.sessions.expired') }
11 | end
12 |
13 | context 'no token' do
14 | before do
15 | PivotalTracker::Client.stubs(:token).raises(
16 | PivotalTracker::Client::NoToken
17 | )
18 |
19 | login
20 | end
21 |
22 | specify { page.should have_content t('flashes.sessions.token') }
23 | end
24 |
25 | context 'rest client unauthorized' do
26 | before do
27 | skip_authentication(DashboardController)
28 |
29 | PivotalTracker::Project.stubs(:all).raises(
30 | RestClient::Unauthorized
31 | )
32 |
33 | visit root_path
34 | end
35 |
36 | specify { current_path.should eq login_path }
37 | specify { page.should have_content t('flashes.sessions.unauthorized') }
38 | end
39 |
40 | end
--------------------------------------------------------------------------------
/spec/features/dashboard/projects_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Dashboard::Projects' do
4 | before { skip_authentication(DashboardController) }
5 |
6 | context 'empty' do
7 | before do
8 | stub_projects
9 | visit root_path
10 | end
11 |
12 | specify { page.should have_content t('dashboard.empty.title') }
13 | specify { page.should have_content t('dashboard.empty.subtitle') }
14 | end
15 |
16 | context 'projects' do
17 | let(:projects) { (1..5).map { |i| init_object(project_params) }}
18 | let(:project) { projects.first }
19 |
20 | before do
21 | stub_projects(projects)
22 | stub_stories(project)
23 | visit root_path
24 | end
25 |
26 | specify { project_nodes.size.should eq 5}
27 | specify { story_nodes.size.should eq 10}
28 | specify { page.should have_content project.name }
29 | specify { page.should have_content project.velocity_scheme }
30 | specify { page.should have_content project.labels.split(',').join(', ') }
31 | specify { page.should have_content project.point_scale.split(',').join(', ') }
32 | end
33 |
34 | end
--------------------------------------------------------------------------------
/app/views/dashboard/ajax/update.js.erb:
--------------------------------------------------------------------------------
1 | <% broadcast '/planning-poker/story/update-story' do %>
2 |
3 | window.flashNow(
4 | 'notice',
5 | "<%= t('flashes.stories.update', id: @resource.id) %>"
6 | );
7 |
8 | $('#story-<%= @resource.id %> .story-detail').addClass('updated');
9 |
10 | setTimeout(function(){
11 | $('#story-<%= @resource.id %> .story-detail').removeClass('updated');
12 | }, 5000);
13 |
14 | $('#story-<%= @resource.id %> textarea#name').html(
15 | '<%= j @resource.name %>'
16 | );
17 |
18 | $('#story-<%= @resource.id %> textarea#description').html(
19 | '<%= j @resource.description %>'
20 | );
21 |
22 | <% if @resource.estimate.to_i >= 0 %>
23 |
24 | $('#estimate-<%= @resource.id %>').html(
25 | "<%= j render('dashboard/stories/estimate', story: @resource) %>"
26 | );
27 |
28 | $('#votes-<%= @resource.id %>').remove();
29 | $('#form-<%= @resource.id %> input#estimate').remove();
30 | $('#form-<%= @resource.id %> .story-actions').remove();
31 |
32 | $('#story-<%= @resource.id %> textarea#name, #story-<%= @resource.id %> textarea#description')
33 | .prop('disabled', 'disabled');
34 |
35 | <% end %>
36 |
37 | <% end %>
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 |
6 | def favicon
7 | redirect_to '/assets/favicon.ico'
8 | end
9 |
10 | # Meant to be used as before_action in any controllers that are not publicly accessible
11 | def require_authentication
12 | redirect_to login_path, alert: t('flashes.sessions.expired') unless user_signed_in?
13 | end
14 |
15 | def reset_token
16 | PivotalTracker::Client.token = current_user['token'] if user_signed_in?
17 | end
18 |
19 | helper_method :user_signed_in?, :current_user
20 |
21 | def current_user
22 | @current_user ||= session[:user]
23 | end
24 |
25 | def user_signed_in?
26 | !!session[:user]
27 | end
28 |
29 | def rescue_steps(message)
30 | reset_session
31 | redirect_to login_path, alert: message
32 | end
33 |
34 | rescue_from PivotalTracker::Client::NoToken do |exception|
35 | rescue_steps t('flashes.sessions.token')
36 | end
37 |
38 | rescue_from RestClient::Unauthorized do |exception|
39 | rescue_steps t('flashes.sessions.unauthorized')
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Pivotal Tracker: Planning Poker
8 |
9 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
10 |
11 |
12 |
16 |
17 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
18 | <%= javascript_include_tag publisher_client_path %>
19 |
20 | <%= favicon_link_tag %>
21 |
22 | <%= csrf_meta_tags %>
23 |
24 |
25 |
26 | <%= render 'notification' %>
27 | <%= render layout_class %>
28 |
29 |
34 |
35 |
--------------------------------------------------------------------------------
/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 PlanningPokerRails
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
17 | # config.time_zone = 'Central Time (US & Canada)'
18 |
19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
21 | # config.i18n.default_locale = :de
22 | config.i18n.enforce_available_locales = true
23 |
24 | # Do not swallow errors in after_commit/after_rollback callbacks.
25 | config.active_record.raise_in_transactional_callbacks = true
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/support/helpers/sessions_helper.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module Helpers
3 | module SessionsHelper
4 |
5 | # Allows to get authenticated user without going through full authentication process.
6 | # To use this method you need to pass a class of the controller, example:
7 | #
8 | # skip_authentication(DashboardController)
9 | #
10 | def skip_authentication(controller)
11 | controller.any_instance.stubs(:require_authentication).returns(false)
12 | controller.any_instance.stubs(:current_user).returns(user)
13 |
14 | page.set_rack_session(
15 | user: {
16 | username: user.username,
17 | token: user.token
18 | }
19 | )
20 | end
21 |
22 | # Login steps to authenticate user.
23 | # This method will stub a user and logs the user in to the system
24 | #
25 | def login
26 | visit login_path
27 |
28 | fill_in 'username', with: Forgery(:internet).email_address
29 | fill_in 'password', with: Forgery(:basic).password
30 |
31 | click_button t('signin').upcase
32 | end
33 |
34 | def logout
35 | click_link t('signout')
36 | end
37 |
38 | private
39 |
40 | def user
41 | @user ||= FactoryGirl.create(:user)
42 | end
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 |
3 | def layout_class
4 | user_signed_in? ? 'authenticated' : 'unauthenticated'
5 | end
6 |
7 | def publisher_client_path
8 | "//#{publisher_domain}/faye/client.js" unless Rails.env.test?
9 | end
10 |
11 | def publisher_environment
12 | ['production', 'test'].include?(Rails.env) ? Rails.env : 'development'
13 | end
14 |
15 | def publisher_domain
16 | Rails.configuration.publisher[:domain] unless Rails.env.test?
17 | end
18 |
19 | def publisher_token
20 | unless Rails.env.test?
21 | digest = OpenSSL::Digest::Digest.new('sha256')
22 | secret = Rails.configuration.publisher[:secret]
23 | data = "/#{publisher_environment}/planning-poker/subscribe"
24 |
25 | OpenSSL::HMAC.hexdigest(digest, secret, data)
26 | end
27 | end
28 |
29 | def broadcast(channel, &block)
30 | unless Rails.env.test?
31 | message = { channel: channel, data: capture(&block), ext: { token: publisher_token }}
32 | uri = URI.parse("http://#{publisher_domain}/faye")
33 |
34 | Net::HTTP.post_form(uri, message: message.to_json)
35 | end
36 | end
37 |
38 | def encoded_user
39 | Base64.strict_encode64(current_user['username'])
40 | end
41 |
42 | def comma_separated(array)
43 | array.split(',').join(', ')
44 | end
45 |
46 | end
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '4.2.5.1'
4 | gem 'sqlite3'
5 | gem 'thin'
6 | gem 'faye'
7 |
8 | # Heroku and New Relic
9 | gem 'rails_12factor'
10 | gem 'newrelic_rpm'
11 |
12 | # Assets
13 | gem 'jquery-rails'
14 | gem 'turbolinks'
15 | gem 'sass-rails', '~> 5.0'
16 | gem 'coffee-rails', '~> 4.1.0'
17 | gem 'uglifier', '>= 1.0.3'
18 | gem 'bootstrap-sass'
19 | gem 'therubyracer', require: 'v8'
20 | gem 'yui-compressor'
21 | gem 'font-awesome-rails'
22 | gem 'responders', '~> 2.0'
23 |
24 | # Custom gems
25 | gem 'pivotal-tracker'
26 | gem 'backtop'
27 | gem 'mongoid', github: 'mongoid'
28 | gem 'bson_ext'
29 |
30 | gem 'rspec-rails', group: [:test, :development]
31 |
32 | group :test do
33 | gem 'factory_girl_rails'
34 | gem 'forgery'
35 | gem 'capybara'
36 | gem 'selenium-webdriver'
37 | gem 'database_cleaner'
38 | gem 'rack_session_access'
39 | gem 'mocha', require: false
40 | gem 'zeus', require: false
41 | end
42 |
43 | group :development do
44 | gem 'spring'
45 | gem 'better_errors'
46 | gem 'binding_of_caller'
47 | gem 'capistrano', '~> 3.0', require: false
48 | gem 'capistrano-rails', '~> 1.1', require: false
49 | gem 'capistrano-bundler', '~> 1.1', require: false
50 | gem 'capistrano-rvm', '~> 0.1', require: false
51 | gem 'codeclimate-test-reporter', require: false
52 | end
53 |
54 | gem 'jbuilder', '~> 2.0'
55 | # bundle exec rake doc:rails generates the API under doc/api.
56 | gem 'sdoc', '~> 0.4.0', group: :doc
57 |
--------------------------------------------------------------------------------
/app/views/application/_authenticated.html.erb:
--------------------------------------------------------------------------------
1 | <%= render 'overlay' %>
2 |
3 |
4 |
21 |
22 | <% if @projects.blank? %>
23 |
24 |
25 |
26 |
27 |
28 | <%= t('dashboard.empty.title') %>
29 | <%= t('dashboard.empty.subtitle') %>
30 |
31 |
32 |
33 |
34 | <% else %>
35 |
36 |
46 |
47 |
48 | <%= yield %>
49 |
50 |
51 | <% end %>
52 |
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_project.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/models/vote_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Vote do
4 | let(:vote) { FactoryGirl.create(:vote) }
5 |
6 | describe '.class' do
7 |
8 | context '#set' do
9 | let(:params) {{
10 | user: vote.user,
11 | story_id: vote.story_id,
12 | vote: Forgery(:basic).number(at_least: 1, at_most: 5)
13 | }}
14 |
15 | after { Vote.set(params) }
16 |
17 | it 'should call find_by on Vote' do
18 | Vote.expects(:find_by).with(
19 | user: params[:user],
20 | story_id: params[:story_id]
21 | )
22 | end
23 |
24 | it 'should call update on vote instance' do
25 | Vote.any_instance.expects(:update).with(
26 | vote: params[:vote]
27 | )
28 | end
29 |
30 | it 'should create vote' do
31 | vote.destroy
32 | Vote.expects(:create).with(
33 | user: params[:user],
34 | story_id: params[:story_id],
35 | vote: params[:vote]
36 | )
37 | end
38 | end
39 |
40 | context '#reset' do
41 | let(:params) {{
42 | user: vote.user,
43 | story_id: vote.story_id,
44 | }}
45 |
46 | after { Vote.reset(params) }
47 |
48 | it 'should call find_by on Vote' do
49 | Vote.expects(:find_by).with(
50 | user: params[:user],
51 | story_id: params[:story_id]
52 | )
53 | end
54 |
55 | it 'should call delete on vote instance' do
56 | Vote.any_instance.expects(:delete)
57 | end
58 | end
59 |
60 | end
61 | end
--------------------------------------------------------------------------------
/app/views/application/_menu.html.erb:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | all: "All"
3 | cancel: "Cancel"
4 | editable: "Editable"
5 | na: "N/A"
6 | owner: "Owner"
7 | planning: "Planning"
8 | poker: "Poker"
9 | reset: "Reset"
10 | requester: "Requester"
11 | reveal: "Reveal"
12 | save: "Save"
13 | signin: "Sign In"
14 | signout: "Sign Out"
15 | story: "Story"
16 | unestimated: "Unestimated"
17 |
18 | dashboard:
19 | empty:
20 | title: "You do not own or have any projects assigned."
21 | subtitle: "Please create a project in Pivotal Tracker first!"
22 |
23 | projects:
24 | id: "Project ID"
25 | info: "Project Info"
26 | labels: "Project Labels"
27 | more: "More Project(s)"
28 | name: "Project Name"
29 | scheme: "Project Scheme"
30 | scale: "Project Point Scale"
31 |
32 | stories:
33 | id: "Story ID"
34 | type: "Story Type"
35 | points: "Story Points"
36 | state: "Story State"
37 |
38 | # Flashes
39 | flashes:
40 | sessions:
41 | destroy: "You have successfully logged out"
42 | expired: "Your session has expired"
43 | failed: "Invalid username or password"
44 | token: "Your API Key / Token is not yet set up. Please set one up in Pivotal Tracker!"
45 | signin: "You will need to sign in first"
46 | success: "You have successfully logged in"
47 | unauthorized: "No API Key / Token found. Please re-login!"
48 |
49 | stories:
50 | update: "Story ID#%{id} has been updated successfully"
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 | div>
61 |
We're sorry, but something went wrong.
62 |
63 | If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_info.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= t('stories.id').upcase %>
6 |
7 |
8 | <%= story.id %>
9 |
10 |
11 |
12 |
13 |
14 |
15 | <%= t('stories.type').upcase %>
16 |
17 |
18 |
19 | <%= story.story_type.capitalize %>
20 |
21 |
22 |
23 |
24 |
25 |
26 | <%= t('stories.points').upcase %>
27 |
28 |
29 | <%= render 'dashboard/stories/estimate', story: story %>
30 |
31 |
32 |
33 |
34 |
35 |
36 | <%= t('stories.state').upcase %>
37 |
38 |
39 | <%= story.current_state.capitalize %>
40 |
41 |
42 |
43 |
44 |
45 |
46 | <%= t('requester').upcase %>
47 |
48 |
49 | <%= story.requested_by %>
50 |
51 |
52 |
53 |
54 |
55 |
56 | <%= t('owner').upcase %>
57 |
58 |
59 | <%= story.owned_by %>
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Debug mode disables concatenation and preprocessing of assets.
23 | # This option may cause significant delays in view rendering with a large
24 | # number of complex assets.
25 | config.assets.debug = true
26 |
27 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
28 | # yet still be able to expire them through the digest params.
29 | config.assets.digest = true
30 |
31 | # Adds additional error checking when serving assets at runtime.
32 | # Checks for improperly declared sprockets dependencies.
33 | # Raises helpful error messages.
34 | config.assets.raise_runtime_errors = true
35 |
36 | # Raises error for missing translations
37 | # config.action_view.raise_on_missing_translations = true
38 |
39 | # Publisher for push notification
40 | config.publisher = {
41 | # development faye server
42 | domain: 'localhost:9292',
43 | # secret key
44 | secret: 'secret'
45 | }
46 | end
47 |
--------------------------------------------------------------------------------
/app/views/dashboard/stories/_story.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= form_tag update_path, id: "form-#{story.id}", class: 'clearfix', remote: true do %>
4 |
5 |
6 | <%= link_to detail_path(story.id, 'collapse', encoded_user), id: "collapse-#{story.id}", remote: true do %>
7 |
8 | <% end %>
9 |
10 |
11 | <%= text_area_tag 'name', story.name, name: 'story[name]', class: 'form-control', rows: 2, disabled: state_accepted?(story) %>
12 | <%= hidden_field_tag 'story_id', story.id, name: 'story_id' %>
13 | <%= hidden_field_tag 'project_id', project.id, name: 'project_id' %>
14 |
15 |
16 |
17 |
18 |
19 | <%= render 'dashboard/stories/info', story: story %>
20 |
21 |
22 |
23 |
24 | <%= text_area_tag 'description', story.description, name: 'story[description]', class: 'form-control', rows: 10, disabled: state_accepted?(story) %>
25 |
26 |
27 |
28 |
29 | <% unless unestimateable?(story) %>
30 |
31 | <%= render 'dashboard/stories/cards', project: project, story: story, reveal: false %>
32 |
33 |
34 | <%= hidden_field_tag 'estimate', nil, name: 'story[estimate]' %>
35 |
36 |
37 | <%= submit_tag t('save'), class: 'btn btn-success' %>
38 | <%= link_to t('cancel'), detail_path(story.id, 'collapse', encoded_user), remote: true, class: 'btn btn-danger' %>
39 |
40 | <% end %>
41 |
42 | <% end %>
43 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe User do
4 | let(:username) { Forgery(:internet).email_address }
5 | let(:password) { 'password' }
6 |
7 | let(:params) {{
8 | username: username,
9 | password: password
10 | }}
11 |
12 | let(:user) { FactoryGirl.create(:user) }
13 |
14 | describe '.class' do
15 |
16 | context '#authenticate' do
17 | before { User.stubs(:create).returns(user) }
18 |
19 | it 'should return nil' do
20 | User.authenticate({}).should be_nil
21 | end
22 |
23 | it 'should call find_by on User' do
24 | User.expects(:find_by).with(
25 | username: params[:username]
26 | )
27 |
28 | User.authenticate(params)
29 | end
30 |
31 | it 'should return user' do
32 | User.authenticate(params).should eq user
33 | end
34 |
35 | it 'should create user' do
36 | user.delete
37 | User.expects(:create).with(params)
38 | User.authenticate(params)
39 | end
40 | end
41 |
42 | context '#create' do
43 | before do
44 | PivotalTracker::Client.stubs(:token).returns(user.token)
45 | User.stubs(:salted).returns(user.salt)
46 | end
47 |
48 | after { User.create(params) }
49 |
50 | it 'should call token on PivotalTracker::Client' do
51 | PivotalTracker::Client.expects(:token).with(
52 | params[:username],
53 | params[:password]
54 | ).returns(user.token)
55 | end
56 |
57 | it 'should call salted' do
58 | User.expects(:salted).with(params[:username])
59 | end
60 |
61 | it 'should call new' do
62 | User.expects(:new).with(params.merge(token: user.token)).returns(user)
63 | end
64 |
65 | it 'should call save' do
66 | User.any_instance.expects(:save)
67 | end
68 | end
69 | end
70 | end
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Access to rack session
41 | config.middleware.use RackSessionAccess::Middleware
42 |
43 | # Raises error for missing translations
44 | # config.action_view.raise_on_missing_translations = true
45 | end
46 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | prefork = -> {
2 | ENV["RAILS_ENV"] = 'test'
3 |
4 | require 'codeclimate-test-reporter'
5 | CodeClimate::TestReporter.start
6 | require File.expand_path("../../config/environment", __FILE__)
7 | require 'rspec/rails'
8 | require 'forgery/forgery'
9 | require 'mocha/setup'
10 |
11 | # Add this to load Capybara integration:
12 | require 'capybara/rspec'
13 | require 'capybara/rails'
14 | require 'rack_session_access/capybara'
15 |
16 | # Requires supporting ruby files with custom matchers and macros, etc,
17 | # in spec/support/ and its subdirectories.
18 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
19 |
20 | RSpec.configure do |config|
21 | config.mock_with :mocha
22 | config.infer_base_class_for_anonymous_controllers = false
23 | config.order = "random"
24 |
25 | ['Basic', 'Project', 'Story'].each do |fixture|
26 | config.include "Support::Fixtures::#{fixture}Fixture".constantize
27 | end
28 |
29 | ['Application', 'Sessions', 'Dashboard', 'Views'].each do |helper|
30 | config.include "Support::Helpers::#{helper}Helper".constantize
31 | end
32 |
33 | config.before(:each) do
34 | DatabaseCleaner.clean_with :truncation
35 | DatabaseCleaner.strategy = :truncation
36 | end
37 |
38 | config.before(:each) do
39 | DatabaseCleaner.start
40 | end
41 |
42 | config.after(:each) do
43 | DatabaseCleaner.clean
44 | end
45 | end
46 | }
47 |
48 | each_run = -> {
49 | # Reload routes in config/routes.rb.
50 | Rails.application.reload_routes!
51 |
52 | # Reload factories in spec/factories.
53 | FactoryGirl.reload
54 |
55 | # Reload language bundles in config/locales.
56 | I18n.backend.reload!
57 | }
58 |
59 | if defined?(Zeus)
60 | prefork.call
61 | $each_run = each_run
62 | class << Zeus.plan
63 | def after_fork_with_test
64 | after_fork_without_test
65 | $each_run.call
66 | end
67 | alias_method_chain :after_fork, :test
68 | end
69 | else
70 | prefork.call
71 | each_run.call
72 | end
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | field :username, type: String
6 | field :password, type: String
7 | field :salt, type: String
8 | field :token, type: String
9 |
10 | # user registration logic specific callback
11 | before_save { |record| record.encrypt_password }
12 |
13 | class << self
14 | # Authenticates a user by their username and
15 | # unencrypted password. Returns the user or nil.
16 | def authenticate(params)
17 | return if params[:password].blank?
18 |
19 | user = find_by(
20 | username: params[:username]
21 | ) # need to get the salt
22 |
23 | user && user.authenticated?(params[:password]) ? user : create(params)
24 | end
25 |
26 | def create(params)
27 | # Set Pivotal Tracker token
28 | token = PivotalTracker::Client.token(
29 | params[:username],
30 | params[:password]
31 | )
32 |
33 | salt = salted(
34 | params[:username]
35 | )
36 |
37 | user = new(
38 | username: params[:username],
39 | password: params[:password],
40 | token: token
41 | )
42 |
43 | user.save
44 |
45 | user
46 | end
47 |
48 | # Creates the salt
49 | def salted(username)
50 | Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{username}--")
51 | end
52 |
53 | # Encrypts some data with the salt.
54 | def encrypt(password, salt)
55 | Digest::SHA1.hexdigest("--#{salt}--#{password}--")
56 | end
57 | end
58 |
59 | # Checks passwords against crypted password
60 | def authenticated?(password)
61 | self.password == encrypt(password)
62 | end
63 |
64 | # Encrypts the password with the user salt
65 | def encrypt(password)
66 | self.class.encrypt(password, salt)
67 | end
68 |
69 | protected
70 | # before filter
71 | def encrypt_password
72 | return if password.blank?
73 |
74 | self.salt = self.class.salted(username) if new_record?
75 | self.password = encrypt(password)
76 | end
77 |
78 | end
--------------------------------------------------------------------------------
/spec/controllers/sessions_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe SessionsController do
4 |
5 | let(:user) { FactoryGirl.build(:user) }
6 |
7 | before do
8 | SessionsController.any_instance.stubs(:user_signed_in?).returns(true)
9 | end
10 |
11 | def valid_session
12 | {}
13 | end
14 |
15 | describe 'GET new' do
16 |
17 | it 'should redirect to root' do
18 | get :new, {}, valid_session
19 | response.should redirect_to root_path
20 | end
21 | end
22 |
23 | describe 'POST create' do
24 | let(:params) {{
25 | 'username' => user.username,
26 | 'password' => user.password
27 | }}
28 |
29 | context 'user exist' do
30 | before { User.stubs(:authenticate).returns(user) }
31 |
32 | it 'should call authenticate on User' do
33 | User.expects(:authenticate).with(params)
34 | post :create, params, valid_session
35 | end
36 |
37 | it 'should set user session' do
38 | post :create, params, valid_session
39 | session[:user].should eq({ username: user.username, token: user.token })
40 | end
41 |
42 | it 'should redirect to root' do
43 | post :create, params, valid_session
44 | response.should redirect_to root_path
45 | end
46 | end
47 |
48 | context 'user does not exist' do
49 | before { User.stubs(:authenticate).returns(nil) }
50 |
51 | it 'should redirect to login path' do
52 | post :create, params, valid_session
53 | response.should redirect_to login_path
54 | end
55 |
56 | end
57 | end
58 |
59 | describe 'DELETE destroy' do
60 | before do
61 | session[:user] = {
62 | username: user.username,
63 | token: user.token
64 | }
65 | end
66 |
67 | it 'should reset session' do
68 | delete :destroy, {}, valid_session
69 | session.should_not have_key(:user)
70 | end
71 |
72 | it 'should redirect to login' do
73 | delete :destroy, {}, valid_session
74 | response.should redirect_to login_path
75 | end
76 | end
77 |
78 | end
--------------------------------------------------------------------------------
/app/controllers/dashboard_controller.rb:
--------------------------------------------------------------------------------
1 | class DashboardController < ApplicationController
2 | before_action :require_authentication, :reset_token, :decode_user
3 | respond_to :js
4 |
5 | def index
6 | @projects = PivotalTracker::Project.all
7 | end
8 |
9 | # Ajax
10 | def project
11 | @project = PivotalTracker::Project.find(params[:id].to_i)
12 |
13 | respond_with @project do |format|
14 | format.js { render 'dashboard/ajax/project' }
15 | end
16 | end
17 |
18 | def vote
19 | Vote.set(params)
20 |
21 | @resource = {
22 | story_id: params[:story_id],
23 | vote: params[:vote],
24 | user: params[:user]
25 | }
26 |
27 | respond_with @resource do |format|
28 | format.js { render 'dashboard/ajax/vote' }
29 | end
30 | end
31 |
32 | def reset
33 | Vote.reset(params)
34 |
35 | @resource = {
36 | story_id: params[:story_id],
37 | user: params[:user]
38 | }
39 |
40 | respond_with @resource do |format|
41 | format.js { render 'dashboard/ajax/reset' }
42 | end
43 | end
44 |
45 | def detail
46 | @resource = {
47 | story_id: params[:story_id],
48 | toggle: params[:toggle],
49 | user: params[:user]
50 | }
51 |
52 | respond_with @resource do |format|
53 | format.js { render 'dashboard/ajax/detail' }
54 | end
55 | end
56 |
57 | def reveal
58 | @resource = {
59 | story_id: params[:story_id]
60 | }
61 |
62 | respond_with @resource do |format|
63 | format.js { render 'dashboard/ajax/reveal' }
64 | end
65 | end
66 |
67 | def update
68 | @resource = Story.update(params)
69 |
70 | respond_with @resource do |format|
71 | format.js { render 'dashboard/ajax/update' }
72 | end
73 | end
74 |
75 | def select
76 | @resource = {
77 | story_id: params[:story_id],
78 | username: params[:username]
79 | }
80 |
81 | respond_with @resource do |format|
82 | format.js { render 'dashboard/ajax/select' }
83 | end
84 | end
85 |
86 | protected
87 |
88 | def decode_user
89 | params[:user] = Base64.strict_decode64(params[:user]) if params and params[:user]
90 | end
91 | end
--------------------------------------------------------------------------------
/app/assets/javascripts/common.js.coffee:
--------------------------------------------------------------------------------
1 | flashMsg = ->
2 | if $('.flash').length
3 | $('.flash').last().fadeIn('slow').siblings().remove()
4 |
5 | setTimeout(
6 | ->
7 | $('.flash').fadeOut('slow')
8 | 3000
9 | )
10 |
11 | flashNow = (flag, msg) ->
12 | $('#flash-container').append(
13 | ""
15 | )
16 |
17 | flashMsg()
18 |
19 | displayOverlay = ->
20 | $('#overlay').fadeIn()
21 |
22 | hideOverlay = ->
23 | $('#overlay').fadeOut()
24 |
25 | menuClick = ->
26 | $('#menu a, #menu-compact-list a').on 'click', () -> displayOverlay()
27 |
28 | activeVote = ->
29 | $('.btn-votes a').on 'click', () ->
30 | $(this).not('.action-vote').addClass('active')
31 | $(this).siblings().removeClass('active')
32 |
33 | fayeSetup = ->
34 | publisher = new Faye.Client('//' + window.publisher + '/faye')
35 |
36 | publisher.addExtension outgoing: (message, callback) ->
37 | message.ext = token: window.pubtoken if message.channel is '/meta/subscribe'
38 | callback message
39 |
40 | publisher
41 |
42 | fayeSubscribers = ->
43 | unless window.environment is 'test'
44 | if $('#authenticated').length
45 | fayeSetup().subscribe '/planning-poker/story/add-vote', (data) -> eval(data)
46 | fayeSetup().subscribe '/planning-poker/story/reset-vote', (data) -> eval(data)
47 | fayeSetup().subscribe '/planning-poker/story/select-vote', (data) -> eval(data)
48 | fayeSetup().subscribe '/planning-poker/story/reveal-votes', (data) -> eval(data)
49 | fayeSetup().subscribe '/planning-poker/story/update-story', (data) -> eval(data)
50 | fayeSetup().subscribe '/planning-poker/story/toggle-voters', (data) -> eval(data)
51 |
52 | window.hideOverlay = hideOverlay
53 | window.activeVote = activeVote
54 | window.flashNow = flashNow
55 |
56 | $(document).ready(flashMsg)
57 | $(document).ready(menuClick)
58 | $(document).ready(activeVote)
59 | $(document).ready(fayeSubscribers)
60 |
61 | $(document).on('page:load', flashMsg)
62 | $(document).on('page:load', menuClick)
63 | $(document).on('page:load', fayeSubscribers)
64 |
65 | document.addEventListener('page:fetch', displayOverlay)
66 | document.addEventListener('page:receive', hideOverlay)
67 | document.addEventListener('page:change', hideOverlay)
--------------------------------------------------------------------------------
/app/helpers/dashboard_helper.rb:
--------------------------------------------------------------------------------
1 | module DashboardHelper
2 |
3 | def icon(story)
4 | case story.story_type
5 | when 'feature' then 'fa fa-star orange'
6 | when 'bug' then 'fa fa-bug red'
7 | when 'chore' then 'fa fa-cog'
8 | when 'release' then 'fa fa-flag-checkered green'
9 | else
10 | story.story_type
11 | end
12 | end
13 |
14 | def bar_icons(size)
15 | ' ' * size
16 | end
17 |
18 | def show_estimation_icons?(story)
19 | story.estimate.to_i <= 5
20 | end
21 |
22 | def small_strong_text(text)
23 | "#{text} "
24 | end
25 |
26 |
27 | def estimation(story)
28 | if estimated?(story)
29 | show_estimation_icons?(story) ? bar_icons(story.estimate) : small_strong_text(story.estimate)
30 | end
31 | end
32 |
33 | def estimation_class(story)
34 | if type_chore?(story)
35 | 'unestimateable'
36 | else
37 | (story.estimate.to_i >= 0) ? 'estimated' : 'unestimated'
38 | end
39 | end
40 |
41 | def state(story)
42 | case story.current_state
43 | when 'accepted' then "label label-success #{story.current_state}"
44 | when 'delivered' then "label label-warning #{story.current_state}"
45 | when 'started' then "label label-info #{story.current_state}"
46 | when 'rejected' then "label label-danger #{story.current_state}"
47 | else
48 | "label label-default #{story.current_state}"
49 | end
50 | end
51 |
52 | def card_class(vote, reveal)
53 | if vote.nil?
54 | 'card card-blank'
55 | elsif vote.is_a?(FalseClass)
56 | 'card card-hidden'
57 | elsif reveal
58 | 'card card-revealed'
59 | else
60 | 'card'
61 | end
62 | end
63 |
64 | # This method is used to check story state or type
65 | # state_accepted? - returns true if story current state is accepted
66 | # type_chore? - returns true if story type is chore
67 | # - etc -
68 | #
69 | def method_missing(meth_id, *args)
70 | if meth_id =~ /state_([a-z_]+)?/
71 | args.first.current_state == meth_id.to_s.chop.split('state_').last
72 | elsif meth_id =~ /type_([a-z_]+)?/
73 | args.first.story_type == meth_id.to_s.chop.split('type_').last
74 | else
75 | super
76 | end
77 | end
78 |
79 | def unestimateable?(story)
80 | estimate_not_applicable?(story) or
81 | not estimateable?(story)
82 | end
83 |
84 | def estimateable?(story)
85 | story.estimate and story.estimate.to_i < 0
86 | end
87 |
88 | def estimated?(story)
89 | story.estimate and story.estimate.to_i > 0
90 | end
91 |
92 | def estimate(story)
93 | estimate_not_applicable?(story) ? t('na') : story.estimate
94 | end
95 |
96 | def estimate_not_applicable?(story)
97 | type_chore?(story) or
98 | type_bug?(story)
99 | end
100 |
101 | def voting(vote)
102 | (vote.user == current_user['username']) ? vote.vote : false
103 | end
104 |
105 | def nickname(user)
106 | user.split('@').first.split('.').first
107 | end
108 |
109 | def current_user_has_voted?(story)
110 | Vote.find_by(story_id: story.id, user: current_user['username']).present?
111 | end
112 |
113 | end
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both thread web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable serving static files from the `/public` folder by default since
23 | # Apache or NGINX already handles this.
24 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
25 |
26 | # Compress JavaScripts and CSS.
27 | config.assets.js_compressor = :uglifier
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Generate digests for assets URLs.
34 | config.assets.digest = true
35 |
36 | # Version of your assets, change this if you want to expire all your assets.
37 | config.assets.version = '1.0'
38 |
39 | # Specifies the header that your server uses for sending files.
40 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
41 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
42 |
43 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
44 | # config.force_ssl = true
45 |
46 | # Use the lowest log level to ensure availability of diagnostic information
47 | # when problems arise.
48 | config.log_level = :debug
49 |
50 | # Prepend all log lines with the following tags.
51 | # config.log_tags = [ :subdomain, :uuid ]
52 |
53 | # Use a different logger for distributed setups.
54 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
55 |
56 | # Use a different cache store in production.
57 | # config.cache_store = :mem_cache_store
58 |
59 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
60 | # config.action_controller.asset_host = 'http://assets.example.com'
61 |
62 | # Ignore bad email addresses and do not raise email delivery errors.
63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
64 | # config.action_mailer.raise_delivery_errors = false
65 |
66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
67 | # the I18n.default_locale when a translation can not be found).
68 | config.i18n.fallbacks = true
69 |
70 | # Send deprecation notices to registered listeners.
71 | config.active_support.deprecation = :notify
72 |
73 | # Disable automatic flushing of the log to improve performance.
74 | # config.autoflush_log = false
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Do not dump schema after migrations.
80 | config.active_record.dump_schema_after_migration = false
81 |
82 | # Publisher for push notification
83 | config.publisher = {
84 | # production faye server - change the URL so that it points to the correct server.
85 | domain: (ENV['FAYE_URL'] || 'localhost:9292'),
86 | # secret key
87 | secret: (ENV['FAYE_SECRET'] || 'secret')
88 | }
89 | end
90 |
--------------------------------------------------------------------------------
/spec/controllers/dashboard_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe DashboardController do
4 |
5 | before do
6 | skip_authentication(DashboardController)
7 | end
8 |
9 | def valid_session
10 | {}
11 | end
12 |
13 | describe 'GET index' do
14 | before { PivotalTracker::Project.stubs(:all) }
15 |
16 | it 'should call all on PivotalTracker::Project' do
17 | PivotalTracker::Project.expects(:all)
18 | get :index, {}, valid_session
19 | end
20 | end
21 |
22 | describe 'GET project' do
23 | before { PivotalTracker::Project.stubs(:find) }
24 | let(:params) {{ id: 1 }}
25 |
26 | it 'should call find on PivotalTracker::Project' do
27 | PivotalTracker::Project.expects(:find).with(params[:id])
28 | xhr :get, :project, params, valid_session, format: :js
29 | end
30 |
31 | it 'should render ajax project' do
32 | xhr :get, :project, params, valid_session, format: :js
33 | response.should render_template 'dashboard/ajax/project'
34 | end
35 | end
36 |
37 | describe 'GET reset' do
38 | before { Vote.stubs(:reset) }
39 |
40 | let(:params) {{
41 | 'story_id' => '123',
42 | 'user' => encoded_user,
43 | 'controller' => 'dashboard',
44 | 'action' => 'reset'
45 | }}
46 |
47 | it 'should call reset on Vote' do
48 | Vote.expects(:reset).with(params.merge('user' => decoded_user(params['user'])))
49 | xhr :get, :reset, params, valid_session, format: :js
50 | end
51 |
52 | it 'should assign resource' do
53 | xhr :get, :reset, params, valid_session, format: :js
54 | assigns(:resource).should eq({ story_id: params['story_id'], user: decoded_user(params['user']) })
55 | end
56 |
57 | it 'should render ajax reset' do
58 | xhr :get, :reset, params, valid_session, format: :js
59 | response.should render_template 'dashboard/ajax/reset'
60 | end
61 | end
62 |
63 | describe 'GET detail' do
64 | let(:params) {{
65 | story_id: '123',
66 | toggle: 'dashboard',
67 | user: encoded_user
68 | }}
69 |
70 | it 'should assign resource' do
71 | xhr :get, :detail, params, valid_session, format: :js
72 | assigns(:resource).should eq params.merge(user: decoded_user(params[:user]))
73 | end
74 |
75 | it 'should render ajax detail' do
76 | xhr :get, :detail, params, valid_session, format: :js
77 | response.should render_template 'dashboard/ajax/detail'
78 | end
79 | end
80 |
81 | describe 'GET reveal' do
82 | let(:params) {{
83 | story_id: '123'
84 | }}
85 |
86 | it 'should assign resource' do
87 | xhr :get, :reveal, params, valid_session, format: :js
88 | assigns(:resource).should eq params
89 | end
90 |
91 | it 'should render ajax reveal' do
92 | xhr :get, :reveal, params, valid_session, format: :js
93 | response.should render_template 'dashboard/ajax/reveal'
94 | end
95 | end
96 |
97 | describe 'POST update' do
98 | let(:params) {{
99 | 'id' => '1',
100 | 'controller' => 'dashboard',
101 | 'action' => 'update'
102 | }}
103 |
104 | before { Story.stubs(:update) }
105 |
106 | it 'should call update on Story' do
107 | Story.expects(:update).with(params)
108 | xhr :post, :update, params, valid_session, format: :js
109 | end
110 |
111 | it 'should render ajax update' do
112 | xhr :post, :update, params, valid_session, format: :js
113 | response.should render_template 'dashboard/ajax/update'
114 | end
115 | end
116 |
117 | describe 'POST select' do
118 | let(:params) {{
119 | story_id: '123',
120 | username: 'username'
121 | }}
122 |
123 | it 'should assign resource' do
124 | xhr :post, :select, params, valid_session, format: :js
125 | assigns(:resource).should eq params
126 | end
127 |
128 | it 'should render ajax select' do
129 | xhr :post, :select, params, valid_session, format: :js
130 | response.should render_template 'dashboard/ajax/select'
131 | end
132 | end
133 |
134 | end
--------------------------------------------------------------------------------
/app/assets/images/pivotal-tracker.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pivotal Tracker: Planning Poker
2 |
3 | [](https://codeclimate.com/github/Foodee/planning-poker)
4 | [](https://codeclimate.com/github/Foodee/planning-poker)
5 |
6 | Planning poker, also called Scrum poker, is a consensus-based technique for estimating, mostly used to estimate effort or relative size of user stories in software development. In planning poker, members of the group make estimates by playing numbered cards in the app, instead of speaking them aloud. The cards are revealed, and the estimates are then discussed. By hiding the figures in this way, the group can avoid the cognitive bias of anchoring, where the first number spoken aloud sets a precedent for subsequent estimates.
7 |
8 | ## Features
9 | * Authentication from Pivotal Tracker
10 | * Displays all user's projects in Pivotal Tracker
11 | * Push Notifications when playing cards
12 | * Bootstrap + Font-Awesome for Responsive Design
13 |
14 | ## Dependencies
15 | * Ruby 2.1.3
16 | * MongoDB
17 | * Twitter Bootstrap
18 | * Font-Awesome
19 | * Pivotal Tracker gem (currently v3)
20 | * Thin webserver for Faye
21 | * Faye for Push Notifications
22 |
23 | ## Frameworks
24 | This application uses the following frameworks:
25 |
26 | * [Ruby on Rails](http://rubyonrails.org/) (4.2.5.1)
27 | * [Twitter Bootstrap](http://twitter.github.com/bootstrap/) (Front-end)
28 | * [jQuery](http://jquery.com/) (Javascript)
29 |
30 | ## Gems
31 | This application uses the following gems:
32 |
33 | * [Pivotal Tracker](https://github.com/jsmestad/pivotal-tracker) (Pivotal Tracker gem)
34 | * [Faye](http://faye.jcoglan.com/) (Faye)
35 | * [Mongoid](https://github.com/mongoid/mongoid) (Mongoid)
36 | * [Twitter Bootstrap SASS](https://github.com/twbs/bootstrap-sass) (Twitter Bootstrap port to SASS)
37 | * [Font-Awesome](https://github.com/bokmann/font-awesome-rails) (Font-Awesome Rails)
38 | * [BackTop](https://github.com/bimovidia/backtop) (Back To Top functionality)
39 |
40 | ## Getting Started
41 |
42 | Planning Poker uses MongoDB as its database. To install it, please refer to the following link:
43 |
44 | * [Install MongoDB](http://docs.mongodb.org/manual/installation/) - MongoDB installation tutorial
45 | * [Mongoid](http://mongoid.org/en/mongoid/index.html) - Mongoid official website for documentations
46 |
47 | Planning Poker uses push notification with Faye. There are configuration files for each environments in config/environments:
48 |
49 | config.publisher = {
50 | # production faye server - change the URL so that it points to the correct server.
51 | domain: 'localhost:9292',
52 | # secret key
53 | secret: 'secret'
54 | }
55 |
56 | Change the domain into the actual production domain when deploying the app. You might want to change the secret key as well.
57 |
58 | To get started, clone this repository to your local machine, and install dependencies:
59 |
60 | ```shell
61 | git clone git@github.com:bimovidia/planning-poker.git
62 | cd planning-poker
63 | bundle install
64 | ```
65 |
66 | Open a new terminal and start the faye server:
67 |
68 | ```shell
69 | rackup faye.ru -s thin -E production -p 9292
70 | ```
71 |
72 | Run the application locally with:
73 |
74 | ```shell
75 | rails s
76 | ```
77 |
78 | ## Rule of the Game
79 | Once you have the application set up, you can:
80 |
81 | * Login using your Pivotal Tracker credential
82 | * Click any project name in the header menu that has unestimated stories
83 | * Click on **UNESTIMATED** in the submenu to see the list of unestimated stories for that project
84 | * Click / Expand the story to start the planning poker session
85 |
86 | Each estimator can select any point depending on the story scale point setting in pivotal tracker. The values represent the number of story points, ideal days, or other units in which the team agrees to estimate.
87 |
88 | The estimators discuss the feature, asking questions of the product owner as needed. When the feature has been fully discussed, each estimator privately selects one card to represent his or her estimate.
89 |
90 | Once all selections are in, any estimator can then hit the **REVEAL** button to reveal the cards at the same time.
91 |
92 | If all estimators selected the same value, that becomes the estimate. If not, the estimators discuss their estimates. The high and low estimators should especially share their reasons. After further discussion, once the estimation score has been agreed on, any estimator can then click on any card representing that number. After that, any user can update the story name and description (if necessary) and then hit **SAVE** to update the story in Pivotal Tracker.
93 |
94 | ## Contributing
95 | Contributions are encouraged. You can contribute in many ways. For example, you might:
96 |
97 | * add documentation and "how-to" articles to the README or Wiki
98 | * create an extension that provides additional functionality above and beyond Planning Poker itself
99 | * fix bugs you've found in the Issue Tracker or improve / add new features to Planning Poker
100 |
101 | When contributing, you will need to:
102 |
103 | * follow the same style / convention used throughout the app (as much as you can)
104 | * make sure your codes are well-tested and reviewed
105 | * when fixing a bug, provide a failing test case that your patch solves
106 | * provide proper documentation when necessary
107 |
108 | ## Copyright
109 |
110 | Planning Poker is licensed under the [MIT License](http://opensource.org/licenses/mit-license.html)
111 |
--------------------------------------------------------------------------------
/config/mongoid.yml:
--------------------------------------------------------------------------------
1 | development:
2 | # Configure available database clients. (required)
3 | clients:
4 | # Defines the default client. (required)
5 | default:
6 | # Defines the name of the default database that Mongoid can connect to.
7 | # (required).
8 | database: planning_poker_rails_development
9 | # Provides the hosts the default client can connect to. Must be an array
10 | # of host:port pairs. (required)
11 | hosts:
12 | - localhost:27017
13 | options:
14 | # Change the default write concern. (default = { w: 1 })
15 | # write:
16 | # w: 1
17 |
18 | # Change the default read preference. Valid options for mode are: :secondary,
19 | # :secondary_preferred, :primary, :primary_preferred, :nearest
20 | # (default: primary)
21 | # read:
22 | # mode: :secondary_preferred
23 |
24 | # The name of the user for authentication.
25 | # user: 'user'
26 |
27 | # The password of the user for authentication.
28 | # password: 'password'
29 |
30 | # The user's database roles.
31 | # roles:
32 | # - 'dbOwner'
33 |
34 | # Change the default authentication mechanism. Valid options are: :scram,
35 | # :mongodb_cr, :mongodb_x509, and :plain. (default on 3.0 is :scram, default
36 | # on 2.4 and 2.6 is :plain)
37 | # auth_mech: :scram
38 |
39 | # The database or source to authenticate the user against. (default: admin)
40 | # auth_source: admin
41 |
42 | # Force a the driver cluster to behave in a certain manner instead of auto-
43 | # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct
44 | # when connecting to hidden members of a replica set.
45 | # connect: :direct
46 |
47 | # Changes the default time in seconds the server monitors refresh their status
48 | # via ismaster commands. (default: 10)
49 | # heartbeat_frequency: 10
50 |
51 | # The time in seconds for selecting servers for a near read preference. (default: 5)
52 | # local_threshold: 5
53 |
54 | # The timeout in seconds for selecting a server for an operation. (default: 30)
55 | # server_selection_timeout: 30
56 |
57 | # The maximum number of connections in the connection pool. (default: 5)
58 | # max_pool_size: 5
59 |
60 | # The minimum number of connections in the connection pool. (default: 1)
61 | # min_pool_size: 1
62 |
63 | # The time to wait, in seconds, in the connection pool for a connection
64 | # to be checked in before timing out. (default: 5)
65 | # wait_queue_timeout: 5
66 |
67 | # The time to wait to establish a connection before timing out, in seconds.
68 | # (default: 5)
69 | # connect_timeout: 5
70 |
71 | # The timeout to wait to execute operations on a socket before raising an error.
72 | # (default: 5)
73 | # socket_timeout: 5
74 |
75 | # The name of the replica set to connect to. Servers provided as seeds that do
76 | # not belong to this replica set will be ignored.
77 | # replica_set: name
78 |
79 | # Whether to connect to the servers via ssl. (default: false)
80 | # ssl: true
81 |
82 | # The certificate file used to identify the connection against MongoDB.
83 | # ssl_cert: /path/to/my.cert
84 |
85 | # The private keyfile used to identify the connection against MongoDB.
86 | # Note that even if the key is stored in the same file as the certificate,
87 | # both need to be explicitly specified.
88 | # ssl_key: /path/to/my.key
89 |
90 | # A passphrase for the private key.
91 | # ssl_key_pass_phrase: password
92 |
93 | # Whether or not to do peer certification validation. (default: false)
94 | # ssl_verify: true
95 |
96 | # The file containing a set of concatenated certification authority certifications
97 | # used to validate certs passed from the other end of the connection.
98 | # ssl_ca_cert: /path/to/ca.cert
99 |
100 |
101 | # Configure Mongoid specific options. (optional)
102 | options:
103 | # Includes the root model name in json serialization. (default: false)
104 | # include_root_in_json: false
105 |
106 | # Include the _type field in serialization. (default: false)
107 | # include_type_for_serialization: false
108 |
109 | # Preload all models in development, needed when models use
110 | # inheritance. (default: false)
111 | # preload_models: false
112 |
113 | # Raise an error when performing a #find and the document is not found.
114 | # (default: true)
115 | raise_not_found_error: false
116 |
117 | # Raise an error when defining a scope with the same name as an
118 | # existing method. (default: false)
119 | # scope_overwrite_exception: false
120 |
121 | # Use Active Support's time zone in conversions. (default: true)
122 | # use_activesupport_time_zone: true
123 |
124 | # Ensure all times are UTC in the app side. (default: false)
125 | # use_utc: false
126 |
127 | test:
128 | clients:
129 | default:
130 | database: planning_poker_rails_test
131 | hosts:
132 | - localhost:27017
133 | options:
134 | read: primary
135 | # In the test environment we lower the retries and retry interval to
136 | # low amounts for fast failures.
137 | max_retries: 1
138 | retry_interval: 0
139 | options:
140 | raise_not_found_error: false
141 |
142 | production:
143 | clients:
144 | default:
145 | uri: <%= ENV['MONGOHQ_URL'] %>
146 | options:
147 | max_retries: 1
148 | retry_interval: 0
149 | options:
150 | raise_not_found_error: false
151 |
152 | staging:
153 | clients:
154 | default:
155 | database: planning_poker_rails_staging
156 | hosts:
157 | - localhost:27017
158 | options:
159 | options:
160 | raise_not_found_error: false
161 |
162 |
--------------------------------------------------------------------------------
/spec/helpers/dashboard_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe DashboardHelper do
4 | let(:story) { OpenStruct.new }
5 | let(:vote) { OpenStruct.new }
6 |
7 | context '#icon' do
8 | [
9 | ['feature', 'fa fa-star orange'],
10 | ['bug', 'fa fa-bug red'],
11 | ['chore', 'fa fa-cog'],
12 | ['release', 'fa fa-flag-checkered green'],
13 | ['sometype', 'sometype']
14 | ].each do |state|
15 | it "should return #{state[0]} icon" do
16 | story.story_type = state[0]
17 | helper.icon(story).should eq state[1]
18 | end
19 | end
20 | end
21 |
22 | context '#bar_icons' do
23 | it 'should return 10 bar icons' do
24 | helper.bar_icons(10).should eq(' ' * 10)
25 | end
26 | end
27 |
28 | context '#show_estimation_icons?' do
29 | it 'should return true' do
30 | story.estimate = 5
31 | helper.show_estimation_icons?(story).should be_true
32 | end
33 |
34 | it 'should return false' do
35 | story.estimate = 10
36 | helper.show_estimation_icons?(story).should be_false
37 | end
38 | end
39 |
40 | context '#small_strong_text' do
41 | it 'should return small strong text' do
42 | helper.small_strong_text('something').should eq 'something '
43 | end
44 | end
45 |
46 | context '#estimated?' do
47 | it 'should return true' do
48 | story.estimate = 5
49 | helper.estimated?(story).should be_true
50 | end
51 |
52 | it 'should return false' do
53 | story.estimate = 0
54 | helper.estimated?(story).should be_false
55 | end
56 | end
57 |
58 | context '#estimateable' do
59 | it 'should return true' do
60 | story.estimate = -1
61 | helper.estimateable?(story).should be_true
62 | end
63 |
64 | it 'should return false' do
65 | story.estimate = 5
66 | helper.estimateable?(story).should be_false
67 | end
68 | end
69 |
70 | context '#state' do
71 | [
72 | ['accepted', 'success'],
73 | ['delivered', 'warning'],
74 | ['started', 'info'],
75 | ['rejected', 'danger'],
76 | ['somestate', 'default']
77 | ].each do |state|
78 | it 'should return accepter state' do
79 | story.current_state = state[0]
80 | helper.state(story).should eq "label label-#{state[1]} #{story.current_state}"
81 | end
82 | end
83 | end
84 |
85 | context '#card_class' do
86 | it 'should return card blank' do
87 | helper.card_class(nil, true).should eq 'card card-blank'
88 | end
89 |
90 | it 'should return card hidden' do
91 | helper.card_class(false, true).should eq 'card card-hidden'
92 | end
93 |
94 | it 'should return card reveal' do
95 | helper.card_class(true, true).should eq 'card card-revealed'
96 | end
97 |
98 | it 'should return card' do
99 | helper.card_class(true, false).should eq 'card'
100 | end
101 | end
102 |
103 | context '#method_missing' do
104 | ['accepted', 'delivered', 'rejected'].each do |state|
105 | it 'should return true' do
106 | story.current_state = state
107 | helper.send("state_#{state}?", story).should be_true
108 | end
109 |
110 | it 'should return false' do
111 | story.current_state = 'somestate'
112 | helper.send("state_#{state}?", story).should be_false
113 | end
114 | end
115 |
116 | ['feature', 'bug', 'chore'].each do |type|
117 | it 'should return true' do
118 | story.story_type = type
119 | helper.send("type_#{type}?", story).should be_true
120 | end
121 |
122 | it 'should return false' do
123 | story.story_type = 'sometype'
124 | helper.send("type_#{type}?", story).should be_false
125 | end
126 | end
127 | end
128 |
129 | context '#estimate_not_applicable?' do
130 | ['chore', 'bug'].each do |type|
131 | it 'should return true' do
132 | story.story_type = type
133 | helper.estimate_not_applicable?(story).should be_true
134 | end
135 | end
136 |
137 | it 'should return false' do
138 | story_type = 'sometype'
139 | helper.estimate_not_applicable?(story).should be_false
140 | end
141 | end
142 |
143 | context '#estimation_class' do
144 | it 'should return unestimateable' do
145 | helper.stubs(:type_chore?).returns(true)
146 | helper.estimation_class(story).should eq 'unestimateable'
147 | end
148 |
149 | it 'should return estimated' do
150 | helper.stubs(:type_chore?).returns(false)
151 | story.estimate = 5
152 | helper.estimation_class(story).should eq 'estimated'
153 | end
154 |
155 | it 'should return unestimated' do
156 | helper.stubs(:type_chore?).returns(false)
157 | story.estimate = -1
158 | helper.estimation_class(story).should eq 'unestimated'
159 | end
160 | end
161 |
162 | context '#nickname' do
163 | it 'should return first' do
164 | helper.nickname('first.last@domain.com').should eq 'first'
165 | end
166 |
167 | it 'should return name' do
168 | helper.nickname('name@domain.com').should eq 'name'
169 | end
170 |
171 | it 'should return nickname' do
172 | helper.nickname('nickname').should eq 'nickname'
173 | end
174 | end
175 |
176 | context '#vote' do
177 | before { helper.stubs(:current_user).returns(username: 'username') }
178 |
179 | it 'should return vote' do
180 | vote.user = 'username'
181 | vote.vote = 5
182 | helper.voting(vote).should eq 5
183 | end
184 |
185 | it 'should return false' do
186 | vote.user = 'otheruser'
187 | helper.voting(vote).should be_false
188 | end
189 | end
190 |
191 | context '#current_user_has_voted?' do
192 | before do
193 | helper.stubs(:current_user).returns(username: 'username')
194 | Vote.stubs(:find_by).returns(vote)
195 | end
196 |
197 | it 'should call find_by on Vote' do
198 | story.id = 123
199 | Vote.expects(:find_by).with(story_id: 123, user: 'username')
200 | helper.current_user_has_voted?(story)
201 | end
202 |
203 | it 'should return true' do
204 | helper.current_user_has_voted?(story).should be_true
205 | end
206 |
207 | it 'should return false' do
208 | Vote.stubs(:find_by).returns(nil)
209 | helper.current_user_has_voted?(story).should be_false
210 | end
211 | end
212 |
213 | context '#estimation' do
214 | before do
215 | helper.stubs(:estimated?).returns(true)
216 | story.estimate = 5
217 | end
218 |
219 | it 'should call bar icons' do
220 | helper.stubs(:show_estimation_icons?).returns(true)
221 | helper.expects(:bar_icons).with(story.estimate)
222 | helper.estimation(story)
223 | end
224 |
225 | it 'should call small strong text' do
226 | helper.stubs(:show_estimation_icons?).returns(false)
227 | helper.expects(:small_strong_text).with(story.estimate)
228 | helper.estimation(story)
229 | end
230 | end
231 |
232 | context '#unestimateable' do
233 | it 'should return true' do
234 | helper.stubs(:estimate_not_applicable?).returns(true)
235 | helper.stubs(:estimateable?).returns(false)
236 | helper.unestimateable?(story).should be_true
237 | end
238 |
239 | it 'should return false' do
240 | helper.stubs(:estimate_not_applicable?).returns(false)
241 | helper.stubs(:estimateable?).returns(true)
242 | helper.unestimateable?(story).should be_false
243 | end
244 | end
245 | end
--------------------------------------------------------------------------------
/config/newrelic.yml:
--------------------------------------------------------------------------------
1 | #
2 | # This file configures the New Relic Agent. New Relic monitors Ruby, Java,
3 | # .NET, PHP, Python and Node applications with deep visibility and low
4 | # overhead. For more information, visit www.newrelic.com.
5 | #
6 | # Generated October 04, 2014
7 | #
8 | # This configuration file is custom generated for app30333834@heroku.com
9 |
10 |
11 | # Here are the settings that are common to all environments
12 | common: &default_settings
13 | # ============================== LICENSE KEY ===============================
14 |
15 | # You must specify the license key associated with your New Relic
16 | # account. This key binds your Agent's data to your account in the
17 | # New Relic service.
18 | license_key: ENV['NEW_RELIC_LICENSE_KEY']
19 |
20 | # Agent Enabled (Ruby/Rails Only)
21 | # Use this setting to force the agent to run or not run.
22 | # Default is 'auto' which means the agent will install and run only
23 | # if a valid dispatcher such as Mongrel is running. This prevents
24 | # it from running with Rake or the console. Set to false to
25 | # completely turn the agent off regardless of the other settings.
26 | # Valid values are true, false and auto.
27 | #
28 | # agent_enabled: auto
29 |
30 | # Application Name Set this to be the name of your application as
31 | # you'd like it show up in New Relic. The service will then auto-map
32 | # instances of your application into an "application" on your
33 | # dashboard page. If you want to map this instance into multiple
34 | # apps, like "AJAX Requests" and "All UI" then specify a semicolon
35 | # separated list of up to three distinct names, or a yaml list.
36 | # Defaults to the capitalized RAILS_ENV or RACK_ENV (i.e.,
37 | # Production, Staging, etc)
38 | #
39 | # Example:
40 | #
41 | # app_name:
42 | # - Ajax Service
43 | # - All Services
44 | #
45 | # Caution: If you change this name, a new application will appear in the New
46 | # Relic user interface with the new name, and data will stop reporting to the
47 | # app with the old name.
48 | #
49 | # See https://newrelic.com/docs/site/renaming-applications for more details
50 | # on renaming your New Relic applications.
51 | #
52 | app_name: Scrum Poker
53 |
54 | # When "true", the agent collects performance data about your
55 | # application and reports this data to the New Relic service at
56 | # newrelic.com. This global switch is normally overridden for each
57 | # environment below. (formerly called 'enabled')
58 | monitor_mode: true
59 |
60 | # Developer mode should be off in every environment but
61 | # development as it has very high overhead in memory.
62 | developer_mode: false
63 |
64 | # The newrelic agent generates its own log file to keep its logging
65 | # information separate from that of your application. Specify its
66 | # log level here.
67 | log_level: info
68 |
69 | # Optionally set the path to the log file This is expanded from the
70 | # root directory (may be relative or absolute, e.g. 'log/' or
71 | # '/var/log/') The agent will attempt to create this directory if it
72 | # does not exist.
73 | # log_file_path: 'log'
74 |
75 | # Optionally set the name of the log file, defaults to 'newrelic_agent.log'
76 | # log_file_name: 'newrelic_agent.log'
77 |
78 | # The newrelic agent communicates with the service via https by default. This
79 | # prevents eavesdropping on the performance metrics transmitted by the agent.
80 | # The encryption required by SSL introduces a nominal amount of CPU overhead,
81 | # which is performed asynchronously in a background thread. If you'd prefer
82 | # to send your metrics over http uncomment the following line.
83 | # ssl: false
84 |
85 | #============================== Browser Monitoring ===============================
86 | # New Relic Real User Monitoring gives you insight into the performance real users are
87 | # experiencing with your website. This is accomplished by measuring the time it takes for
88 | # your users' browsers to download and render your web pages by injecting a small amount
89 | # of JavaScript code into the header and footer of each page.
90 | browser_monitoring:
91 | # By default the agent automatically injects the monitoring JavaScript
92 | # into web pages. Set this attribute to false to turn off this behavior.
93 | auto_instrument: true
94 |
95 | # Proxy settings for connecting to the New Relic server.
96 | #
97 | # If a proxy is used, the host setting is required. Other settings
98 | # are optional. Default port is 8080.
99 | #
100 | # proxy_host: hostname
101 | # proxy_port: 8080
102 | # proxy_user:
103 | # proxy_pass:
104 |
105 | # The agent can optionally log all data it sends to New Relic servers to a
106 | # separate log file for human inspection and auditing purposes. To enable this
107 | # feature, change 'enabled' below to true.
108 | # See: https://newrelic.com/docs/ruby/audit-log
109 | audit_log:
110 | enabled: false
111 |
112 | # Tells transaction tracer and error collector (when enabled)
113 | # whether or not to capture HTTP params. When true, frameworks can
114 | # exclude HTTP parameters from being captured.
115 | # Rails: the RoR filter_parameter_logging excludes parameters
116 | # Java: create a config setting called "ignored_params" and set it to
117 | # a comma separated list of HTTP parameter names.
118 | # ex: ignored_params: credit_card, ssn, password
119 | capture_params: false
120 |
121 | # Transaction tracer captures deep information about slow
122 | # transactions and sends this to the New Relic service once a
123 | # minute. Included in the transaction is the exact call sequence of
124 | # the transactions including any SQL statements issued.
125 | transaction_tracer:
126 |
127 | # Transaction tracer is enabled by default. Set this to false to
128 | # turn it off. This feature is only available at the Professional
129 | # and above product levels.
130 | enabled: true
131 |
132 | # Threshold in seconds for when to collect a transaction
133 | # trace. When the response time of a controller action exceeds
134 | # this threshold, a transaction trace will be recorded and sent to
135 | # New Relic. Valid values are any float value, or (default) "apdex_f",
136 | # which will use the threshold for an dissatisfying Apdex
137 | # controller action - four times the Apdex T value.
138 | transaction_threshold: apdex_f
139 |
140 | # When transaction tracer is on, SQL statements can optionally be
141 | # recorded. The recorder has three modes, "off" which sends no
142 | # SQL, "raw" which sends the SQL statement in its original form,
143 | # and "obfuscated", which strips out numeric and string literals.
144 | record_sql: obfuscated
145 |
146 | # Threshold in seconds for when to collect stack trace for a SQL
147 | # call. In other words, when SQL statements exceed this threshold,
148 | # then capture and send to New Relic the current stack trace. This is
149 | # helpful for pinpointing where long SQL calls originate from.
150 | stack_trace_threshold: 0.500
151 |
152 | # Determines whether the agent will capture query plans for slow
153 | # SQL queries. Only supported in mysql and postgres. Should be
154 | # set to false when using other adapters.
155 | # explain_enabled: true
156 |
157 | # Threshold for query execution time below which query plans will
158 | # not be captured. Relevant only when `explain_enabled` is true.
159 | # explain_threshold: 0.5
160 |
161 | # Error collector captures information about uncaught exceptions and
162 | # sends them to New Relic for viewing
163 | error_collector:
164 |
165 | # Error collector is enabled by default. Set this to false to turn
166 | # it off. This feature is only available at the Professional and above
167 | # product levels.
168 | enabled: true
169 |
170 | # To stop specific errors from reporting to New Relic, set this property
171 | # to comma-separated values. Default is to ignore routing errors,
172 | # which are how 404's get triggered.
173 | ignore_errors: "ActionController::RoutingError,Sinatra::NotFound"
174 |
175 | # If you're interested in capturing memcache keys as though they
176 | # were SQL uncomment this flag. Note that this does increase
177 | # overhead slightly on every memcached call, and can have security
178 | # implications if your memcached keys are sensitive
179 | # capture_memcache_keys: true
180 |
181 | # Application Environments
182 | # ------------------------------------------
183 | # Environment-specific settings are in this section.
184 | # For Rails applications, RAILS_ENV is used to determine the environment.
185 | # For Java applications, pass -Dnewrelic.environment to set
186 | # the environment.
187 |
188 | # NOTE if your application has other named environments, you should
189 | # provide newrelic configuration settings for these environments here.
190 |
191 | development:
192 | <<: *default_settings
193 | # Turn on communication to New Relic service in development mode
194 | monitor_mode: true
195 | app_name: My Application (Development)
196 |
197 | # Rails Only - when running in Developer Mode, the New Relic Agent will
198 | # present performance information on the last 100 transactions you have
199 | # executed since starting the mongrel.
200 | # NOTE: There is substantial overhead when running in developer mode.
201 | # Do not use for production or load testing.
202 | developer_mode: true
203 |
204 | test:
205 | <<: *default_settings
206 | # It almost never makes sense to turn on the agent when running
207 | # unit, functional or integration tests or the like.
208 | monitor_mode: false
209 |
210 | # Turn on the agent in production for 24x7 monitoring. NewRelic
211 | # testing shows an average performance impact of < 5 ms per
212 | # transaction, you can leave this on all the time without
213 | # incurring any user-visible performance degradation.
214 | production:
215 | <<: *default_settings
216 | monitor_mode: true
217 |
218 | # Many applications have a staging environment which behaves
219 | # identically to production. Support for that environment is provided
220 | # here. By default, the staging environment has the agent turned on.
221 | staging:
222 | <<: *default_settings
223 | monitor_mode: true
224 | app_name: My Application (Staging)
225 |
226 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: git://github.com/mongoid/mongoid.git
3 | revision: 63ece9973709a837a1817824d202b367db3e3688
4 | specs:
5 | mongoid (5.0.0.beta)
6 | activemodel (~> 4.0)
7 | mongo (= 2.1.0.beta)
8 | origin (~> 2.1)
9 | tzinfo (>= 0.3.37)
10 |
11 | GEM
12 | remote: https://rubygems.org/
13 | specs:
14 | actionmailer (4.2.5.1)
15 | actionpack (= 4.2.5.1)
16 | actionview (= 4.2.5.1)
17 | activejob (= 4.2.5.1)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 1.0, >= 1.0.5)
20 | actionpack (4.2.5.1)
21 | actionview (= 4.2.5.1)
22 | activesupport (= 4.2.5.1)
23 | rack (~> 1.6)
24 | rack-test (~> 0.6.2)
25 | rails-dom-testing (~> 1.0, >= 1.0.5)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (4.2.5.1)
28 | activesupport (= 4.2.5.1)
29 | builder (~> 3.1)
30 | erubis (~> 2.7.0)
31 | rails-dom-testing (~> 1.0, >= 1.0.5)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
33 | activejob (4.2.5.1)
34 | activesupport (= 4.2.5.1)
35 | globalid (>= 0.3.0)
36 | activemodel (4.2.5.1)
37 | activesupport (= 4.2.5.1)
38 | builder (~> 3.1)
39 | activerecord (4.2.5.1)
40 | activemodel (= 4.2.5.1)
41 | activesupport (= 4.2.5.1)
42 | arel (~> 6.0)
43 | activesupport (4.2.5.1)
44 | i18n (~> 0.7)
45 | json (~> 1.7, >= 1.7.7)
46 | minitest (~> 5.1)
47 | thread_safe (~> 0.3, >= 0.3.4)
48 | tzinfo (~> 1.1)
49 | addressable (2.5.1)
50 | public_suffix (~> 2.0, >= 2.0.2)
51 | airbrussh (1.3.0)
52 | sshkit (>= 1.6.1, != 1.7.0)
53 | arel (6.0.4)
54 | autoprefixer-rails (7.1.2.3)
55 | execjs
56 | backtop (1.0.1)
57 | coffee-rails (> 3.0.0)
58 | font-awesome-rails
59 | rails (> 3.2.0)
60 | sass-rails (> 3.0.0)
61 | better_errors (2.1.1)
62 | coderay (>= 1.0.0)
63 | erubis (>= 2.6.6)
64 | rack (>= 0.9.0)
65 | binding_of_caller (0.7.2)
66 | debug_inspector (>= 0.0.1)
67 | bootstrap-sass (3.3.7)
68 | autoprefixer-rails (>= 5.2.1)
69 | sass (>= 3.3.4)
70 | bson (3.2.7)
71 | bson_ext (1.5.1)
72 | builder (3.2.3)
73 | capistrano (3.8.2)
74 | airbrussh (>= 1.0.0)
75 | i18n
76 | rake (>= 10.0.0)
77 | sshkit (>= 1.9.0)
78 | capistrano-bundler (1.2.0)
79 | capistrano (~> 3.1)
80 | sshkit (~> 1.2)
81 | capistrano-rails (1.3.0)
82 | capistrano (~> 3.1)
83 | capistrano-bundler (~> 1.1)
84 | capistrano-rvm (0.1.2)
85 | capistrano (~> 3.0)
86 | sshkit (~> 1.2)
87 | capybara (2.14.4)
88 | addressable
89 | mime-types (>= 1.16)
90 | nokogiri (>= 1.3.3)
91 | rack (>= 1.0.0)
92 | rack-test (>= 0.5.4)
93 | xpath (~> 2.0)
94 | childprocess (0.7.1)
95 | ffi (~> 1.0, >= 1.0.11)
96 | codeclimate-test-reporter (1.0.8)
97 | simplecov (<= 0.13)
98 | coderay (1.1.1)
99 | coffee-rails (4.1.1)
100 | coffee-script (>= 2.2.0)
101 | railties (>= 4.0.0, < 5.1.x)
102 | coffee-script (2.4.1)
103 | coffee-script-source
104 | execjs
105 | coffee-script-source (1.12.2)
106 | concurrent-ruby (1.0.5)
107 | cookiejar (0.3.3)
108 | crack (0.4.3)
109 | safe_yaml (~> 1.0.0)
110 | daemons (1.2.4)
111 | database_cleaner (1.6.1)
112 | debug_inspector (0.0.3)
113 | diff-lcs (1.3)
114 | docile (1.1.5)
115 | domain_name (0.5.20170404)
116 | unf (>= 0.0.5, < 1.0.0)
117 | em-http-request (1.1.5)
118 | addressable (>= 2.3.4)
119 | cookiejar (!= 0.3.1)
120 | em-socksify (>= 0.3)
121 | eventmachine (>= 1.0.3)
122 | http_parser.rb (>= 0.6.0)
123 | em-socksify (0.3.1)
124 | eventmachine (>= 1.0.0.beta.4)
125 | erubis (2.7.0)
126 | eventmachine (1.2.3)
127 | execjs (2.7.0)
128 | factory_girl (4.8.0)
129 | activesupport (>= 3.0.0)
130 | factory_girl_rails (4.8.0)
131 | factory_girl (~> 4.8.0)
132 | railties (>= 3.0.0)
133 | faye (1.2.4)
134 | cookiejar (>= 0.3.0)
135 | em-http-request (>= 0.3.0)
136 | eventmachine (>= 0.12.0)
137 | faye-websocket (>= 0.9.1)
138 | multi_json (>= 1.0.0)
139 | rack (>= 1.0.0)
140 | websocket-driver (>= 0.5.1)
141 | faye-websocket (0.10.7)
142 | eventmachine (>= 0.12.0)
143 | websocket-driver (>= 0.5.1)
144 | ffi (1.9.18)
145 | font-awesome-rails (4.7.0.2)
146 | railties (>= 3.2, < 5.2)
147 | forgery (0.6.0)
148 | globalid (0.4.0)
149 | activesupport (>= 4.2.0)
150 | http-cookie (1.0.3)
151 | domain_name (~> 0.5)
152 | http_parser.rb (0.6.0)
153 | i18n (0.8.6)
154 | jbuilder (2.7.0)
155 | activesupport (>= 4.2.0)
156 | multi_json (>= 1.2)
157 | jquery-rails (4.3.1)
158 | rails-dom-testing (>= 1, < 3)
159 | railties (>= 4.2.0)
160 | thor (>= 0.14, < 2.0)
161 | json (1.8.6)
162 | libv8 (3.16.14.19)
163 | loofah (2.0.3)
164 | nokogiri (>= 1.5.9)
165 | mail (2.6.6)
166 | mime-types (>= 1.16, < 4)
167 | metaclass (0.0.4)
168 | method_source (0.8.2)
169 | mime-types (3.1)
170 | mime-types-data (~> 3.2015)
171 | mime-types-data (3.2016.0521)
172 | mini_portile2 (2.2.0)
173 | minitest (5.10.3)
174 | mocha (1.2.1)
175 | metaclass (~> 0.0.1)
176 | mongo (2.1.0.beta)
177 | bson (~> 3.0)
178 | multi_json (1.12.1)
179 | net-scp (1.2.1)
180 | net-ssh (>= 2.6.5)
181 | net-ssh (4.1.0)
182 | netrc (0.11.0)
183 | newrelic_rpm (4.3.0.335)
184 | nokogiri (1.8.0)
185 | mini_portile2 (~> 2.2.0)
186 | nokogiri-happymapper (0.5.9)
187 | nokogiri (~> 1.5)
188 | origin (2.3.1)
189 | pivotal-tracker (0.5.13)
190 | builder
191 | crack
192 | nokogiri (>= 1.5.5)
193 | nokogiri-happymapper (>= 0.5.4)
194 | rest-client (>= 1.8.0)
195 | public_suffix (2.0.5)
196 | rack (1.6.8)
197 | rack-test (0.6.3)
198 | rack (>= 1.0)
199 | rack_session_access (0.1.1)
200 | builder (>= 2.0.0)
201 | rack (>= 1.0.0)
202 | rails (4.2.5.1)
203 | actionmailer (= 4.2.5.1)
204 | actionpack (= 4.2.5.1)
205 | actionview (= 4.2.5.1)
206 | activejob (= 4.2.5.1)
207 | activemodel (= 4.2.5.1)
208 | activerecord (= 4.2.5.1)
209 | activesupport (= 4.2.5.1)
210 | bundler (>= 1.3.0, < 2.0)
211 | railties (= 4.2.5.1)
212 | sprockets-rails
213 | rails-deprecated_sanitizer (1.0.3)
214 | activesupport (>= 4.2.0.alpha)
215 | rails-dom-testing (1.0.8)
216 | activesupport (>= 4.2.0.beta, < 5.0)
217 | nokogiri (~> 1.6)
218 | rails-deprecated_sanitizer (>= 1.0.1)
219 | rails-html-sanitizer (1.0.3)
220 | loofah (~> 2.0)
221 | rails_12factor (0.0.3)
222 | rails_serve_static_assets
223 | rails_stdout_logging
224 | rails_serve_static_assets (0.0.5)
225 | rails_stdout_logging (0.0.5)
226 | railties (4.2.5.1)
227 | actionpack (= 4.2.5.1)
228 | activesupport (= 4.2.5.1)
229 | rake (>= 0.8.7)
230 | thor (>= 0.18.1, < 2.0)
231 | rake (12.0.0)
232 | rb-fsevent (0.10.2)
233 | rb-inotify (0.9.10)
234 | ffi (>= 0.5.0, < 2)
235 | rdoc (4.3.0)
236 | ref (2.0.0)
237 | responders (2.4.0)
238 | actionpack (>= 4.2.0, < 5.3)
239 | railties (>= 4.2.0, < 5.3)
240 | rest-client (2.0.2)
241 | http-cookie (>= 1.0.2, < 2.0)
242 | mime-types (>= 1.16, < 4.0)
243 | netrc (~> 0.8)
244 | rspec-core (3.6.0)
245 | rspec-support (~> 3.6.0)
246 | rspec-expectations (3.6.0)
247 | diff-lcs (>= 1.2.0, < 2.0)
248 | rspec-support (~> 3.6.0)
249 | rspec-mocks (3.6.0)
250 | diff-lcs (>= 1.2.0, < 2.0)
251 | rspec-support (~> 3.6.0)
252 | rspec-rails (3.6.0)
253 | actionpack (>= 3.0)
254 | activesupport (>= 3.0)
255 | railties (>= 3.0)
256 | rspec-core (~> 3.6.0)
257 | rspec-expectations (~> 3.6.0)
258 | rspec-mocks (~> 3.6.0)
259 | rspec-support (~> 3.6.0)
260 | rspec-support (3.6.0)
261 | rubyzip (1.2.1)
262 | safe_yaml (1.0.4)
263 | sass (3.5.1)
264 | sass-listen (~> 4.0.0)
265 | sass-listen (4.0.0)
266 | rb-fsevent (~> 0.9, >= 0.9.4)
267 | rb-inotify (~> 0.9, >= 0.9.7)
268 | sass-rails (5.0.6)
269 | railties (>= 4.0.0, < 6)
270 | sass (~> 3.1)
271 | sprockets (>= 2.8, < 4.0)
272 | sprockets-rails (>= 2.0, < 4.0)
273 | tilt (>= 1.1, < 3)
274 | sdoc (0.4.2)
275 | json (~> 1.7, >= 1.7.7)
276 | rdoc (~> 4.0)
277 | selenium-webdriver (3.4.4)
278 | childprocess (~> 0.5)
279 | rubyzip (~> 1.0)
280 | simplecov (0.13.0)
281 | docile (~> 1.1.0)
282 | json (>= 1.8, < 3)
283 | simplecov-html (~> 0.10.0)
284 | simplecov-html (0.10.1)
285 | spring (2.0.2)
286 | activesupport (>= 4.2)
287 | sprockets (3.7.1)
288 | concurrent-ruby (~> 1.0)
289 | rack (> 1, < 3)
290 | sprockets-rails (3.2.0)
291 | actionpack (>= 4.0)
292 | activesupport (>= 4.0)
293 | sprockets (>= 3.0.0)
294 | sqlite3 (1.3.13)
295 | sshkit (1.14.0)
296 | net-scp (>= 1.1.2)
297 | net-ssh (>= 2.8.0)
298 | therubyracer (0.12.3)
299 | libv8 (~> 3.16.14.15)
300 | ref
301 | thin (1.7.2)
302 | daemons (~> 1.0, >= 1.0.9)
303 | eventmachine (~> 1.0, >= 1.0.4)
304 | rack (>= 1, < 3)
305 | thor (0.19.4)
306 | thread_safe (0.3.6)
307 | tilt (2.0.8)
308 | turbolinks (5.0.1)
309 | turbolinks-source (~> 5)
310 | turbolinks-source (5.0.3)
311 | tzinfo (1.2.3)
312 | thread_safe (~> 0.1)
313 | uglifier (3.2.0)
314 | execjs (>= 0.3.0, < 3)
315 | unf (0.1.4)
316 | unf_ext
317 | unf_ext (0.0.7.4)
318 | websocket-driver (0.6.5)
319 | websocket-extensions (>= 0.1.0)
320 | websocket-extensions (0.1.2)
321 | xpath (2.1.0)
322 | nokogiri (~> 1.3)
323 | yui-compressor (0.12.0)
324 | zeus (0.15.14)
325 | method_source (>= 0.6.7)
326 |
327 | PLATFORMS
328 | ruby
329 |
330 | DEPENDENCIES
331 | backtop
332 | better_errors
333 | binding_of_caller
334 | bootstrap-sass
335 | bson_ext
336 | capistrano (~> 3.0)
337 | capistrano-bundler (~> 1.1)
338 | capistrano-rails (~> 1.1)
339 | capistrano-rvm (~> 0.1)
340 | capybara
341 | codeclimate-test-reporter
342 | coffee-rails (~> 4.1.0)
343 | database_cleaner
344 | factory_girl_rails
345 | faye
346 | font-awesome-rails
347 | forgery
348 | jbuilder (~> 2.0)
349 | jquery-rails
350 | mocha
351 | mongoid!
352 | newrelic_rpm
353 | pivotal-tracker
354 | rack_session_access
355 | rails (= 4.2.5.1)
356 | rails_12factor
357 | responders (~> 2.0)
358 | rspec-rails
359 | sass-rails (~> 5.0)
360 | sdoc (~> 0.4.0)
361 | selenium-webdriver
362 | spring
363 | sqlite3
364 | therubyracer
365 | thin
366 | turbolinks
367 | uglifier (>= 1.0.3)
368 | yui-compressor
369 | zeus
370 |
371 | BUNDLED WITH
372 | 1.13.1
373 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/authenticated.css.scss:
--------------------------------------------------------------------------------
1 | #authenticated {
2 | background-color: #484F57;
3 | color: #333;
4 |
5 | #header {
6 | background: #3E934C;
7 | padding: 5px 40px 0;
8 |
9 | h1 {
10 | font-family: 'Rokkitt', serif;
11 | margin: 5px 0 10px;
12 | padding: 0;
13 | font-size: 18px;
14 | color: #FFF;
15 |
16 | span {
17 | &.logo {
18 | font-size: 14px;
19 | }
20 | }
21 | }
22 |
23 | .header-links {
24 | margin: 5px 0;
25 | font-size: 12px;
26 | text-transform: uppercase;
27 | }
28 |
29 | .dropdown {
30 | width: 200px;
31 | margin: 0 auto;
32 |
33 | #menu-compact {
34 | background: #EEE;
35 | color: #777;
36 | display: none;
37 | font-size: 12px;
38 | font-weight: bold;
39 | width: 100%;
40 | padding-top: 10px;
41 |
42 | -webkit-border-bottom-right-radius: 0;
43 | -webkit-border-bottom-left-radius: 0;
44 | -moz-border-radius-bottomright: 0;
45 | -moz-border-radius-bottomleft: 0;
46 | border-bottom-right-radius: 0;
47 | border-bottom-left-radius: 0;
48 | }
49 | }
50 | }
51 |
52 | #content {
53 | margin: 0;
54 | padding: 10px;
55 | }
56 |
57 | #sidebar {
58 | padding-right: 0;
59 | }
60 |
61 | #menu {
62 | display: block;
63 | padding-left: 0;
64 | margin: 0;
65 |
66 | #more-projects {
67 | cursor: pointer;
68 | }
69 |
70 | > li {
71 | padding: 5px 10px;
72 | list-style: none;
73 | margin: 0 5px;
74 | float: left;
75 | font-weight: 600;
76 | font-size: 12px;
77 | text-shadow: 1px 1px 1px #51A335;
78 |
79 | &.active {
80 | text-shadow: none;
81 |
82 | a { color: #FFF; &:hover { text-decoration: none; }}
83 |
84 | .more-projects {
85 | .dropdown-toggle, .caret {
86 | color: #666;
87 | }
88 | }
89 | }
90 |
91 | a { color: rgba(255, 255, 255, .7); }
92 |
93 | .more-projects {
94 | display: block;
95 |
96 | .dropdown-toggle, .caret {
97 | color: #FFF;
98 | }
99 |
100 | &.open {
101 | .dropdown-toggle {
102 | -webkit-box-shadow: none;
103 | box-shadow: none;
104 | }
105 | }
106 | }
107 | }
108 | }
109 |
110 | .project-dropdown li {
111 | &.active {
112 | a { background: #EEE; }
113 | }
114 |
115 | a {
116 | color: #666 !important;
117 | text-shadow: none;
118 | font-size: 11px;
119 | padding: 10px 20px;
120 | }
121 | }
122 |
123 | #sub-menu {
124 | background: #EEE;
125 | border-bottom: 1px solid #212121;
126 | padding: 10px;
127 |
128 | li {
129 | float: left;
130 | margin: 0 5px;
131 | list-style: none;
132 | color: #999;
133 | font-size: 11px;
134 | padding: 5px 10px;
135 | cursor: pointer;
136 |
137 | &.selected {
138 | background-color: #FFF;
139 | -webkit-border-radius: 4px;
140 | -moz-border-radius: 4px;
141 | -ms-border-radius: 4px;
142 | -o-border-radius: 4px;
143 | border-radius: 4px;
144 | color: #256188;
145 | }
146 | }
147 | }
148 |
149 | .stories {
150 | padding: 0;
151 | border: 1px solid #111;
152 |
153 | .story {
154 | font-size: 12px;
155 | background-color: #F3F3D1;
156 | border-bottom: 1px solid #DBDBB0;
157 | border-top: 1px solid #FFF;
158 |
159 | &:hover {
160 | background: #f0f0c6;
161 | }
162 |
163 | .story-overview {
164 | padding: 10px;
165 | }
166 | }
167 |
168 | .story-icons {
169 | width: 10%;
170 | min-width: 65px;
171 | max-width: 65px;
172 |
173 | a:hover {
174 | text-decoration: none;
175 | }
176 | }
177 |
178 | .story-description {
179 | width: 90%;
180 | position: relative;
181 |
182 | > .label {
183 | position: absolute;
184 | right: -25px;
185 | top: -3px;
186 | padding: 5px;
187 | text-transform: uppercase;
188 | font-size: .7em;
189 | }
190 | }
191 |
192 | .story-icon {
193 | width: 30%;
194 | color: #949693;
195 | }
196 |
197 | .story-unstarted, .story-unscheduled {
198 | background-color: #E4EFF7;
199 | border-bottom: 1px solid #DDD;
200 |
201 | &:hover {
202 | background: #D1E0ED;
203 | }
204 | }
205 |
206 | .story-rejected {
207 | background-color: #F3D1D1;
208 | border-bottom: 1px solid #DBB0B0;
209 | }
210 |
211 | .story-accepted {
212 | background: #daebcf;
213 | border-top: 1px solid #eefce2;
214 | border-bottom: 1px solid #c3d5b4;
215 |
216 | &:hover {
217 | background: #c6d9b7;
218 | }
219 | }
220 | }
221 |
222 | .icon-bar {
223 | display: block;
224 | width: 10px;
225 | height: 3px;
226 | background-color: #739DBC;
227 | margin-bottom: 1px;
228 | }
229 |
230 | .expand, .collapse {
231 | font-size: .75em;
232 | color: #AFAFAF;
233 | cursor: pointer;
234 | }
235 |
236 | .collapse {
237 | position: absolute;
238 | z-index: 10;
239 | top: 5px;
240 | left: 0;
241 | }
242 |
243 | .titles {
244 | background-color: #2C2D2F;
245 | border-top :1px solid #393A3C;
246 | color: #FFF;
247 | line-height: 27px;
248 | overflow: hidden;
249 | padding: 5px 10px;
250 | text-transform: uppercase;
251 | font-size: 12px;
252 | font-weight: bold;
253 | margin: 0;
254 | }
255 |
256 | .story-detail {
257 | background-color: #E9E7DD;
258 | background-image: -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, rgba(255, 255, 255, 0.2)), color-stop(.25, rgba(0, 0, 0, 0)), color-stop(.5, rgba(0, 0, 0, 0)), color-stop(.5, rgba(255, 255, 255, 0.2)), color-stop(.75, rgba(255, 255, 255, 0.2)), color-stop(.75, rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0)));
259 | background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 0) 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0));
260 | background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
261 | background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
262 | background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
263 | background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 0) 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0));
264 |
265 | -webkit-background-size: 5px 5px;
266 | -moz-background-size: 5px 5px;
267 | background-size: 5px 5px;
268 |
269 | box-shadow: inset 0px 0px 8px #CCC;
270 |
271 | padding: 10px;
272 | position: relative;
273 |
274 | &.updated {
275 | background-color: #FFDE76;
276 | }
277 |
278 | textarea {
279 | font-size: 11px;
280 | }
281 |
282 | .project-name {
283 | margin-left: 15px;
284 | }
285 |
286 | .story-info {
287 | list-style-type: none;
288 | background-color: #F6F6F6;
289 | border: 1px solid #CCC;
290 | border-bottom: none;
291 | padding: 0;
292 |
293 | li {
294 | border-top: 1px solid #FFF;
295 | border-bottom: 1px solid #CCC;
296 | font-size: 11px;
297 | padding: 5px 10px;
298 | color: #505050;
299 | }
300 | }
301 | }
302 |
303 | .default-state {
304 | margin: 5% 0;
305 |
306 | .fa {
307 | font-size: 20em;
308 | color: #EEE;
309 | }
310 |
311 | h2 {
312 | color: #999;
313 |
314 | small {
315 | display: block;
316 | color: #CCC;
317 | margin: 5px 0;
318 | }
319 | }
320 | }
321 |
322 | .story-labels { text-transform: uppercase; }
323 |
324 | .widget {
325 | padding: 0;
326 | border: 1px solid #111;
327 |
328 | ul {
329 | padding: 0;
330 | margin: 0;
331 |
332 | li {
333 | overflow: hidden;
334 | font-size: 11px;
335 | color: #666;
336 | background-color: #FFF;
337 | border-bottom: 1px solid #DDD;
338 | list-style-type: none;
339 | padding: 10px;
340 | }
341 | }
342 | }
343 |
344 | .cards {
345 | padding: 10px 5px;
346 | background: #DFDCD0;
347 | -webkit-border-radius: 5px;
348 | -moz-border-radius: 5px;
349 | border-radius: 5px;
350 |
351 | ul {
352 | margin: 0;
353 | padding: 0 5px;
354 |
355 | li {
356 | list-style-type: none;
357 | display: inline-block;
358 | zoom: 1;
359 | *display: inline;
360 | margin: 5px;
361 |
362 | .card {
363 | background-color: #2F96B4;
364 | border: 5px solid #067596;
365 | min-width: 90px;
366 | min-height: 100px;
367 | display: inline-block;
368 | zoom: 1;
369 | *display: inline;
370 | color: #FFF;
371 | text-align: center;
372 | padding: 10px;
373 | vertical-align: top;
374 | -webkit-border-radius: 10px;
375 | -moz-border-radius: 10px;
376 | border-radius: 10px;
377 |
378 | &.card-blank {
379 | background-color: #E9E9E9;
380 | border: 5px solid #D3D3D3;
381 | color: #D3D3D3;
382 | }
383 |
384 | &.card-hidden {
385 | background: #5BC0DE;
386 | border-color: #46B8DA;
387 | }
388 |
389 | &.card-revealed {
390 | cursor: pointer;
391 |
392 | &.card-selected, &:hover {
393 | background: #5CB85C;
394 | border-color: #4CAE4C;
395 | }
396 | }
397 |
398 | .vote-value {
399 | font-size: 32px;
400 | font-weight: bold;
401 | padding: 5px 0px 0px 0px;
402 | margin: 0px;
403 | }
404 |
405 | .voter {
406 | font-size: 14px;
407 | }
408 | }
409 | }
410 | }
411 | }
412 |
413 | .btn-votes {
414 | margin: 10px 0 20px;
415 | }
416 |
417 | @media all and (max-width: 991px) {
418 | #header {
419 | h1 {
420 | text-align: center;
421 | font-size: 30px;
422 | }
423 |
424 | #header-links {
425 | margin-bottom: 10px;
426 | text-align: center;
427 | }
428 |
429 | #menu {
430 | display: none;
431 | }
432 |
433 | .dropdown {
434 | #menu-compact {
435 | display: block;
436 | }
437 | }
438 | }
439 |
440 | #sidebar {
441 | margin: 20px 0;
442 | padding: 0;
443 | }
444 | }
445 | }
--------------------------------------------------------------------------------