23 | volumes:
24 | - .:/Falko-2017.2-BackEnd
25 | - bundle-cache:/bundle-cache
26 | networks:
27 | - falko-backend
28 | ports:
29 | - 3000:3000
30 | depends_on:
31 | - db
32 |
33 | volumes:
34 | bundle-cache:
35 | name: bundle-cache
36 |
37 | networks:
38 | falko-backend:
39 | name: falko-backend
40 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "pathname"
3 | require "fileutils"
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts "== Installing dependencies =="
18 | system! "gem install bundler --conservative"
19 | system("bundle check") || system!("bundle install")
20 |
21 |
22 | # puts "\n== Copying sample files =="
23 | # unless File.exist?('config/database.yml')
24 | # cp 'config/database.yml.sample', 'config/database.yml'
25 | # end
26 |
27 | puts "\n== Preparing database =="
28 | system! "bin/rails db:setup"
29 |
30 | puts "\n== Removing old logs and tempfiles =="
31 | system! "bin/rails log:clear tmp:clear"
32 |
33 | puts "\n== Restarting application server =="
34 | system! "bin/rails restart"
35 | end
36 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
11 | 2. Update the README.md with details of changes to the interface, this includes new environment variables exposed ports, useful file locations and container parameters.
12 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent.
13 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
14 |
15 | ## Code of Conduct
16 |
17 | - You can find useful info about our code of conduct at CODE_OF_CONDUCT.md, found at this project root.
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:2.4.1
2 |
3 | MAINTAINER alaxallves@gmail.com
4 |
5 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
6 |
7 | RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - \
8 | && apt-get install -y nodejs
9 |
10 | RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
11 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
12 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
13 | apt-get update && apt-get install -y yarn postgresql-client
14 |
15 | WORKDIR /Falko-2017.2-BackEnd
16 |
17 | ENV RAILS_ENV=production
18 |
19 | ENV BUNDLE_PATH /bundle-cache
20 |
21 | # Client ID provided by the GitHub Prod app
22 | ENV CLIENT_ID=cbd5f91719282354f09b
23 |
24 | # Client secret provided by the GitHub Prod app
25 | ENV CLIENT_SECRET=634dd13c943b8196d4345334031c43d6d5a75fc8
26 |
27 | COPY . /Falko-2017.2-BackEnd
28 |
29 | COPY Gemfile /Falko-2017.2-BackEnd/Gemfile
30 | COPY Gemfile.lock /Falko-2017.2-BackEnd/Gemfile.lock
31 |
32 | EXPOSE 3000
33 |
34 | ENTRYPOINT ["./start-prod.sh"]
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Falko Organization
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/controllers/revisions_controller.rb:
--------------------------------------------------------------------------------
1 | class RevisionsController < ApplicationController
2 | include ValidationsHelper
3 | include RevisionsDoc
4 |
5 | before_action :set_revision, only: [:show, :update]
6 |
7 | before_action only: [:index, :create] do
8 | validate_sprint(0, :sprint_id)
9 | end
10 |
11 | before_action only: [:show, :update, :destroy] do
12 | validate_sprint_dependencies(:id, "revision")
13 | end
14 |
15 | def index
16 | @revision = @sprint.revision
17 | render json: @revision
18 | end
19 |
20 | def create
21 | create_sprint_dependencies("revision", revision_params)
22 | end
23 |
24 | def show
25 | render json: @revision
26 | end
27 |
28 | def update
29 | if @revision.update(revision_params)
30 | render json: @revision
31 | else
32 | render json: @revision.errors, status: :unprocessable_entity
33 | end
34 | end
35 |
36 | def destroy
37 | @revision.destroy
38 | end
39 |
40 | private
41 | def set_revision
42 | @revision = Revision.find(params[:id])
43 | end
44 |
45 | def revision_params
46 | params.require(:revision).permit(done_report: [], undone_report: [])
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails"
4 | # Pick the frameworks you want:
5 | require "active_model/railtie"
6 | require "active_job/railtie"
7 | require "active_record/railtie"
8 | require "action_controller/railtie"
9 | require "action_mailer/railtie"
10 | require "action_view/railtie"
11 | # require "action_cable/engine"
12 | # require "sprockets/railtie"
13 | require "rails/test_unit/railtie"
14 |
15 | # Require the gems listed in Gemfile, including any gems
16 | # you've limited to :test, :development, or :production.
17 | Bundler.require(*Rails.groups)
18 |
19 | module Falko20172
20 | class Application < Rails::Application
21 | # Initialize configuration defaults for originally generated Rails version.
22 | config.load_defaults 5.1
23 | config.autoload_paths << Rails.root.join("lib")
24 | # Settings in config/environments/* take precedence over those specified here.
25 | # Application configuration should go into files in config/initializers
26 | # -- all .rb files in that directory are automatically loaded.
27 |
28 | # Only loads a smaller set of middleware suitable for API only apps.
29 | # Middleware like session, flash, cookies can be added back manually.
30 | # Skip views, helpers and assets when generating a new resource.
31 | config.api_only = true
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | language: generic
4 |
5 | env:
6 | global:
7 | - RAILS_ENV=test
8 | - CC_TEST_REPORTER_ID=095bc7916972a84272890f2c79eaba7fd64124a4b5b4e826ba0a4d0599b6bfe3
9 |
10 | services:
11 | - docker
12 |
13 | before_script:
14 | - mv scripts/docker/docker-compose.test.yml .
15 | - docker-compose -f docker-compose.test.yml up -d
16 | - docker exec falko-api-test bash -c "curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > cc-test-reporter"
17 | - docker exec falko-api-test bash -c "chmod +x cc-test-reporter"
18 | - docker exec falko-api-test bash -c "./cc-test-reporter before-build"
19 |
20 | install: true
21 |
22 | addons:
23 | code_climate:
24 | repo_token: "095bc7916972a84272890f2c79eaba7fd64124a4b5b4e826ba0a4d0599b6bfe3"
25 |
26 | script:
27 | - >
28 | while ! docker logs falko-api-test | grep "Listening on tcp://0.0.0.0:3000"; do
29 | echo "Waiting for start script to finish..."
30 | sleep 5
31 | done;
32 | - docker exec -it falko-api-test bundle exec rails db:test:prepare
33 | - docker exec -it falko-api-test bundle exec rails test
34 | - docker exec -it falko-api-test bundle exec rubocop
35 |
36 | after_script:
37 | - docker exec -it falko-api-test bash -c "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT --coverage-input-type simplecov --id $CC_TEST_REPORTER_ID"
38 | - docker-compose down --remove-orphans
39 |
--------------------------------------------------------------------------------
/app/controllers/retrospectives_controller.rb:
--------------------------------------------------------------------------------
1 | class RetrospectivesController < ApplicationController
2 | include ValidationsHelper
3 | include RetrospectivesDoc
4 |
5 | before_action :set_retrospective, only: [:show, :update, :destroy]
6 |
7 | before_action only: [:index, :create] do
8 | validate_sprint(0, :sprint_id)
9 | end
10 |
11 | before_action only: [:show, :update, :destroy] do
12 | validate_sprint_dependencies(:id, "retrospective")
13 | end
14 |
15 |
16 | def index
17 | @retrospective = @sprint.retrospective
18 |
19 | if @retrospective == nil
20 | @retrospective = []
21 | end
22 |
23 | render json: @retrospective
24 | end
25 |
26 | def create
27 | create_sprint_dependencies("retrospective", retrospective_params)
28 | end
29 |
30 | def show
31 | render json: @retrospective
32 | end
33 |
34 | def update
35 | if @retrospective.update(retrospective_params)
36 | render json: @retrospective
37 | else
38 | render json: @retrospective.errors, status: :unprocessable_entity
39 | end
40 | end
41 |
42 | def destroy
43 | @retrospective.destroy
44 | end
45 |
46 | private
47 | def set_retrospective
48 | @retrospective = Retrospective.find(params[:id])
49 | end
50 |
51 | def retrospective_params
52 | params.require(:retrospective).permit(:sprint_report, positive_points: [], negative_points: [] , improvements: [])
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/app/helpers/burndown_helper.rb:
--------------------------------------------------------------------------------
1 | module BurndownHelper
2 | def check_pipeline(story, burned_stories)
3 | if story.pipeline == "Done"
4 | if burned_stories[story.final_date] == nil
5 | burned_stories[story.final_date] = story.story_points
6 | else
7 | burned_stories[story.final_date] += story.story_points
8 | end
9 | end
10 | end
11 |
12 | def get_burned_points(sprint, burned_stories)
13 | for story in sprint.stories
14 | check_pipeline(story, burned_stories)
15 | end
16 |
17 | return burned_stories
18 | end
19 |
20 | def get_total_points(sprint)
21 | total_points = 0
22 |
23 | for story in sprint.stories
24 | total_points += story.story_points
25 | end
26 |
27 | return total_points
28 | end
29 |
30 | def set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points)
31 | range_dates.each do |date|
32 | if burned_stories[date] == nil
33 | burned_stories[date] = total_points
34 | else
35 | total_points -= burned_stories[date]
36 | burned_stories[date] = total_points
37 | end
38 | date_axis.push(date)
39 | points_axis.push(burned_stories[date])
40 | end
41 | end
42 |
43 | def set_ideal_line(days_of_sprint, ideal_line, planned_points)
44 | for day in (days_of_sprint).downto(0)
45 | ideal_line.push(planned_points * (day / (Float days_of_sprint)))
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/app/controllers/grades_controller.rb:
--------------------------------------------------------------------------------
1 | class GradesController < ApplicationController
2 | include MetricHelper
3 | include GradesDoc
4 |
5 | before_action :set_grade, only: [:show, :update, :destroy]
6 |
7 | before_action only: [:index, :create] do
8 | validate_project(0, :project_id)
9 | end
10 |
11 | before_action only: [:show, :update] do
12 | validate_grade(:id, 0)
13 | end
14 |
15 | def index
16 | grade = @project.grade
17 | render json: grade
18 | end
19 |
20 | def create
21 | if @project.grade.blank?
22 | grade = Grade.new(grade_params)
23 | grade.project = @project
24 |
25 | if grade.save
26 | render json: grade, status: :created
27 | else
28 | render json: grade.errors, status: :unprocessable_entity
29 | end
30 | else
31 | render json: @project.grade
32 | end
33 | end
34 |
35 | def update
36 | if @grade.update(grade_params)
37 | render json: @grade
38 | else
39 | render json: @grade.errors, status: :unprocessable_entity
40 | end
41 | end
42 |
43 | def show
44 | final_metric = get_metrics(@grade)
45 | render json: final_metric
46 | end
47 |
48 | def destroy
49 | @grade.destroy
50 | end
51 |
52 | private
53 | def grade_params
54 | params.require(:grade).permit(:weight_debts, :weight_velocity, :weight_burndown)
55 | end
56 |
57 | def set_grade
58 | @grade = Grade.find(params[:id])
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/app/controllers/releases_controller.rb:
--------------------------------------------------------------------------------
1 | class ReleasesController < ApplicationController
2 | include ValidationsHelper
3 | include ReleasesDoc
4 |
5 | before_action :set_release, only: [:show, :update, :destroy]
6 |
7 | before_action only: [:index, :create] do
8 | validate_project(0, :project_id)
9 | end
10 |
11 | before_action only: [:show, :edit, :update, :destroy] do
12 | validate_release(:id, 0)
13 | end
14 |
15 | def index
16 | @releases = @project.releases.reverse
17 | render json: @releases
18 | end
19 |
20 | def show
21 | update_amount_of_sprints
22 | render json: @release
23 | end
24 |
25 | def create
26 | @release = Release.create(release_params)
27 | @release.project = @project
28 | update_amount_of_sprints
29 | if @release.save
30 | render json: @release, status: :created
31 | else
32 | render json: @release.errors, status: :unprocessable_entity
33 | end
34 | end
35 |
36 | def update
37 | if @release.update(release_params)
38 | render json: @release
39 | else
40 | render json: @release.errors, status: :unprocessable_entity
41 | end
42 | end
43 |
44 | def destroy
45 | @release.destroy
46 | end
47 |
48 | private
49 | def set_release
50 | @release = Release.find(params[:id])
51 | end
52 |
53 | def release_params
54 | params.require(:release).permit(:name, :description, :amount_of_sprints, :initial_date, :final_date)
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/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 `rails 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 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: fbaf4d96ad5701178ae81fc6158701d7117a0e212845183f90429489101c4e3dadce2a63ef87f228b2dd2aa7109dda79c73a9386bac2bcd85237d9984d68cf5c
22 |
23 | test:
24 | secret_key_base: 4b182d9b7c7d4c04e3229e2d0294952305f2c475cd73884316b3bf6230e95f00a53364702860ea59ecb6c88746ef52aa92043f0ab3bac84d4ecab1e1a8e0e0f4
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: e14c6cba62f0405feb13e55eed54d74c89c32d454a66b6a5babb6bb5f3fdca51776fff22cc589a4d3635c95f603332f69cd1b26ae56ad702cf69063350fdbd26
33 |
--------------------------------------------------------------------------------
/app/helpers/issue_graphic_helper.rb:
--------------------------------------------------------------------------------
1 | module IssueGraphicHelper
2 | def get_issues_graphic(actual_date, option, issues)
3 | first_date = actual_date - 3.month
4 | number_of_issues = {}
5 | dates = []
6 | total_open_issues = [0, 0, 0]
7 | total_closed_issues = [0, 0, 0]
8 |
9 | if option == "month"
10 | dates = [(actual_date - 2.month).strftime("%B"), (actual_date - 1.month).strftime("%B"), (actual_date).strftime("%B")]
11 | end
12 |
13 |
14 | issues.each do |issue|
15 | increase_issues(issue, actual_date, total_open_issues, total_closed_issues, first_date)
16 | end
17 | number_of_issues = { opened_issues: total_open_issues, closed_issues: total_closed_issues, months: dates }
18 | return number_of_issues
19 | end
20 |
21 | def increase_issues(issue, actual_date, total_open_issues, total_closed_issues, first_date)
22 | if issue.created_at.to_date <= actual_date && issue.created_at.to_date >= first_date
23 | interval_issues(issue, total_open_issues, actual_date)
24 | if issue.closed_at != nil
25 | interval_issues(issue, total_closed_issues, actual_date)
26 | end
27 | end
28 | end
29 |
30 | def interval_issues (issue, total_open_issues, actual_date)
31 | last_month = actual_date - 1.month
32 | if issue.created_at.to_date.month == actual_date.month
33 | increment_total(total_open_issues, 2)
34 | elsif issue.created_at.to_date.month == last_month.month
35 | increment_total(total_open_issues, 1)
36 | else
37 | increment_total(total_open_issues, 0)
38 | end
39 | end
40 |
41 | def increment_total(total, position)
42 | total[position] = total[position] + 1
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/app/helpers/velocity_helper.rb:
--------------------------------------------------------------------------------
1 | module VelocityHelper
2 | include BurndownHelper
3 |
4 | def get_total_points_release(release)
5 | total_points_release = 0
6 |
7 | for sprint in release.sprints
8 | total_points_release += get_total_points(sprint)
9 | end
10 |
11 | return total_points_release
12 | end
13 |
14 | def calculate_points(sprint)
15 | sprint_total_points = 0
16 | sprint_completed_points = 0
17 |
18 | sprint.stories.each do |story|
19 | sprint_total_points += story.story_points
20 | if story.pipeline == "Done"
21 | sprint_completed_points += story.story_points
22 | end
23 | end
24 |
25 | points = { sprint_total_points: sprint_total_points, sprint_completed_points: sprint_completed_points }
26 | end
27 |
28 | def get_sprints_informations(sprints, actual_sprint)
29 | names = []
30 | total_points = []
31 | completed_points = []
32 | velocities = []
33 | points = {}
34 |
35 | sprints.each do |sprint|
36 | if actual_sprint.final_date >= sprint.final_date
37 | names.push(sprint.name)
38 |
39 | points = calculate_points(sprint)
40 |
41 | total_points.push(points[:sprint_total_points])
42 | completed_points.push(points[:sprint_completed_points])
43 | velocities.push(calculate_velocity(completed_points))
44 | end
45 | end
46 |
47 | sprints_informations = { total_points: total_points, completed_points: completed_points, names: names, velocities: velocities }
48 | end
49 |
50 | def calculate_velocity(completed_points)
51 | size = completed_points.size
52 | total_completed_points = 0
53 |
54 | completed_points.each do |point|
55 | total_completed_points += point
56 | end
57 |
58 | return Float(total_completed_points) / size
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/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.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join("tmp/caching-dev.txt").exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | "Cache-Control" => "public, max-age=#{2.days.seconds.to_i}"
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 |
41 | # Raises error for missing translations
42 | # config.action_view.raise_on_missing_translations = true
43 |
44 | # Use an evented file watcher to asynchronously detect changes in source code,
45 | # routes, locales, etc. This feature depends on the listen gem.
46 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
47 | end
48 |
--------------------------------------------------------------------------------
/test/models/grade_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class GradeTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "Ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "This project is part of the course MDS.",
15 | is_project_from_github: true,
16 | user_id: @user.id,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 |
28 | @sprint = Sprint.create(
29 | name: "Sprint1",
30 | description: "This sprint.",
31 | initial_date: "23-04-1993",
32 | final_date: "23-04-2003",
33 | release_id: @release.id
34 | )
35 |
36 | @grade = Grade.create(
37 | weight_debts: "1",
38 | weight_burndown: "1",
39 | weight_velocity: "1",
40 | project_id: @project.id
41 | )
42 |
43 | @grade = Grade.create(
44 | weight_debts: "1",
45 | weight_burndown: "1",
46 | weight_velocity: "1",
47 | project_id: @project.id
48 | )
49 | end
50 |
51 | test "should save a grade" do
52 | assert @grade.save
53 | end
54 |
55 | test "Grade should have a weight debts" do
56 | @grade.weight_debts = nil
57 | assert_not @grade.save
58 | end
59 |
60 | test "Grade should have a weight burndown" do
61 | @grade.weight_burndown = nil
62 | assert_not @grade.save
63 | end
64 |
65 | test "Grade should have a weight velocity" do
66 | @grade.weight_velocity = nil
67 | assert_not @grade.save
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | apipie
4 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
5 | post "authenticate", to: "authentication#authenticate"
6 | post "request_github_token", to: "users#request_github_token"
7 | post "remove_github_token", to: "users#remove_github_token"
8 |
9 | post "password/forgot", to: "passwords#forgot"
10 | post "password/reset", to: "passwords#reset"
11 | get "password/validate_token", to: "passwords#validate_token"
12 |
13 |
14 | get "sprints/:id/burndown", to: "sprints#get_burndown"
15 |
16 | get "repos", to: "projects#github_projects_list"
17 | get "projects/:id/contributors", to: "projects#get_contributors"
18 | post "projects/:id/issues/assignees", to: "issues#update_assignees"
19 |
20 | get "sprints/:id/to_do_stories", to: "stories#to_do_list"
21 | get "sprints/:id/done_stories", to: "stories#done_list"
22 | get "sprints/:id/doing_stories", to: "stories#doing_list"
23 |
24 | get "projects/:id/issues", to: "issues#index"
25 | get "projects/:id/issues/:page", to: "issues#index"
26 | post "projects/:id/issues", to: "issues#create"
27 | put "projects/:id/issues", to: "issues#update"
28 | patch "projects/:id/issues", to: "issues#update"
29 | delete "projects/:id/issues", to: "issues#close"
30 |
31 | get "sprints/:id/velocity", to: "sprints#get_velocity"
32 |
33 | post "projects/:id/reopen_issue", to: "issues#reopen_issue"
34 | post "/projects/:id/issues/graphic", to: "issues#issue_graphic_data"
35 |
36 | resources :users, shallow: true do
37 | resources :projects do
38 | resources :grades
39 | resources :releases do
40 | resources :sprints do
41 | resources :stories
42 | resources :revisions
43 | resources :retrospectives
44 | end
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/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 public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | "Cache-Control" => "public, max-age=#{1.hour.seconds.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/test/models/revision_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RevisionTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "Ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Esse projeto faz parte da disciplina MDS.",
15 | is_project_from_github: true,
16 | user_id: @user.id,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 |
28 | @sprint = Sprint.create(
29 | name: "Sprint1",
30 | description: "Essa sprint",
31 | initial_date: "23-04-1993",
32 | final_date: "23-04-2003",
33 | release_id: @release.id
34 | )
35 |
36 | @revision = Revision.create(
37 | done_report: ["A us11 foi feita com sucesso"],
38 | undone_report: ["Tudo foi feito"],
39 | sprint_id: @sprint.id
40 | )
41 | end
42 |
43 | test "should save a revision" do
44 | assert @revision.save
45 | end
46 |
47 | test "Revision should have done_report between 0 and 500 caracters" do
48 | @revision.done_report = ["r" * 250]
49 | assert @revision.save
50 | end
51 |
52 | test "Revision should have undone_report between 0 and 500 caracters" do
53 | @revision.undone_report = ["r" * 250]
54 | assert @revision.save
55 | end
56 |
57 | test "Revision should have done_report less than 500 caracters" do
58 | @revision.done_report = "b" * 501
59 | assert_not @revision.save
60 | end
61 |
62 | test "Revision should have undone_report less than 500 caracters" do
63 | @revision.undone_report = "b" * 501
64 | assert_not @revision.save
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/app/docs/users_doc.rb:
--------------------------------------------------------------------------------
1 | module UsersDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :user do
5 | param :name, String, "User' name"
6 | param :email, String, "User's email"
7 | param :password_digest, String, "User's password"
8 | param :created_at, Date, "User's time of creation", allow_nil: false
9 | param :updated_at, Date, "User's time of edition", allow_nil: false
10 | param :description, String, "User's acess token"
11 | end
12 |
13 | api :GET, "/users/:id", "Show a user"
14 | error code: 401, desc: "Unauthorized"
15 | error code: 404, desc: "Not Found"
16 | error code: 500, desc: "Internal Server Error"
17 | description "Show a specific user"
18 | returns code: 200, desc: "Ok" do
19 | param_group :user
20 | end
21 | example <<-EOS
22 | {
23 | "id": 1,
24 | "name": "Vitor Barbosa",
25 | "email": "barbosa@gmail.com",
26 | "password_digest": "$2a$10$GDiOPE7a2YMzhaOdgW88NOecH3.eiBLcCZQjDjoi2ykrAdgreV2ge",
27 | "created_at": "2019-04-11T15:42:33.741Z",
28 | "updated_at": "2019-04-11T15:42:33.741Z",
29 | "access_token": null
30 | }
31 | EOS
32 | def show
33 | end
34 |
35 | api :POST, "/users", "Create a user"
36 | error code: 401, desc: "Unauthorized"
37 | error code: 404, desc: "Not Found"
38 | error code: 500, desc: "Internal Server Error"
39 | description "Create a specific user"
40 | param_group :user
41 | def create
42 | end
43 |
44 | api :PATCH, "/users/:id", "Update a user"
45 | error code: 401, desc: "Unauthorized"
46 | error code: 404, desc: "Not Found"
47 | error code: 500, desc: "Internal Server Error"
48 | description "Update a specific user"
49 | param_group :user
50 | def update
51 | end
52 |
53 | api :DELETE, "/users/:id", "Delete a user"
54 | error code: 401, desc: "Unauthorized"
55 | error code: 404, desc: "Not Found"
56 | error code: 500, desc: "Internal Server Error"
57 | description "Delete a specific user"
58 | param_group :user
59 | def destroy
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/app/controllers/passwords_controller.rb:
--------------------------------------------------------------------------------
1 | class PasswordsController < ApplicationController
2 | skip_before_action :authenticate_request
3 |
4 | def forgot
5 | if password_params[:email].blank?
6 | return render json: { error: "Email not present" }, status: :bad_request
7 | end
8 |
9 | user = User.find_by(email: password_params[:email].downcase)
10 |
11 | if user.present?
12 | user.generate_password_token!
13 | UserMailer.with(user: user).recover_password_email.deliver_now
14 | render json: { status: "ok" }, status: :ok
15 | else
16 | render json: { status: "ok" }, status: :ok
17 | end
18 | end
19 |
20 | def reset
21 | if password_params[:token].blank?
22 | return render json: { error: "Token not present" } , status: :bad_request
23 | end
24 |
25 | token = password_params[:token].to_s
26 |
27 | user = User.find_by(reset_password_token: token)
28 |
29 | if user.present? && user.password_token_valid?
30 | if user.reset_password!(password_params[:password])
31 | render json: { status: "ok" }, status: :ok
32 | else
33 | render json: { error: user.errors.full_messages }, status: :unprocessable_entity
34 | end
35 | else
36 | render json: { error: ["Link not valid or expired. Try generating a new one."] }, status: :not_found
37 | end
38 | end
39 |
40 | def validate_token
41 | if password_params[:token].blank?
42 | return render json: { error: "Token not present" }, status: :bad_request
43 | else
44 | token = password_params[:token].to_s
45 | end
46 |
47 | user = User.find_by(reset_password_token: token)
48 |
49 | if user.present? && user.password_token_valid?
50 | render json: { status: "true" }, status: :ok
51 | else
52 | render json: { status: "false", error: ["Link not valid or expired. Try generating a new one."] }, status: :ok
53 | end
54 | end
55 |
56 | private
57 |
58 | def password_params
59 | params.permit(:email, :token, :password)
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/test/models/project_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ProjectTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "Ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Some project description.",
15 | user_id: @user.id,
16 | is_project_from_github: true,
17 | is_scoring: false
18 | )
19 |
20 | @project2 = Project.create(
21 | name: "Falko",
22 | description: "Some project description.",
23 | user_id: @user.id,
24 | is_project_from_github: false
25 | )
26 | end
27 |
28 | test "should save valid project" do
29 | assert @project.save
30 | end
31 |
32 | test "should not save project with a blank name" do
33 | @project.name = ""
34 | assert_not @project.save
35 | end
36 |
37 | test "should not save a project with name smaller than 2 characters" do
38 | @project.name = "M"
39 | assert_not @project.save
40 | end
41 |
42 | test "should not save a project with name bigger than 128 characters" do
43 | @project.name = "a" * 129
44 | assert_not @project.save
45 | end
46 |
47 | test "should save a project with an exacty 2 or 128 characters name" do
48 | @project.name = "Ma"
49 | assert @project.save
50 | @project.name = "a" * 128
51 | assert @project.save
52 | end
53 |
54 | test "should save project with a blank description" do
55 | @project.description = ""
56 | assert @project.save
57 | end
58 |
59 | test "should not save a project with description bigger than 256 characters" do
60 | @project.description = "a" * 257
61 | assert_not @project.save
62 | end
63 |
64 | test "should save a project with an exacty 256 characters description" do
65 | @project.description = "a" * 256
66 | assert @project.save
67 | end
68 |
69 | test "should save a project with a blank description" do
70 | @project.description = ""
71 | assert @project.save
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | ruby "2.4.1"
3 |
4 | git_source(:github) do |repo_name|
5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
6 | "https://github.com/#{repo_name}.git"
7 | end
8 |
9 |
10 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
11 | gem "rails", "~> 5.1.6"
12 | # Use postgresql as the database for Active Record
13 | gem "pg", "~> 1.0.0"
14 | # Use Puma as the app server
15 | gem "puma", "~> 3.7"
16 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
17 | # gem 'jbuilder', '~> 2.5'
18 | # Use ActiveModel has_secure_password
19 | gem "bcrypt", "~> 3.1.7"
20 | gem "jwt", "~> 1.5.6"
21 | gem "simple_command"
22 | gem "rest-client"
23 |
24 | gem "apipie-rails", "~> 0.5.11"
25 |
26 | gem "travis", "~> 1.8", ">= 1.8.8"
27 |
28 | gem "codeclimate-test-reporter", group: :test, require: nil
29 |
30 | gem "carrierwave", "~> 1.1"
31 | gem "carrierwave-base64", "~> 2.5", ">= 2.5.3"
32 |
33 | gem "simple_token_authentication", "~> 1.0"
34 | # Use Capistrano for deployment
35 | # gem 'capistrano-rails', group: :development
36 |
37 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
38 | gem "rack-cors"
39 | gem "octokit", "~> 4.0"
40 | group :development, :test do
41 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
42 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
43 | gem "minitest", "~> 5.8", ">= 5.8.4"
44 | gem "simplecov", require: false
45 | gem "minitest-reporters"
46 | end
47 |
48 | group :development do
49 | gem "listen", ">= 3.0.5", "< 3.2"
50 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
51 | gem "spring"
52 | gem "spring-watcher-listen", "~> 2.0.0"
53 | end
54 |
55 | group :test do
56 | gem "rubocop"
57 | end
58 |
59 | group :production do
60 | gem "rails_12factor"
61 | gem "activesupport", "~> 5.1", ">= 5.1.4"
62 | end
63 |
64 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
65 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
66 |
--------------------------------------------------------------------------------
/test/models/sprint_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class SprintTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Gilberto",
7 | email: "gilbertin@teste.com",
8 | password: "1234567",
9 | password_confirmation: "1234567"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Some project description.",
15 | is_project_from_github: true,
16 | user_id: @user.id,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 |
28 | @sprint = Sprint.create(
29 | name: "Sprint 1",
30 | description: "Sprint 1 us10",
31 | initial_date: "06/10/2017",
32 | final_date: "13/10/2017",
33 | release_id: @release.id
34 | )
35 | end
36 |
37 | test "should save a valid sprint" do
38 | assert @sprint.save
39 | end
40 |
41 | test "Sprint should have a name" do
42 | @sprint.name = ""
43 | assert_not @sprint.save
44 | end
45 |
46 | test "Sprint should have a start date" do
47 | @sprint.initial_date = ""
48 | assert_not @sprint.save
49 | end
50 |
51 | test "Sprint should have an end date" do
52 | @sprint.final_date = ""
53 | assert_not @sprint.save
54 | end
55 |
56 | test "Sprint name should have more than 1 characters" do
57 | @sprint.name = "s"
58 | assert_not @sprint.save
59 | end
60 |
61 | test "Sprint name should not have more than 128 characters" do
62 | @sprint.name = "a" * 129
63 | assert_not @sprint.save
64 | end
65 |
66 | test "Sprint name should have 2 characters" do
67 | @sprint.name = "ss"
68 | assert @sprint.save
69 | end
70 |
71 | test "Sprint name should have between 2 and 128 characters" do
72 | @sprint.name = "s" * 60
73 | assert @sprint.save
74 | end
75 |
76 | test "Sprint name should have 128 characters" do
77 | @sprint.name = "s" * 128
78 | assert @sprint.save
79 | end
80 |
81 | test "should not save a sprint with end date before start date" do
82 | @sprint.initial_date = "13/10/2017"
83 | @sprint.final_date = "06/10/2017"
84 | assert_not @sprint.save
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/app/docs/grades_doc.rb:
--------------------------------------------------------------------------------
1 | module GradesDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :grade do
5 | param :weight_burndown, Float, "Grade's weight of burndown"
6 | param :weight_velocity, Float, "Grade's weight of velocity"
7 | param :weight_debts, Float, "Grade's weight of debts"
8 | param :created_at, Date, "Grade's time of creation", allow_nil: false
9 | param :updated_at, Date, "Grade's time of edition", allow_nil: false
10 | param :project_id, :number, "Project's id"
11 | end
12 |
13 | api :GET, "/projects/:project_id/grades", "Show grades for a project"
14 | error code: 401, desc: "Unauthorized"
15 | error code: 404, desc: "Not Found"
16 | error code: 500, desc: "Internal Server Error"
17 | description "Show all projects of a specific user"
18 | returns code: 200, desc: "Ok" do
19 | param_group :grade
20 | end
21 | example <<-EOS
22 | {
23 | "id": 1,
24 | "weight_burndown": 1,
25 | "weight_velocity": 1,
26 | "weight_debts": 1,
27 | "created_at": "2019-04-27T19:20:21.581Z",
28 | "updated_at": "2019-04-27T19:20:21.581Z",
29 | "project_id": 1
30 | }
31 | EOS
32 | def index
33 | end
34 |
35 | api :POST, "/projects/:project_id/grades", "Create a new grade"
36 | error code: 401, desc: "Unauthorized"
37 | error code: 404, desc: "Not Found"
38 | error code: 500, desc: "Internal Server Error"
39 | description "Create grade for a specific project"
40 | param_group :grade
41 | def create
42 | end
43 |
44 | api :PATCH, "/grades/:id", "Update a grade"
45 | error code: 401, desc: "Unauthorized"
46 | error code: 404, desc: "Not Found"
47 | error code: 500, desc: "Internal Server Error"
48 | description "Update a created grade"
49 | param_group :grade
50 | def update
51 | end
52 |
53 | api :GET, "/grades/:id", "Show a grade"
54 | error code: 401, desc: "Unauthorized"
55 | error code: 404, desc: "Not Found"
56 | error code: 500, desc: "Internal Server Error"
57 | description "Show a specific grade"
58 | returns code: 200, desc: "Ok" do
59 | param_group :grade
60 | end
61 | def show
62 | end
63 |
64 | api :DELETE, "/grades/:id", "Delete a grade"
65 | error code: 401, desc: "Unauthorized"
66 | error code: 404, desc: "Not Found"
67 | error code: 500, desc: "Internal Server Error"
68 | description "Delete a specific grade"
69 | param_group :grade
70 | def destroy
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/app/adapters/adapter.rb:
--------------------------------------------------------------------------------
1 | module Adapter
2 | class GitHubProject
3 | def initialize(request)
4 | @logged_user = AuthorizeApiRequest.call(request.headers).result
5 | @client = Octokit::Client.new(access_token: @logged_user.access_token)
6 | Octokit.auto_paginate = true
7 | @client
8 | end
9 |
10 | def get_github_user
11 | user_login = @client.user.login
12 | return user_login
13 | end
14 |
15 | def get_github_repos(user_login)
16 | @client.repositories(user_login)
17 | end
18 |
19 | def get_github_orgs(user_login)
20 | @client.organizations(user_login)
21 | end
22 |
23 | def get_github_orgs_repos(org)
24 | @client.organization_repositories(org.login)
25 | end
26 |
27 | def get_contributors(github_slug)
28 | @client.contributors(github_slug)
29 | end
30 | end
31 |
32 | class GitHubIssue
33 | def initialize(request)
34 | @logged_user = AuthorizeApiRequest.call(request.headers).result
35 | @client = Octokit::Client.new(access_token: @logged_user.access_token)
36 | @client.auto_paginate = false
37 | @client
38 | end
39 |
40 | def get_github_user
41 | user_login = @client.user.login
42 | end
43 |
44 | def list_issues(github_slug, page_number)
45 | @client.list_issues(github_slug, page: page_number, per_page: 15)
46 | end
47 |
48 | def total_issues_pages(last_page)
49 | header_response = @client.last_response.rels[:last]
50 | unless header_response.nil?
51 | number_of_pages = header_response.href.match(/page=(\d+).*$/)[1]
52 | return number_of_pages.to_i
53 | else
54 | return last_page.to_i
55 | end
56 | end
57 |
58 | def list_all_issues(github_slug)
59 | @client.list_issues(github_slug, state: "all")
60 | end
61 |
62 | def create_issue(github_slug, issue_params)
63 | @client.create_issue(github_slug, issue_params[:name], issue_params[:body])
64 | end
65 |
66 | def update_issue(github_slug, issue_params)
67 | @client.update_issue(github_slug, issue_params[:number], issue_params[:name], issue_params[:body])
68 | end
69 |
70 | def close_issue(github_slug, issue_params)
71 | @client.close_issue(github_slug, issue_params[:number])
72 | end
73 |
74 | def reopen_issue(github_slug, issue_params)
75 | @client.reopen_issue(github_slug, issue_params[:number])
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "production" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # If you are preloading your application and using Active Record, it's
36 | # recommended that you close any connections to the database before workers
37 | # are forked to prevent connection leakage.
38 | #
39 | # before_fork do
40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41 | # end
42 |
43 | # The code in the `on_worker_boot` will be called if you are using
44 | # clustered mode by specifying a number of `workers`. After each worker
45 | # process is booted, this block will be run. If you are using the `preload_app!`
46 | # option, you will want to use this block to reconnect to any threads
47 | # or connections that may have been created at application boot, as Ruby
48 | # cannot share connections between processes.
49 | #
50 | on_worker_boot do
51 | ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52 | end
53 | #
54 |
55 | # Allow puma to be restarted by `rails restart` command.
56 | plugin :tmp_restart
57 |
--------------------------------------------------------------------------------
/test/models/release_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ReleaseTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "Ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Some project description.",
15 | user_id: @user.id,
16 | is_project_from_github: true,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 | end
28 |
29 | test "should save a valid release" do
30 | assert @release.save
31 | end
32 |
33 | test "should not save a release without a project" do
34 | @release.project_id = nil
35 | assert_not @release.save
36 | end
37 |
38 | test "should not save a release without name" do
39 | @release.name = ""
40 | assert_not @release.save
41 | end
42 |
43 | test "should with name smaller than 2 characters" do
44 | @release.name = "R"
45 | assert_not @release.save
46 | end
47 |
48 | test "should with name greater than 80 characters" do
49 | @release.name = "R" * 81
50 | assert_not @release.save
51 | end
52 |
53 | test "should not save a release with initial date after final date" do
54 | @release.initial_date = "01/01/2017"
55 | @release.final_date = "01/01/2016"
56 | assert_not @release.save
57 | end
58 |
59 | test "should not save a release without initial date" do
60 | @release.initial_date = ""
61 | assert_not @release.save
62 | end
63 |
64 | test "should not save a release without final date" do
65 | @release.final_date = ""
66 | assert_not @release.save
67 | end
68 |
69 | test "should not save a release with an invalid initial date" do
70 | @release.initial_date = "50/50/2000"
71 | assert_not @release.save
72 | end
73 |
74 | test "should not save a release with an invalid final date" do
75 | @release.final_date = "50/50/2000"
76 | assert_not @release.save
77 | end
78 |
79 | test "should save a release with a blank description" do
80 | @release.description = ""
81 | assert @release.save
82 | end
83 |
84 | test "should not save a release with description greater than 256 characters" do
85 | @release.description = "A" * 257
86 | assert_not @release.save
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | require "rest-client"
2 | class UsersController < ApplicationController
3 | include ValidationsHelper
4 | include UsersDoc
5 |
6 | skip_before_action :authenticate_request, only: [:create, :all]
7 |
8 | before_action only: [:show, :update, :destroy] do
9 | set_user
10 | validate_user(:id, 0)
11 | end
12 |
13 | # GET /users/1
14 | def show
15 | @user = User.find(params[:id].to_i)
16 | render json: @user
17 | end
18 |
19 | # POST /users
20 | def create
21 | @user = User.new(user_params)
22 | if @user.save
23 | @token = AuthenticateUser.call(@user.email, @user.password)
24 |
25 | @result = { token: @token.result }
26 |
27 | response.set_header("auth_token", @token.result)
28 | render json: @result, status: :created
29 | else
30 | render json: @user.errors, status: :unprocessable_entity
31 | end
32 | end
33 |
34 | def request_github_token
35 | code_token = params[:code]
36 |
37 | result = RestClient.post(
38 | "https://github.com/login/oauth/access_token",
39 | client_id: ENV["CLIENT_ID"],
40 | client_secret: ENV["CLIENT_SECRET"],
41 | code: code_token,
42 | accept: :json
43 | )
44 |
45 |
46 | access_token = result.split("&")[0].split("=")[1]
47 |
48 | unless access_token == "bad_verification_code" || access_token == nil
49 | @user = User.find(params[:id])
50 | @user.access_token = access_token
51 |
52 | if @user.update_column(:access_token, access_token)
53 | render json: @user
54 | else
55 | render json: @user.errors, status: :unprocessable_entity
56 | end
57 | else
58 | render json: result, status: :bad_request
59 | end
60 | end
61 |
62 | def remove_github_token
63 | @user = User.find(params[:id])
64 | @user.access_token = nil
65 | if @user.update_column(:access_token, nil)
66 | render json: @user
67 | else
68 | render json: @user.errors, status: :unprocessable_entity
69 | end
70 | end
71 |
72 | # PATCH/PUT /users/1
73 | def update
74 | if @user.update(user_params)
75 | render json: @user
76 | else
77 | render json: @user.errors, status: :unprocessable_entity
78 | end
79 | end
80 |
81 | # DELETE /users/1
82 | def destroy
83 | @user.destroy
84 | render json: { status: 200, message: "User deleted successfully" }.to_json
85 | end
86 |
87 | private
88 | def set_user
89 | @user = User.find(params[:id])
90 | end
91 |
92 | def user_params
93 | params.require(:user).permit(:name, :email, :password, :password_confirmation)
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/test/models/retrospective_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RetrospectiveTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Some project description.",
15 | user_id: @user.id,
16 | is_project_from_github: true,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 |
28 | @sprint = Sprint.create(
29 | name: "Sprint1",
30 | description: "Some sprint",
31 | initial_date: "23/04/1993",
32 | final_date: "23/04/2003",
33 | release_id: @release.id
34 | )
35 |
36 | @retrospective = Retrospective.create(
37 | sprint_report: "Sprint description",
38 | positive_points: ["Very good"],
39 | negative_points: ["No tests"],
40 | improvements: ["Improve front-end"],
41 | sprint_id: @sprint.id
42 | )
43 | end
44 |
45 | test "should save a retrospective" do
46 | assert @retrospective.save
47 | end
48 |
49 | test "Restrospective should have sprint_report less than 1500 characters" do
50 | @retrospective.sprint_report = "a" * 1501
51 | assert_not @retrospective.save
52 | end
53 |
54 | test "Restrospective should have sprint_report 600 characters" do
55 | @retrospective.sprint_report = "a" * 600
56 | assert @retrospective.save
57 | end
58 |
59 | test "Restrospective should have positive_points less than 500 characters" do
60 | @retrospective.positive_points = "b" * 501
61 | assert_not @retrospective.save
62 | end
63 |
64 | test "Restrospective should have positive_points" do
65 | @retrospective.positive_points = ""
66 | assert_not @retrospective.save
67 | end
68 |
69 | test "Restrospective should have negative_points less than 500 characters" do
70 | @retrospective.negative_points = "c" * 501
71 | assert_not @retrospective.save
72 | end
73 |
74 | test "Restrospective should have negative_points" do
75 | @retrospective.negative_points = ""
76 | assert_not @retrospective.save
77 | end
78 |
79 | test "Restrospective should have improvements less than 500 characters" do
80 | @retrospective.improvements = "d" * 501
81 | assert_not @retrospective.save
82 | end
83 |
84 | test "Restrospective should have improvements" do
85 | @retrospective.improvements = ""
86 | assert_not @retrospective.save
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/app/docs/revisions_doc.rb:
--------------------------------------------------------------------------------
1 | module RevisionsDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :revision do
5 | param :done_report, Array, of: String, desc: "Sprint revision's done report"
6 | param :undone_report, Array, of: String, desc: "Sprint revision's undone report"
7 | param :created_at, Date, "Revision's time of creation", allow_nil: false
8 | param :updated_at, Date, "Revision's time of edition", allow_nil: false
9 | param :sprint_id, :number, "Id of sprint revised"
10 | end
11 |
12 | api :GET, "/sprints/:sprint_id/revisions", "Show revisions for a sprint"
13 | error code: 401, desc: "Unauthorized"
14 | error code: 404, desc: "Not Found"
15 | error code: 500, desc: "Internal Server Error"
16 | description "Show all revisions of a specific sprint"
17 | returns code: 200, desc: "Ok" do
18 | param_group :revision
19 | end
20 | example <<-EOS
21 | {
22 | "id": 1,
23 | "done_report": [
24 | "Story US11 was done."
25 | ],
26 | "undone_report": [
27 | "Story US21 was not done."
28 | ],
29 | "created_at": "2019-04-11T15:42:35.826Z",
30 | "updated_at": "2019-04-11T15:42:35.826Z",
31 | "sprint_id": 1
32 | }
33 | EOS
34 | def index
35 | end
36 |
37 | api :POST, "/sprints/:sprint_id/revisions", "Create a revision"
38 | error code: 401, desc: "Unauthorized"
39 | error code: 404, desc: "Not Found"
40 | error code: 500, desc: "Internal Server Error"
41 | description "Create revision for a specific sprint"
42 | param_group :revision
43 | def create
44 | end
45 |
46 | api :GET, "/revisions/:id", "Show a revision"
47 | error code: 401, desc: "Unauthorized"
48 | error code: 404, desc: "Not Found"
49 | error code: 500, desc: "Internal Server Error"
50 | description "Show a revision of a specific sprint"
51 | returns code: 200, desc: "Ok" do
52 | param_group :revision
53 | end
54 | example <<-EOS
55 | {
56 | "id": 2,
57 | "done_report": [
58 | "Story US12 was done."
59 | ],
60 | "undone_report": [
61 | "Story US22 was not done."
62 | ],
63 | "created_at": "2019-04-11T15:42:35.851Z",
64 | "updated_at": "2019-04-11T15:42:35.851Z",
65 | "sprint_id": 2
66 | }
67 | EOS
68 | def show
69 | end
70 |
71 | api :PATCH, "/revisions/:id", "Update a revision"
72 | error code: 401, desc: "Unauthorized"
73 | error code: 404, desc: "Not Found"
74 | error code: 500, desc: "Internal Server Error"
75 | description "Update revision for a specific sprint"
76 | param_group :revision
77 | def update
78 | end
79 |
80 | api :DELETE, "/revisions/:id", "Delete a revision"
81 | error code: 401, desc: "Unauthorized"
82 | error code: 404, desc: "Not Found"
83 | error code: 500, desc: "Internal Server Error"
84 | description "Delete revision for a specific sprint"
85 | param_group :revision
86 | def destroy
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | @echo "*********************************\n"
3 | @echo "Lifting up the whole environment!\n"
4 | @echo "*********************************\n"
5 | @docker-compose up
6 |
7 | quiet-run:
8 | @echo "*********************************\n"
9 | @echo "Lifting up the whole environment!\n"
10 | @echo "*********************************\n"
11 | @docker-compose up --detach
12 |
13 | run-db:
14 | @echo "************************\n"
15 | @echo "Lifting up the database!\n"
16 | @echo "************************\n"
17 | @docker-compose up falko-database
18 |
19 | run-api:
20 | @echo "*************************\n"
21 | @echo "Lifting up the api!\n"
22 | @echo "*************************\n"
23 | @docker-compose up falko-server
24 |
25 | console:
26 | @echo "*************************\n"
27 | @echo "Entering the rails console!\n"
28 | @echo "*************************\n"
29 | @docker-compose falko-server rails console
30 |
31 | create-db:
32 | @echo "*************************\n"
33 | @echo "Creating rails database!\n"
34 | @echo "*************************\n"
35 | @docker-compose exec falko-server bundle exec rails db:create
36 |
37 | seed:
38 | @echo "*************************\n"
39 | @echo "Executing database seed scripts\n"
40 | @echo "*************************\n"
41 | @docker-compose exec falko-server bundle exec rails db:seed
42 |
43 | down:
44 | @echo "******************************\n"
45 | @echo "Dropping down the environment!\n"
46 | @echo "******************************\n"
47 | @docker-compose down
48 |
49 | ps:
50 | @echo "************************\n"
51 | @echo "Listing running services\n"
52 | @echo "************************\n"
53 | @docker-compose ps
54 |
55 | migrate:
56 | @echo "******************\n"
57 | @echo "Migrating database\n"
58 | @echo "******************\n"
59 | @docker-compose exec falko-server bundle exec rails db:migrate
60 |
61 | test:
62 | @echo "***************************\n"
63 | @echo "Executing all api tests\n"
64 | @echo "***************************\n"
65 | @docker-compose exec falko-server bundle exec rails test
66 |
67 | rm-network:
68 | @echo "**********************************\n"
69 | @echo "Removing all networks!\n"
70 | @echo "**********************************\n"
71 | @docker network rm $(sudo docker network ls -q)
72 |
73 | rm-flk-network:
74 | @echo "**********************************\n"
75 | @echo "Removing Falko network!\n"
76 | @echo "**********************************\n"
77 | @docker network rm falko-backend
78 |
79 | rm-volume:
80 | @echo "*********************\n"
81 | @echo "Removing all volumes!\n"
82 | @echo "*********************\n"
83 | @docker volume rm $(sudo docker volume ls -q)
84 |
85 | .PHONY: no_targets__ list
86 | no_targets__:
87 | list:
88 | @sh -c "$(MAKE) -p no_targets__ -prRn -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {split(\$$1,A,/ /);for(i in A)print A[i]}' | grep -v '__\$$' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'"
89 |
--------------------------------------------------------------------------------
/app/controllers/projects_controller.rb:
--------------------------------------------------------------------------------
1 | require "rest-client"
2 |
3 | class ProjectsController < ApplicationController
4 | include ValidationsHelper
5 | include ProjectsDoc
6 |
7 | before_action :set_project, only: [:destroy, :show, :get_contributors]
8 |
9 | before_action only: [:index, :create] do
10 | validate_user(0, :user_id)
11 | end
12 |
13 | before_action only: [:show, :update, :destroy] do
14 | validate_project(:id, 0)
15 | end
16 |
17 | def index
18 | @projects = User.find(params[:user_id]).projects
19 | render json: @projects
20 | end
21 |
22 | def github_projects_list
23 | client = Adapter::GitHubProject.new(request)
24 |
25 | user_login = client.get_github_user
26 |
27 | @form_params_user = { user: [] }
28 | @form_params_user[:user].push(login: user_login)
29 |
30 | user_repos = []
31 |
32 | (client.get_github_repos(user_login)).each do |repo|
33 | user_repos.push(repo.name)
34 | end
35 |
36 | @form_params_user[:user].push(repos: user_repos)
37 |
38 | @form_params_orgs = { orgs: [] }
39 |
40 | (client.get_github_orgs(user_login)).each do |org|
41 | repos_names = []
42 | (client.get_github_orgs_repos(org)).each do |repo|
43 | repos_names.push(repo.name)
44 | end
45 | @form_params_orgs[:orgs].push(name: org.login, repos: repos_names)
46 | end
47 |
48 | @form_params_user_orgs = @form_params_orgs.merge(@form_params_user)
49 |
50 | render json: @form_params_user_orgs
51 | end
52 |
53 | def show
54 | render json: @project
55 | end
56 |
57 | def create
58 | @project = Project.create(project_params)
59 | @project.user_id = @current_user.id
60 |
61 | if @project.save
62 | render json: @project, status: :created
63 | else
64 | render json: @project.errors, status: :unprocessable_entity
65 | end
66 | end
67 |
68 | def update
69 | if @project.update(project_params)
70 | render json: @project
71 | else
72 | render json: @project.errors, status: :unprocessable_entity
73 | end
74 | end
75 |
76 | def destroy
77 | @project.destroy
78 | end
79 |
80 | def get_contributors
81 | client = Adapter::GitHubProject.new(request)
82 |
83 | contributors = []
84 |
85 | (client.get_contributors(@project.github_slug)).each do |contributor|
86 | contributors.push(contributor.login)
87 | end
88 |
89 | render json: contributors, status: :ok
90 | end
91 |
92 | private
93 | def set_project
94 | begin
95 | @project = Project.find(params[:id])
96 | rescue ActiveRecord::RecordNotFound
97 | render json: { errors: "Project not found" }, status: :not_found
98 | end
99 | end
100 |
101 | def project_params
102 | params.require(:project).permit(:name, :description, :user_id, :is_project_from_github, :github_slug, :is_scoring)
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Gilberto",
7 | email: "gilbertin@teste.com",
8 | password: "1234567",
9 | password_confirmation: "1234567"
10 | )
11 | end
12 |
13 | test "should save a valid user" do
14 | assert @user.save
15 | end
16 |
17 | test "User should have a name" do
18 | @user.name = ""
19 | assert_not @user.save
20 | end
21 |
22 | test "User name should have more than 2 characters" do
23 | @user.name = "gi"
24 | assert_not @user.save
25 | end
26 |
27 | test "User name should not have more than 80 characters" do
28 | @user.name = "a" * 81
29 | assert_not @user.save
30 | end
31 |
32 | test "The number of characters in user name is between 3 and 80" do
33 | @user.name = "oda"
34 | assert @user.save
35 | @user.name = "a" * 80
36 | assert @user.save
37 | end
38 |
39 | test "User should have a email" do
40 | @user.email = ""
41 | assert_not @user.save
42 | end
43 |
44 | test "should note save user with duplicate emails" do
45 | duplicate_user = @user.dup
46 | duplicate_user.email = @user.email
47 | @user.save
48 | assert_not duplicate_user.save
49 | end
50 |
51 | test "should save email with right validation format" do
52 | valid_addresses = %w[testing@example.com user@foo.COM A_MEM-BER@foo.bar.org
53 | first.last@foo.jp alice+bob@baz.cn]
54 | valid_addresses.each do |valid_address|
55 | @user.email = valid_address
56 | assert @user.save, "#{valid_address.inspect} should be valid"
57 | end
58 | end
59 |
60 | test "should not save user with email in an invalid format" do
61 | invalid_addresses = %w[testing@example,com user_at_foo.org user.name@example.
62 | foo@bar_baz.com foo@bar+baz.com]
63 | invalid_addresses.each do |invalid_address|
64 | @user.email = invalid_address
65 | assert_not @user.save, "#{invalid_address.inspect} should be invalid"
66 | end
67 | end
68 |
69 | test "User should not have a blank password" do
70 | @user_wrong = User.create(
71 | name: "Gilberto",
72 | email: "gilbertin@teste.com",
73 | password: "",
74 | password_confirmation: "",
75 | )
76 |
77 | assert_not @user_wrong.save
78 | end
79 |
80 | test "User password should not have less than 6 characters" do
81 | @user_wrong = User.create(
82 | name: "Gilberto",
83 | email: "gilbertin@teste.com",
84 | password: "g",
85 | password_confirmation: "g",
86 | )
87 |
88 | assert_not @user_wrong.save
89 | end
90 |
91 | test "User password should not have more than 80 characters" do
92 | @user_wrong = User.create(
93 | name: "Gilberto",
94 | email: "gilbertin@teste.com",
95 | password: "g" * 81,
96 | password_confirmation: "g" * 81,
97 | )
98 |
99 | assert_not @user_wrong.save
100 | end
101 |
102 | test "The number of characters in user password is between 6 and 80" do
103 | assert @user.save
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fritzo@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/app/docs/retrospectives_doc.rb:
--------------------------------------------------------------------------------
1 | module RetrospectivesDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :retrospective do
5 | param :sprint_report, String, "Sprint reports"
6 | param :positive_points, Array, of: String, desc: "Sprint positive points", default_value: []
7 | param :negative_points, Array, of: String, desc: "Sprint negative points", default_value: []
8 | param :improvements, Array, of: String, desc: "Sprint improvements", default_value: []
9 | param :created_at, Date, "Sprint's time of creation", allow_nil: false
10 | param :updated_at, Date, "Sprint's time of edition", allow_nil: false
11 | param :sprint_id, :number, "Sprint's id"
12 | end
13 |
14 | api :GET, "/sprints/:sprint_id/retrospectives", "Show retrospectives for a sprint"
15 | error code: 401, desc: "Unauthorized"
16 | error code: 404, desc: "Not Found"
17 | error code: 500, desc: "Internal Server Error"
18 | description "Show all retrospectives of a specific sprint"
19 | returns code: 200, desc: "Ok" do
20 | param_group :retrospective
21 | end
22 | example <<-EOS
23 | {
24 | "id": 1,
25 | "sprint_report": "Sprint 1",
26 | "positive_points": [
27 | "Very good"
28 | ],
29 | "negative_points": [
30 | "No tests"
31 | ],
32 | "improvements": [
33 | "Improve front-end"
34 | ],
35 | "created_at": "2019-04-11T15:42:36.102Z",
36 | "updated_at": "2019-04-11T15:42:36.102Z",
37 | "sprint_id": 1
38 | }
39 | EOS
40 | def index
41 | end
42 |
43 | api :POST, "/sprints/:sprint_id/retrospectives", "Create a retrospective"
44 | error code: 401, desc: "Unauthorized"
45 | error code: 404, desc: "Not Found"
46 | error code: 500, desc: "Internal Server Error"
47 | description "Create retrospective of a specific sprint"
48 | param_group :retrospective
49 | def create
50 | end
51 |
52 | api :GET, "/retrospectives/:id", "Show a retrospective"
53 | error code: 401, desc: "Unauthorized"
54 | error code: 404, desc: "Not Found"
55 | error code: 500, desc: "Internal Server Error"
56 | description "Show a specific retrospective of a sprint"
57 | returns code: 200, desc: "Ok" do
58 | param_group :retrospective
59 | end
60 | example <<-EOS
61 | {
62 | "id": 1,
63 | "sprint_report": "Sprint 1",
64 | "positive_points": [
65 | "Very good"
66 | ],
67 | "negative_points": [
68 | "No tests"
69 | ],
70 | "improvements": [
71 | "Improve front-end"
72 | ],
73 | "created_at": "2019-04-11T15:42:36.102Z",
74 | "updated_at": "2019-04-11T15:42:36.102Z",
75 | "sprint_id": 1
76 | }
77 | EOS
78 | def show
79 | end
80 |
81 | api :PATCH, "/retrospectives/:id", "Update a retrospective"
82 | error code: 401, desc: "Unauthorized"
83 | error code: 404, desc: "Not Found"
84 | error code: 500, desc: "Internal Server Error"
85 | description "Update retrospective of a specific sprint"
86 | param_group :retrospective
87 | def update
88 | end
89 |
90 | api :DELETE, "/retrospectives/:id", "Delete a retrospective"
91 | error code: 401, desc: "Unauthorized"
92 | error code: 404, desc: "Not Found"
93 | error code: 500, desc: "Internal Server Error"
94 | description "Delete retrospective of a specific sprint"
95 | param_group :retrospective
96 | def destroy
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/test/models/story_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class StoryTest < ActiveSupport::TestCase
4 | def setup
5 | @user = User.create(
6 | name: "Gilberto",
7 | email: "gilbertin@teste.com",
8 | password: "1234567",
9 | password_confirmation: "1234567"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Some project description.",
15 | user_id: @user.id,
16 | is_project_from_github: true,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "Release 1",
22 | description: "First Release.",
23 | initial_date: "01/01/2017",
24 | final_date: "02/02/2019",
25 | project_id: @project.id
26 | )
27 |
28 | @sprint = Sprint.create(
29 | name: "Sprint 1",
30 | description: "Sprint 1 us10",
31 | initial_date: "06/10/2017",
32 | final_date: "13/10/2017",
33 | release_id: @release.id
34 | )
35 |
36 | @story = Story.create(
37 | name: "Story 1",
38 | description: "Story 1 us14",
39 | assign: "Lucas",
40 | pipeline: "in progress",
41 | initial_date: "01/01/2017",
42 | issue_number: "10",
43 | sprint_id: @sprint.id
44 | )
45 | end
46 |
47 | test "should save a valid story" do
48 | assert @story.save
49 | end
50 |
51 | test "Story should have a name" do
52 | @story.name = ""
53 | assert_not @story.save
54 | end
55 |
56 | test "Story should have a start date" do
57 | @story.initial_date = ""
58 | assert_not @story.save
59 | end
60 |
61 | test "Story name should have more than 1 characters" do
62 | @story.name = "s"
63 | assert_not @story.save
64 | end
65 |
66 | test "Story name should not have more than 128 characters" do
67 | @story.name = "a" * 129
68 | assert_not @story.save
69 | end
70 |
71 | test "The number of characters in story name is between 2 and 128" do
72 | @story.name = "ss"
73 | assert @story.save
74 |
75 | @story.name = "s" * 60
76 | assert @story.save
77 |
78 | @story.name = "s" * 128
79 | assert @story.save
80 | end
81 |
82 | test "Story assign should have more than 1 characters" do
83 | @story.assign = "s"
84 | assert_not @story.save
85 | end
86 |
87 | test "Story name should not have more than 32 characters" do
88 | @story.assign = "a" * 33
89 | assert_not @story.save
90 | end
91 |
92 | test "The number of characters in story assign is between 2 and 32" do
93 | @story.assign = "ss"
94 | assert @story.save
95 |
96 | @story.assign = "s" * 16
97 | assert @story.save
98 |
99 | @story.assign = "s" * 32
100 | assert @story.save
101 | end
102 |
103 | test "Story pipeline should have more than 3 characters" do
104 | @story.pipeline = "s"
105 | assert_not @story.save
106 | end
107 |
108 | test "Story name should not have more than 16 characters" do
109 | @story.pipeline = "a" * 17
110 | assert_not @story.save
111 | end
112 |
113 | test "The number of characters in story pipeline is between 4 and 16" do
114 | @story.pipeline = "ssss"
115 | assert @story.save
116 |
117 | @story.pipeline = "s" * 5
118 | assert @story.save
119 |
120 | @story.pipeline = "s" * 16
121 | assert @story.save
122 | end
123 | end
124 |
--------------------------------------------------------------------------------
/app/controllers/stories_controller.rb:
--------------------------------------------------------------------------------
1 | class StoriesController < ApplicationController
2 | include ValidationsHelper
3 | include StoriesDoc
4 |
5 | before_action :set_story, only: [:show, :update, :destroy]
6 |
7 | before_action only: [:index, :create] do
8 | validate_sprint(0, :sprint_id)
9 | end
10 |
11 |
12 | before_action only: [:show, :edit, :update, :destroy] do
13 | validate_sprint_dependencies(:id, "story")
14 | end
15 |
16 | def index
17 | # @sprint used from validate_sprint(0, :sprint_id)
18 | @stories = @sprint.stories.reverse
19 | render json: @stories
20 | end
21 |
22 | def to_do_list
23 | @sprint = Sprint.find(params[:id])
24 |
25 | @stories = @sprint.stories.select { |story| story.pipeline == "To Do" }
26 |
27 | render json: format_json_output(@stories)
28 | end
29 |
30 | def doing_list
31 | @sprint = Sprint.find(params[:id])
32 |
33 | @stories = @sprint.stories.select { |story| story.pipeline == "Doing" }
34 |
35 | render json: format_json_output(@stories)
36 | end
37 |
38 | def done_list
39 | @sprint = Sprint.find(params[:id])
40 |
41 | @stories = @sprint.stories.select { |story| story.pipeline == "Done" }
42 |
43 | render json: format_json_output(@stories)
44 | end
45 |
46 | def show
47 | @story = Story.find(params[:id])
48 | render json: @story
49 | end
50 |
51 | def create
52 | @story = Story.create(story_params)
53 | @story.sprint = @sprint
54 | if validate_stories(@story.story_points, 0, :sprint_id)
55 | if @story.save
56 | render json: @story, status: :created
57 | else
58 | render json: @story.errors, status: :unprocessable_entity
59 | end
60 | else
61 | render json: { error: "Story points have to be set" }, status: :unprocessable_entity
62 | end
63 | end
64 |
65 | def update
66 | if story_params[:pipeline] == "Done"
67 | @story.final_date = Date.today
68 | end
69 | if @story.update(story_params)
70 | render json: @story
71 | else
72 | render json: @story.errors, status: :unprocessable_entity
73 | end
74 | end
75 |
76 | def destroy
77 | @story.destroy
78 | end
79 |
80 | private
81 | def set_story
82 | @story = Story.find(params[:id])
83 | end
84 |
85 | def format_json_output(stories)
86 | form_params = { stories_infos: [] }
87 |
88 | if stories != nil
89 | if stories.kind_of?(Array)
90 | stories.each do |story|
91 | form_params[:stories_infos].push(name: story.name, description: story.description, assign: story.assign, pipeline: story.pipeline, initial_date: story.initial_date, story_points: story.story_points, final_date: story.final_date, issue_number: story.issue_number, id: story.id)
92 | end
93 | else
94 | form_params[:stories_infos].push(name: stories.name, description: stories.description, assign: stories.assign, pipeline: stories.pipeline, initial_date: stories.initial_date, story_points: stories.story_points, final_date: stories.final_date, issue_number: stories.issue_number, id: stories.id)
95 | end
96 | end
97 |
98 | form_params
99 | end
100 |
101 | def story_params
102 | params.require(:story).permit(:name, :description, :assign, :pipeline, :initial_date, :story_points, :final_date, :issue_number, :issue_id)
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/app/docs/releases_doc.rb:
--------------------------------------------------------------------------------
1 | module ReleasesDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :release do
5 | param :name, String, "Release's name"
6 | param :description, String, "Release's description"
7 | param :amount_of_sprints, Integer, "Release's number of sprints"
8 | param :created_at, Date, "Release's time of creation", allow_nil: false
9 | param :updated_at, Date, "Release's time of edition", allow_nil: false
10 | param :project_id, :number, "Project's id"
11 | param :initial_date, Date, "Release's initial date"
12 | param :final_date, Date, "Release's final date"
13 | end
14 |
15 | api :GET, "/projects/:project_id/releases", "Show releases for a project"
16 | error code: 401, desc: "Unauthorized"
17 | error code: 404, desc: "Not Found"
18 | error code: 500, desc: "Internal Server Error"
19 | description "Show all releases of a specific project"
20 | returns code: 200, desc: "Ok" do
21 | param_group :release
22 | end
23 | example <<-EOS
24 | [
25 | {
26 | "id": 2,
27 | "name": "R2",
28 | "description": "Agile Release",
29 | "amount_of_sprints": 0,
30 | "created_at": "2019-04-11T15:42:34.118Z",
31 | "updated_at": "2019-04-27T20:55:28.228Z",
32 | "project_id": 1,
33 | "initial_date": "2016-01-01",
34 | "final_date": "2016-12-01"
35 | },
36 | {
37 | "id": 1,
38 | "name": "R1",
39 | "description": "RUP Release",
40 | "amount_of_sprints": 2,
41 | "created_at": "2019-04-11T15:42:34.101Z",
42 | "updated_at": "2019-04-27T20:55:28.231Z",
43 | "project_id": 1,
44 | "initial_date": "2016-01-01",
45 | "final_date": "2016-10-01"
46 | }
47 | ]
48 | EOS
49 | def index
50 | end
51 |
52 | api :GET, "releases/:id", "Show a release for a project"
53 | error code: 401, desc: "Unauthorized"
54 | error code: 404, desc: "Not Found"
55 | error code: 500, desc: "Internal Server Error"
56 | description "Show a specific release of a project"
57 | returns code: 200, desc: "Ok" do
58 | param_group :release
59 | end
60 | example <<-EOS
61 | {
62 | "project_id": 1,
63 | "id": 1,
64 | "amount_of_sprints": 2,
65 | "name": "R1",
66 | "description": "RUP Release",
67 | "initial_date": "2016-01-01",
68 | "final_date": "2016-10-01",
69 | "created_at": "2019-04-11T15:42:34.101Z",
70 | "updated_at": "2019-04-27T20:55:28.231Z"
71 | }
72 | EOS
73 | def show
74 | end
75 |
76 | api :POST, "/projects/:project_id/releases", "Create a release"
77 | error code: 401, desc: "Unauthorized"
78 | error code: 404, desc: "Not Found"
79 | error code: 500, desc: "Internal Server Error"
80 | description "Create release of a project"
81 | param_group :release
82 | def create
83 | end
84 |
85 | api :PATCH, "/releases/:id", "Update a release"
86 | error code: 401, desc: "Unauthorized"
87 | error code: 404, desc: "Not Found"
88 | error code: 500, desc: "Internal Server Error"
89 | description "Update release of a project"
90 | param_group :release
91 | def update
92 | end
93 |
94 | api :DELETE, "/releases/:id", "Delete a release"
95 | error code: 401, desc: "Unauthorized"
96 | error code: 404, desc: "Not Found"
97 | error code: 500, desc: "Internal Server Error"
98 | description "Delete release of a project"
99 | param_group :release
100 | def destroy
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/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 threaded 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 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
19 | # `config/secrets.yml.key`.
20 | config.read_encrypted_secrets = true
21 |
22 | # Disable serving static files from the `/public` folder by default since
23 | # Apache or NGINX already handles this.
24 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
25 |
26 |
27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
28 | # config.action_controller.asset_host = 'http://assets.example.com'
29 |
30 | # Specifies the header that your server uses for sending files.
31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
33 |
34 |
35 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
36 | # config.force_ssl = true
37 |
38 | # Use the lowest log level to ensure availability of diagnostic information
39 | # when problems arise.
40 | config.log_level = :debug
41 |
42 | # Prepend all log lines with the following tags.
43 | config.log_tags = [ :request_id ]
44 |
45 | # Use a different cache store in production.
46 | # config.cache_store = :mem_cache_store
47 |
48 | # Use a real queuing backend for Active Job (and separate queues per environment)
49 | # config.active_job.queue_adapter = :resque
50 | # config.active_job.queue_name_prefix = "Falko-2017_2_#{Rails.env}"
51 | config.action_mailer.perform_caching = false
52 |
53 | config.secret_key_base = ENV["SECRET_KEY_BASE"]
54 |
55 | # Ignore bad email addresses and do not raise email delivery errors.
56 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
57 | # config.action_mailer.raise_delivery_errors = false
58 |
59 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
60 | # the I18n.default_locale when a translation cannot be found).
61 | config.i18n.fallbacks = true
62 |
63 | # Send deprecation notices to registered listeners.
64 | config.active_support.deprecation = :notify
65 |
66 | # Use default logging formatter so that PID and timestamp are not suppressed.
67 | config.log_formatter = ::Logger::Formatter.new
68 |
69 | # Use a different logger for distributed setups.
70 | # require 'syslog/logger'
71 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
72 |
73 | if ENV["RAILS_LOG_TO_STDOUT"].present?
74 | logger = ActiveSupport::Logger.new(STDOUT)
75 | logger.formatter = config.log_formatter
76 | config.logger = ActiveSupport::TaggedLogging.new(logger)
77 | end
78 |
79 | # Do not dump schema after migrations.
80 | config.active_record.dump_schema_after_migration = false
81 | end
82 |
--------------------------------------------------------------------------------
/app/controllers/sprints_controller.rb:
--------------------------------------------------------------------------------
1 | class SprintsController < ApplicationController
2 | include ValidationsHelper
3 | include VelocityHelper
4 | include BurndownHelper
5 | include MetricHelper
6 | include SprintsDoc
7 |
8 | before_action :set_sprint, only: [:show, :update, :destroy, :get_burndown]
9 |
10 | before_action only: [:index, :create] do
11 | validate_release(0, :release_id)
12 | end
13 |
14 | before_action only: [:show, :update, :destroy, :get_velocity, :get_metrics] do
15 | validate_sprint(:id, 0)
16 | end
17 |
18 | def index
19 | @sprints = @release.sprints.reverse
20 | render json: @sprints
21 | end
22 |
23 | def show
24 | render json: @sprint
25 | end
26 |
27 | def create
28 | @sprint = Sprint.new(sprint_params)
29 | if validate_sprints_date("sprint", sprint_params)
30 | @sprint.release = @release
31 | update_amount_of_sprints
32 | if @sprint.save
33 | render json: @sprint, status: :created
34 | @sprint.release = @release
35 | update_amount_of_sprints
36 | else
37 | render json: @sprint.errors, status: :unprocessable_entity
38 | end
39 | else
40 | render json: { error: "Can not create a sprint outside the range of a release" }, status: :unprocessable_entity
41 | end
42 | end
43 |
44 | def update
45 | if validate_sprints_date("sprint", sprint_params)
46 | if @sprint.update(sprint_params)
47 | render json: @sprint
48 | else
49 | render json: @sprint.errors, status: :unprocessable_entity
50 | end
51 | else
52 | render json: { error: "Can not create a story outside the range of a sprint" }, status: :unprocessable_entity
53 | end
54 | end
55 |
56 | def destroy
57 | @sprint.destroy
58 | update_amount_of_sprints
59 | end
60 |
61 | def get_velocity
62 | release = @sprint.release
63 | if release.project.is_scoring == true
64 | velocity = get_sprints_informations(release.sprints, @sprint)
65 |
66 | render json: velocity
67 | else
68 | render json: { error: "The Velocity is only available in projects that use Story Points" }, status: :unprocessable_entity
69 | end
70 | end
71 |
72 | def get_burndown
73 | project = @sprint.release.project
74 | if project.is_scoring == true
75 | burned_stories = {}
76 | coordenates = []
77 | date_axis = []
78 | points_axis = []
79 | ideal_line = []
80 |
81 | total_points = get_total_points(@sprint)
82 | burned_stories = get_burned_points(@sprint, burned_stories)
83 |
84 | range_dates = (@sprint.initial_date .. @sprint.final_date)
85 |
86 | set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points)
87 | days_of_sprint = date_axis.length - 1
88 | set_ideal_line(days_of_sprint, ideal_line, total_points)
89 |
90 | coordenates = { x: date_axis, y: points_axis, ideal_line: ideal_line }
91 |
92 | burned_stories = burned_stories.sort_by { |key, value| key }
93 |
94 | render json: coordenates
95 | else
96 | render json: { error: "The Burndown Chart is only available in projects that use Story Points" }, status: :unprocessable_entity
97 | end
98 | end
99 |
100 | private
101 | def set_sprint
102 | @sprint = Sprint.find(params[:id])
103 | end
104 |
105 | def sprint_params
106 | params.require(:sprint).permit(:name, :description, :initial_date, :final_date, :release_id)
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | TargetRubyVersion: 2.4
3 | # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
4 | # to ignore them, so only the ones explicitly set in this file are enabled.
5 | DisabledByDefault: true
6 | Exclude:
7 | - '**/templates/**/*'
8 | - '**/vendor/**/*'
9 | - '**/vendor/**/.*'
10 | - '**/node_modules/**/*'
11 | - 'actionpack/lib/action_dispatch/journey/parser.rb'
12 |
13 | # Prefer &&/|| over and/or.
14 | Style/AndOr:
15 | Enabled: true
16 |
17 | # Do not use braces for hash literals when they are the last argument of a
18 | # method call.
19 | Style/BracesAroundHashParameters:
20 | Enabled: true
21 | EnforcedStyle: context_dependent
22 |
23 | # Align `when` with `case`.
24 | Layout/CaseIndentation:
25 | Enabled: true
26 |
27 | # Align comments with method definitions.
28 | Layout/CommentIndentation:
29 | Enabled: true
30 |
31 | Layout/EmptyLineAfterMagicComment:
32 | Enabled: true
33 |
34 | # In a regular class definition, no empty lines around the body.
35 | Layout/EmptyLinesAroundClassBody:
36 | Enabled: true
37 |
38 | # In a regular method definition, no empty lines around the body.
39 | Layout/EmptyLinesAroundMethodBody:
40 | Enabled: true
41 |
42 | # In a regular module definition, no empty lines around the body.
43 | Layout/EmptyLinesAroundModuleBody:
44 | Enabled: true
45 |
46 | Layout/FirstParameterIndentation:
47 | Enabled: true
48 |
49 | # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
50 | Style/HashSyntax:
51 | Enabled: true
52 |
53 | # Method definitions after `private` or `protected` isolated calls need one
54 | # extra level of indentation.
55 | Layout/IndentationConsistency:
56 | Enabled: true
57 | EnforcedStyle: rails
58 |
59 | # Two spaces, no tabs (for indentation).
60 | Layout/IndentationWidth:
61 | Enabled: true
62 |
63 | Layout/SpaceAfterColon:
64 | Enabled: true
65 |
66 | Layout/SpaceAfterComma:
67 | Enabled: true
68 |
69 | Layout/SpaceAroundEqualsInParameterDefault:
70 | Enabled: true
71 |
72 | Layout/SpaceAroundKeyword:
73 | Enabled: true
74 |
75 | Layout/SpaceAroundOperators:
76 | Enabled: true
77 |
78 | Layout/SpaceBeforeFirstArg:
79 | Enabled: true
80 |
81 | # Defining a method with parameters needs parentheses.
82 | Style/MethodDefParentheses:
83 | Enabled: true
84 |
85 | # Use `foo {}` not `foo{}`.
86 | Layout/SpaceBeforeBlockBraces:
87 | Enabled: true
88 |
89 | # Use `foo { bar }` not `foo {bar}`.
90 | Layout/SpaceInsideBlockBraces:
91 | Enabled: true
92 |
93 | # Use `{ a: 1 }` not `{a:1}`.
94 | Layout/SpaceInsideHashLiteralBraces:
95 | Enabled: true
96 |
97 | Layout/SpaceInsideParens:
98 | Enabled: true
99 |
100 | # Check quotes usage according to lint rule below.
101 | Style/StringLiterals:
102 | Enabled: true
103 | EnforcedStyle: double_quotes
104 |
105 | # Detect hard tabs, no hard tabs.
106 | Layout/Tab:
107 | Enabled: true
108 |
109 | # Blank lines should not have any spaces.
110 | Layout/TrailingBlankLines:
111 | Enabled: true
112 |
113 | # No trailing whitespace.
114 | Layout/TrailingWhitespace:
115 | Enabled: true
116 |
117 | # Use quotes for string literals when they are enough.
118 | Style/UnneededPercentQ:
119 | Enabled: true
120 |
121 | # Align `end` with the matching keyword or starting expression except for
122 | # assignments, where it should be aligned with the LHS.
123 | Lint/EndAlignment:
124 | Enabled: true
125 | EnforcedStyleAlignWith: variable
126 |
127 | # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
128 | Lint/RequireParentheses:
129 | Enabled: true
--------------------------------------------------------------------------------
/app/docs/projects_doc.rb:
--------------------------------------------------------------------------------
1 | module ProjectsDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :project do
5 | param :name, String, "Project's name"
6 | param :description, String, "Project's description"
7 | param :created_at, Date, "Project's time of creation", allow_nil: false
8 | param :updated_at, Date, "Project's time of edition", allow_nil: false
9 | param :user_id, :number, "User's id of project's owner"
10 | param :is_project_from_github, :boolean, "Verify if project is from a github account"
11 | param :is_scoring, :boolean, "Verify if project counts story points"
12 | end
13 |
14 | api :GET, "/users/:user_id/projects", "Show projects for an user"
15 | error code: 401, desc: "Unauthorized"
16 | error code: 404, desc: "Not Found"
17 | error code: 500, desc: "Internal Server Error"
18 | description "Show all projects of a specific user"
19 | returns code: 200, desc: "Ok" do
20 | param_group :project
21 | end
22 | example <<-EOS
23 | [
24 | {
25 | "id":1,
26 | "name":"Owla",
27 | "description":"This project helps improving classes",
28 | "created_at":"2019-04-11T15:42:34.013Z",
29 | "updated_at":"2019-04-11T15:42:34.013Z",
30 | "user_id":1,
31 | "github_slug":null,
32 | "is_project_from_github":true,
33 | "is_scoring":true
34 | },
35 | {
36 | "id":2,
37 | "name":"Falko",
38 | "description":"This project helps agile projects",
39 | "created_at":"2019-04-11T15:42:34.044Z",
40 | "updated_at":"2019-04-11T15:42:34.044Z",
41 | "user_id":1,
42 | "github_slug":null,
43 | "is_project_from_github":true,
44 | "is_scoring":true
45 | }
46 | ]
47 | EOS
48 | def index
49 | end
50 |
51 | api :GET, "/repos", "Show a github projects list"
52 | error code: 401, desc: "Unauthorized"
53 | error code: 404, desc: "Not Found"
54 | error code: 500, desc: "Internal Server Error"
55 | description "Show a projects list of current user from github"
56 | returns code: 200, desc: "Ok" do
57 | param_group :project
58 | end
59 | def github_projects_list
60 | end
61 |
62 | api :GET, "/projects/:id", "Show a specific project"
63 | error code: 401, desc: "Unauthorized"
64 | error code: 404, desc: "Not Found"
65 | error code: 500, desc: "Internal Server Error"
66 | returns code: 200, desc: "Ok" do
67 | param_group :project
68 | end
69 | example <<-EOS
70 | {
71 | "id":1,
72 | "name":"Owla",
73 | "description":"This project helps improving classes",
74 | "created_at":"2019-04-11T15:42:34.013Z",
75 | "updated_at":"2019-04-11T15:42:34.013Z",
76 | "user_id":1,
77 | "github_slug":null,
78 | "is_project_from_github":true,
79 | "is_scoring":true
80 | }
81 | EOS
82 | def show
83 | end
84 |
85 | api :POST, "/users/:user_id/projects", "Create a project"
86 | error code: 401, desc: "Unauthorized"
87 | error code: 404, desc: "Not Found"
88 | error code: 500, desc: "Internal Server Error"
89 | description "Create project for a specific user"
90 | param_group :project
91 | def create
92 | end
93 |
94 | api :PATCH, "/projects/:id", "Update a project"
95 | error code: 401, desc: "Unauthorized"
96 | error code: 404, desc: "Not Found"
97 | error code: 500, desc: "Internal Server Error"
98 | description "Update project for a specific user"
99 | param_group :project
100 | def update
101 | end
102 |
103 | api :DELETE, "/projects/:id", "Delete a project"
104 | error code: 401, desc: "Unauthorized"
105 | error code: 404, desc: "Not Found"
106 | error code: 500, desc: "Internal Server Error"
107 | description "Delete project for a specific user"
108 | param_group :project
109 | def destroy
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/test/helpers/metric_helper_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class VelocityHelperTest < ActiveSupport::TestCase
4 | include VelocityHelper
5 | include MetricHelper
6 |
7 | def setup
8 | @user = User.create(
9 | name: "Ronaldo",
10 | email: "Ronaldofenomeno@gmail.com",
11 | password: "123456789",
12 | password_confirmation: "123456789"
13 | )
14 |
15 | @project = Project.create(
16 | name: "Falko",
17 | description: "Some project description.",
18 | user_id: @user.id,
19 | is_project_from_github: true,
20 | is_scoring: true
21 | )
22 |
23 | @release = Release.create(
24 | name: "R1",
25 | description: "Description",
26 | initial_date: "01/01/2018",
27 | final_date: "01/01/2019",
28 | amount_of_sprints: "20",
29 | project_id: @project.id
30 | )
31 |
32 | @first_sprint = Sprint.create(
33 | name: "Sprint 1",
34 | description: "Sprint 1 us10",
35 | initial_date: "06/10/2016",
36 | final_date: "13/10/2018",
37 | release_id: @release.id,
38 | )
39 |
40 | @second_sprint = Sprint.create(
41 | name: "Sprint 2",
42 | description: "Sprint 2 us10",
43 | initial_date: "06/10/2016",
44 | final_date: "13/10/2018",
45 | release_id: @release.id,
46 | )
47 |
48 | @first_story = Story.create(
49 | name: "Story 1",
50 | description: "Story 1 us14",
51 | assign: "Lucas",
52 | pipeline: "In Progress",
53 | initial_date: "01/01/2017",
54 | issue_number: "8",
55 | sprint_id: @first_sprint.id,
56 | story_points: "5"
57 | )
58 |
59 | @second_story = Story.create(
60 | name: "Story 2",
61 | description: "Story 2 us14",
62 | assign: "Lucas",
63 | pipeline: "Done",
64 | initial_date: "01/01/2017",
65 | final_date: "01/02/2017",
66 | issue_number: "9",
67 | sprint_id: @first_sprint.id,
68 | story_points: "8"
69 | )
70 |
71 | @third_story = Story.create(
72 | name: "Story 3",
73 | description: "Story 3 us14",
74 | assign: "Lucas",
75 | pipeline: "Done",
76 | initial_date: "01/01/2017",
77 | final_date: "01/02/2017",
78 | issue_number: "10",
79 | sprint_id: @first_sprint.id,
80 | story_points: "3"
81 | )
82 |
83 | @another_first_story = Story.create(
84 | name: "Story 1",
85 | description: "Story 1 us13",
86 | assign: "Lucas",
87 | pipeline: "Done",
88 | initial_date: "01/02/2017",
89 | final_date: "01/03/2017",
90 | issue_number: "1",
91 | sprint_id: @second_sprint.id,
92 | story_points: "13"
93 | )
94 |
95 | @another_second_story = Story.create(
96 | name: "Story 1",
97 | description: "Story 1 us13",
98 | assign: "Lucas",
99 | pipeline: "Done",
100 | initial_date: "01/02/2017",
101 | final_date: "01/03/2017",
102 | issue_number: "2",
103 | sprint_id: @second_sprint.id,
104 | story_points: "5"
105 | )
106 |
107 | @token = AuthenticateUser.call(@user.email, @user.password)
108 | end
109 |
110 | test "Should calculate debts metric" do
111 | grade_value_test = 0
112 | grades = {}
113 |
114 | planned_points = @another_first_story.story_points +
115 | @another_second_story.story_points
116 | burned_points = @another_first_story.story_points +
117 | @another_second_story.story_points
118 |
119 | metric_value_test = Float(planned_points - burned_points) / planned_points
120 |
121 | if metric_value_test <= 0.2
122 | grade_value_test += 4
123 | elsif metric_value_test <= 0.4
124 | grade_value_test += 3
125 | elsif metric_value_test <= 0.6
126 | grade_value_test += 2
127 | elsif metric_value_test <= 0.9
128 | grade_value_test += 1
129 | elsif metric_value_test <= 1
130 | grade_value_test += 0
131 | end
132 |
133 | grades = calculate_metrics(@release)
134 | grade_value = grades[:metric_debts_value]
135 |
136 | assert_equal grade_value_test, grade_value
137 | end
138 | end
139 |
--------------------------------------------------------------------------------
/test/controllers/passwords_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 | require "bcrypt"
3 |
4 | class PasswordsControllerTest < ActionDispatch::IntegrationTest
5 | def setup
6 | @user = User.create(
7 | "name": "Eduardo",
8 | "email": "eduardo@gmail.com",
9 | "password": "asecurepassword",
10 | "password_confirmation": "asecurepassword"
11 | )
12 | end
13 |
14 | test "should create token on forgot password request" do
15 | post "/password/forgot/", params: {
16 | "email": "eduardo@gmail.com"
17 | }
18 | assert_response :ok
19 | end
20 |
21 | test "should not create token given a non-existe email" do
22 | post "/password/forgot/", params: {
23 | "email": "nonexistent@gmail.com"
24 | }
25 | assert_response :ok
26 | end
27 |
28 | test "should not create token given a blank email" do
29 | post "/password/forgot/", params: {
30 | "email": ""
31 | }
32 | assert_response :bad_request
33 | end
34 |
35 | test "should update password given a valid token" do
36 | post "/password/forgot", params: {
37 | "email": "eduardo@gmail.com"
38 | }
39 |
40 | token = User.find_by(id: @user.id).reset_password_token
41 |
42 | post "/password/reset/", params: {
43 | "token": token,
44 | "password": "anewsecurepassword",
45 | "password_confirmation": "anewsecurepassword"
46 | }
47 | assert_response :ok
48 |
49 | updated_user = User.find_by(id: @user.id)
50 | new_password = BCrypt::Password.new(updated_user.password_digest)
51 | assert(new_password == "anewsecurepassword")
52 | assert_nil(updated_user.reset_password_token)
53 | end
54 |
55 | test "should not update password given an expired token" do
56 | post "/password/forgot/", params: {
57 | "email": "eduardo@gmail.com"
58 | }
59 | travel 5.hour
60 | token = User.find_by(id: @user.id).reset_password_token
61 | post "/password/reset/", params: {
62 | "token": token,
63 | "password": "anewsecurepassword",
64 | "password_confirmiation": "anewsecurepassword"
65 | }
66 | assert_response :not_found
67 |
68 | end
69 |
70 | test "should not update password given a different token" do
71 | post "/password/forgot/", params: {
72 | "email": "eduardo@gmail.com"
73 | }
74 | assert_response :ok
75 |
76 | post "/password/reset/", params: {
77 | "token": SecureRandom.hex(10),
78 | "password": "somepassword",
79 | "password_confirmation": "somepasswowrd"
80 | }
81 | assert_response :not_found
82 | end
83 |
84 | test "should not update password given a blank token" do
85 | post "/password/reset/", params: {
86 | "token": "",
87 | "password": "somepassword",
88 | "password_confirmation": "somepassword"
89 | }
90 | assert_response :bad_request
91 | end
92 |
93 | test "should return ok given a valid token" do
94 | post "/password/forgot", params: {
95 | "email": "eduardo@gmail.com"
96 | }
97 |
98 | token = User.find_by(id: @user.id).reset_password_token
99 |
100 | get "/password/validate_token?token=#{token}"
101 |
102 | assert_response :ok
103 | body = JSON.parse(response.body)
104 | assert_equal(body["status"], "true")
105 | end
106 |
107 | test "should not return true given an expired token" do
108 | post "/password/forgot", params: {
109 | "email": "eduardo@gmail.com"
110 | }
111 |
112 | token = User.find_by(id: @user.id).reset_password_token
113 |
114 | travel 5.hour
115 |
116 | get "/password/validate_token?token=#{token}"
117 |
118 | assert_response :ok
119 | body = JSON.parse(response.body)
120 | assert_equal(body["status"], "false")
121 | assert_equal(body["error"], ["Link not valid or expired. Try generating a new one."])
122 | end
123 |
124 | test "should not return true given an invalid token" do
125 | post "/password/forgot", params: {
126 | "email": "eduardo@gmail.com"
127 | }
128 | assert_response :ok
129 |
130 | get "/password/validate_token?token=#{SecureRandom.hex(10)}"
131 |
132 | assert_response :ok
133 | body = JSON.parse(response.body)
134 | assert_equal(body["status"], "false")
135 | assert_equal(body["error"], ["Link not valid or expired. Try generating a new one."])
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/app/helpers/metric_helper.rb:
--------------------------------------------------------------------------------
1 | module MetricHelper
2 | include BurndownHelper
3 | include VelocityHelper
4 |
5 | def get_metrics(grade)
6 | last_release = grade.project.releases.last
7 | if last_release.blank?
8 | return nil
9 | else
10 | metrics = calculate_metrics(last_release)
11 |
12 | if metrics.blank?
13 | final_metric = 0
14 | else
15 | sum_of_weights = grade.weight_debts + grade.weight_velocity + grade.weight_burndown
16 |
17 | final_metric = (Float (grade.weight_debts * metrics[:metric_debts_value]) +
18 | (grade.weight_velocity * metrics[:metric_velocity_value]) +
19 | (grade.weight_burndown * metrics[:metric_burndown_value])) /
20 | sum_of_weights
21 | end
22 |
23 | return final_metric.round(1)
24 | end
25 | end
26 |
27 | def calculate_metrics(release)
28 | sprint = release.sprints.last
29 | if sprint.blank? || sprint.stories.blank?
30 | return nil
31 | else
32 | if release.project.is_scoring == true
33 | burned_stories = {}
34 | date_axis = []
35 | points_axis = []
36 | ideal_line = []
37 | metric_burndown_array = []
38 | amount_of_sprints = release.sprints.count
39 | metric_velocity_value = 0
40 | planned_points = 0
41 | burned_points = 0
42 |
43 | velocity = get_sprints_informations(release.sprints, sprint)
44 | total_points = get_total_points(sprint)
45 | burned_stories = get_burned_points(sprint, burned_stories)
46 | total_sprints_points = velocity[:total_points]
47 | velocities = velocity[:velocities]
48 |
49 | range_dates = (sprint.initial_date .. sprint.final_date)
50 |
51 | set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points)
52 | days_of_sprint = date_axis.length - 1
53 | set_ideal_line(days_of_sprint, ideal_line, total_points)
54 | ideal_burned_points = ideal_line[0] - ideal_line[1]
55 |
56 | for i in 0..(date_axis.length - 2)
57 | real_burned_points = points_axis[i] - points_axis[i + 1]
58 | burned_percentage = Float((real_burned_points).abs * 100) / ideal_burned_points
59 | metric_burndown_array.push(burned_percentage)
60 | end
61 |
62 | for i in 0..(amount_of_sprints - 1)
63 | metric_velocity_value += (total_sprints_points[i] - velocities[i])
64 | end
65 |
66 | for i in 0..(release.sprints.length - 1)
67 | planned_points += velocity[:total_points][i]
68 | burned_points += velocity[:completed_points][i]
69 | end
70 |
71 | metric_burndown_value = calculate_burndown(metric_burndown_array)
72 |
73 | metric_debts_value = Float(planned_points - burned_points) / planned_points
74 | metric_debts_value = calculate_velocity_and_debt(metric_debts_value)
75 |
76 | total_points = get_total_points_release(release)
77 | metric_velocity_value = Float metric_velocity_value / total_points
78 | metric_velocity_value = calculate_velocity_and_debt(metric_velocity_value)
79 |
80 | return metrics = { metric_debts_value: metric_debts_value,
81 | metric_velocity_value: metric_velocity_value,
82 | metric_burndown_value: metric_burndown_value }
83 | end
84 | end
85 | end
86 |
87 | def calculate_velocity_and_debt(metric)
88 | values = 0
89 |
90 | if metric <= 0.2
91 | values += 4
92 | elsif metric <= 0.4
93 | values += 3
94 | elsif metric <= 0.6
95 | values += 2
96 | elsif metric <= 0.9
97 | values += 1
98 | elsif metric <= 1
99 | values += 0
100 | end
101 |
102 | return values
103 | end
104 |
105 | def calculate_burndown(metric)
106 | values = 0
107 |
108 | for i in 0..(metric.length - 1)
109 | if metric[i] <= 10 || metric[i] >= 200
110 | values += 0
111 | elsif metric[i] <= 40
112 | values += 1
113 | elsif metric[i] <= 60
114 | values += 2
115 | elsif metric[i] <= 80
116 | values += 3
117 | elsif metric[i] <= 100
118 | values += 4
119 | end
120 | end
121 |
122 | return Float values / metric.length
123 | end
124 | end
125 |
--------------------------------------------------------------------------------
/test/helpers/burndown_helper_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class VelocityHelperTest < ActiveSupport::TestCase
4 | include VelocityHelper
5 |
6 | def setup
7 | @user = User.create(
8 | name: "Ronaldo",
9 | email: "Ronaldofenomeno@gmail.com",
10 | password: "123456789",
11 | password_confirmation: "123456789"
12 | )
13 |
14 | @project = Project.create(
15 | name: "Falko",
16 | description: "Some project description.",
17 | user_id: @user.id,
18 | is_project_from_github: true,
19 | is_scoring: true
20 | )
21 |
22 | @release = Release.create(
23 | name: "R1",
24 | description: "Description",
25 | initial_date: "01/01/2018",
26 | final_date: "01/01/2019",
27 | amount_of_sprints: "20",
28 | project_id: @project.id
29 | )
30 |
31 | @first_sprint = Sprint.create(
32 | name: "Sprint 1",
33 | description: "Sprint 1 us10",
34 | initial_date: "06/10/2016",
35 | final_date: "13/10/2018",
36 | release_id: @release.id,
37 | )
38 |
39 | @second_sprint = Sprint.create(
40 | name: "Sprint 2",
41 | description: "Sprint 2 us10",
42 | initial_date: "06/10/2016",
43 | final_date: "13/10/2018",
44 | release_id: @release.id,
45 | )
46 |
47 | @first_story = Story.create(
48 | name: "Story 1",
49 | description: "Story 1 us14",
50 | assign: "Lucas",
51 | pipeline: "In Progress",
52 | initial_date: "01/01/2017",
53 | issue_number: "8",
54 | sprint_id: @first_sprint.id,
55 | story_points: "5"
56 | )
57 |
58 | @second_story = Story.create(
59 | name: "Story 2",
60 | description: "Story 2 us14",
61 | assign: "Lucas",
62 | pipeline: "Done",
63 | initial_date: "01/01/2017",
64 | final_date: "01/02/2017",
65 | issue_number: "9",
66 | sprint_id: @first_sprint.id,
67 | story_points: "8"
68 | )
69 |
70 | @third_story = Story.create(
71 | name: "Story 3",
72 | description: "Story 3 us14",
73 | assign: "Lucas",
74 | pipeline: "Done",
75 | initial_date: "01/01/2017",
76 | final_date: "01/02/2017",
77 | issue_number: "10",
78 | sprint_id: @first_sprint.id,
79 | story_points: "3"
80 | )
81 |
82 | @another_first_story = Story.create(
83 | name: "Story 1",
84 | description: "Story 1 us13",
85 | assign: "Lucas",
86 | pipeline: "Done",
87 | initial_date: "01/02/2017",
88 | final_date: "01/03/2017",
89 | issue_number: "1",
90 | sprint_id: @second_sprint.id,
91 | story_points: "13"
92 | )
93 |
94 | @another_second_story = Story.create(
95 | name: "Story 1",
96 | description: "Story 1 us13",
97 | assign: "Lucas",
98 | pipeline: "Done",
99 | initial_date: "01/02/2017",
100 | final_date: "01/03/2017",
101 | issue_number: "2",
102 | sprint_id: @second_sprint.id,
103 | story_points: "5"
104 | )
105 |
106 | @token = AuthenticateUser.call(@user.email, @user.password)
107 | end
108 |
109 | test "Should get burned sprint points" do
110 | final_date = @another_first_story.final_date
111 |
112 | burned_stories_test = {}
113 | burned_stories = {}
114 |
115 | burned_stories_test[final_date] =
116 | @another_first_story.story_points +
117 | @another_second_story.story_points
118 |
119 |
120 | assert_equal burned_stories_test, get_burned_points(@second_sprint, burned_stories)
121 | end
122 |
123 | test "Should set ideal axis" do
124 | date_axis_test = []
125 | ideal_line_test = []
126 | ideal_line = []
127 |
128 | planned_points = @another_first_story.story_points +
129 | @another_second_story.story_points
130 |
131 | range_dates = (@another_first_story.initial_date .. @another_first_story.final_date)
132 |
133 | range_dates.each do |date|
134 | date_axis_test.push(date)
135 | end
136 |
137 | days_of_sprint = date_axis_test.length - 1
138 |
139 | for day in (days_of_sprint).downto(0)
140 | ideal_line_test.push(planned_points * (day / (Float days_of_sprint)))
141 | end
142 |
143 | set_ideal_line(days_of_sprint, ideal_line, planned_points)
144 |
145 | assert_equal ideal_line_test, ideal_line
146 | end
147 | end
148 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20190418161604) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "grades", force: :cascade do |t|
19 | t.float "weight_burndown"
20 | t.float "weight_velocity"
21 | t.float "weight_debts"
22 | t.datetime "created_at", null: false
23 | t.datetime "updated_at", null: false
24 | t.bigint "project_id"
25 | t.index ["project_id"], name: "index_grades_on_project_id"
26 | end
27 |
28 | create_table "projects", force: :cascade do |t|
29 | t.string "name"
30 | t.text "description"
31 | t.datetime "created_at", null: false
32 | t.datetime "updated_at", null: false
33 | t.bigint "user_id"
34 | t.string "github_slug"
35 | t.boolean "is_project_from_github"
36 | t.boolean "is_scoring"
37 | t.index ["user_id"], name: "index_projects_on_user_id"
38 | end
39 |
40 | create_table "releases", force: :cascade do |t|
41 | t.string "name"
42 | t.text "description"
43 | t.integer "amount_of_sprints"
44 | t.datetime "created_at", null: false
45 | t.datetime "updated_at", null: false
46 | t.bigint "project_id"
47 | t.date "initial_date"
48 | t.date "final_date"
49 | t.index ["project_id"], name: "index_releases_on_project_id"
50 | end
51 |
52 | create_table "retrospectives", force: :cascade do |t|
53 | t.text "sprint_report"
54 | t.text "positive_points", default: [], array: true
55 | t.text "negative_points", default: [], array: true
56 | t.text "improvements", default: [], array: true
57 | t.datetime "created_at", null: false
58 | t.datetime "updated_at", null: false
59 | t.bigint "sprint_id"
60 | t.index ["sprint_id"], name: "index_retrospectives_on_sprint_id"
61 | end
62 |
63 | create_table "revisions", force: :cascade do |t|
64 | t.text "done_report", array: true
65 | t.text "undone_report", array: true
66 | t.datetime "created_at", null: false
67 | t.datetime "updated_at", null: false
68 | t.bigint "sprint_id"
69 | t.index ["sprint_id"], name: "index_revisions_on_sprint_id"
70 | end
71 |
72 | create_table "sprints", force: :cascade do |t|
73 | t.string "name"
74 | t.text "description"
75 | t.datetime "created_at", null: false
76 | t.datetime "updated_at", null: false
77 | t.bigint "release_id"
78 | t.date "initial_date"
79 | t.date "final_date"
80 | t.index ["release_id"], name: "index_sprints_on_release_id"
81 | end
82 |
83 | create_table "stories", force: :cascade do |t|
84 | t.string "name"
85 | t.text "description"
86 | t.string "assign"
87 | t.string "pipeline"
88 | t.datetime "created_at", null: false
89 | t.datetime "updated_at", null: false
90 | t.bigint "sprint_id"
91 | t.date "initial_date"
92 | t.date "final_date"
93 | t.integer "story_points"
94 | t.string "issue_number"
95 | t.integer "issue_id"
96 | t.index ["sprint_id"], name: "index_stories_on_sprint_id"
97 | end
98 |
99 | create_table "users", force: :cascade do |t|
100 | t.string "name"
101 | t.string "email"
102 | t.string "password_digest"
103 | t.datetime "created_at", null: false
104 | t.datetime "updated_at", null: false
105 | t.string "access_token"
106 | t.string "reset_password_token"
107 | t.datetime "reset_password_sent_at"
108 | end
109 |
110 | add_foreign_key "grades", "projects"
111 | add_foreign_key "projects", "users"
112 | add_foreign_key "releases", "projects"
113 | add_foreign_key "retrospectives", "sprints"
114 | add_foreign_key "revisions", "sprints"
115 | add_foreign_key "sprints", "releases"
116 | add_foreign_key "stories", "sprints"
117 | end
118 |
--------------------------------------------------------------------------------
/app/controllers/issues_controller.rb:
--------------------------------------------------------------------------------
1 | require "rest-client"
2 |
3 | class IssuesController < ApplicationController
4 | include IssueGraphicHelper
5 |
6 | before_action :set_authorization, except: [:update_assignees]
7 | before_action :set_project
8 |
9 | def index
10 | client = Adapter::GitHubIssue.new(request)
11 |
12 | if params[:page].present?
13 | @issues = client.list_issues(@project.github_slug, params[:page])
14 | total_pages = client.total_issues_pages(params[:page])
15 | else
16 | @issues = client.list_all_issues(@project.github_slug)
17 | total_pages = 1
18 | end
19 |
20 | convert_form_params(@issues)
21 |
22 | all_stories = Story.all
23 |
24 | all_stories_number = []
25 |
26 | all_stories.each do |story|
27 | all_stories_number.push(story.issue_number.to_i)
28 | end
29 |
30 | @filter_form = { issues_infos: [], total_pages: total_pages }
31 |
32 | @filter_form[:issues_infos] = @form_params[:issues_infos].reject do |h|
33 | all_stories_number.include? h[:issue_id]
34 | end
35 |
36 | render json: @filter_form
37 | end
38 |
39 | def create
40 | client = Adapter::GitHubIssue.new(request)
41 |
42 | @issue = client.create_issue(@project.github_slug, issue_params)
43 |
44 | convert_form_params(@issue)
45 |
46 | render json: @form_params, status: :created
47 | end
48 |
49 | def update
50 | client = Adapter::GitHubIssue.new(request)
51 |
52 | @issue = client.update_issue(@project.github_slug, issue_params)
53 |
54 | convert_form_params(@issue)
55 |
56 | render json: @form_params
57 | end
58 |
59 | def close
60 | client = Adapter::GitHubIssue.new(request)
61 |
62 | client.close_issue(@project.github_slug, issue_params)
63 |
64 | render status: :ok
65 | end
66 |
67 | def issue_graphic_data
68 | client = Adapter::GitHubIssue.new(request)
69 |
70 | @issues = client.list_all_issues(@project.github_slug)
71 |
72 | if @issues.count != 0
73 |
74 | actual_date = params[:actual_date].to_date
75 | option = params[:option]
76 |
77 | data_of_issues = {}
78 |
79 | data_of_issues = get_issues_graphic(actual_date, option, @issues)
80 |
81 | render json: data_of_issues
82 | else
83 | render json: { error: "Issues don't exists" }, status: :not_found
84 | end
85 | end
86 |
87 | def reopen_issue
88 | client = Adapter::GitHubIssue.new(request)
89 |
90 | client.reopen_issue(@project.github_slug, issue_params)
91 |
92 | render status: 200
93 | end
94 |
95 | def update_assignees
96 | begin
97 | set_project
98 |
99 | @current_user = AuthorizeApiRequest.call(request.headers).result
100 |
101 | response = RestClient.patch("https://api.github.com/repos/#{@project.github_slug}/issues/#{params[:issue_number]}",
102 | { assignees: params[:assignees] }.to_json,
103 | Authorization: "token #{@current_user.access_token}"
104 | )
105 |
106 | render status: :ok
107 | rescue RestClient::UnprocessableEntity
108 | render json: { errors: "Cannot update assignees" }, status: :unprocessable_entity
109 | rescue RestClient::NotFound
110 | render json: { errors: "Content not found" }, status: :not_found
111 | rescue ActiveRecord::RecordNotFound
112 | render json: { errors: "Project not found" }, status: :not_found
113 | end
114 | end
115 |
116 | private
117 |
118 | def set_authorization
119 | client = Adapter::GitHubIssue.new(request)
120 | end
121 |
122 | def set_project
123 | begin
124 | @project = Project.find(params[:id])
125 | rescue ActiveRecord::RecordNotFound
126 | render json: { errors: "Project not found" }, status: :not_found
127 | end
128 | end
129 |
130 | def convert_form_params(issue)
131 | @form_params = { issues_infos: [] }
132 | if issue.kind_of?(Array)
133 | @issues.each do |issue|
134 | make_form_params(issue)
135 | end
136 | else
137 | make_form_params(issue)
138 | end
139 | @form_params
140 | end
141 |
142 | def assignee_counter(issue)
143 | assignees = []
144 | if issue.assignees.count > 0
145 | issue.assignees.each do |assignee|
146 | assignees.push(assignee.login)
147 | end
148 | end
149 | assignees
150 | end
151 |
152 | def make_form_params(issue)
153 | assignees = assignee_counter(issue)
154 | @form_params[:issues_infos].push(name: issue.title, number: issue.number, body: issue.body, issue_id: issue.id, assignees: assignees) unless issue.pull_request
155 | end
156 |
157 | def issue_params
158 | params.require(:issue).permit(:name, :body, :number)
159 | end
160 | end
161 |
--------------------------------------------------------------------------------
/test/helpers/velocity_helper_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class VelocityHelperTest < ActiveSupport::TestCase
4 | include VelocityHelper
5 |
6 | def setup
7 | @user = User.create(
8 | name: "Ronaldo",
9 | email: "Ronaldofenomeno@gmail.com",
10 | password: "123456789",
11 | password_confirmation: "123456789"
12 | )
13 |
14 | @project = Project.create(
15 | name: "Falko",
16 | description: "Some project description.",
17 | user_id: @user.id,
18 | is_project_from_github: true,
19 | is_scoring: true
20 | )
21 |
22 | @release = Release.create(
23 | name: "R1",
24 | description: "Description",
25 | initial_date: "01/01/2018",
26 | final_date: "01/01/2019",
27 | amount_of_sprints: "20",
28 | project_id: @project.id
29 | )
30 |
31 | @first_sprint = Sprint.create(
32 | name: "Sprint 1",
33 | description: "Sprint 1 us10",
34 | initial_date: "06/10/2016",
35 | final_date: "13/10/2018",
36 | release_id: @release.id,
37 | )
38 |
39 | @second_sprint = Sprint.create(
40 | name: "Sprint 2",
41 | description: "Sprint 2 us10",
42 | initial_date: "06/10/2016",
43 | final_date: "13/10/2018",
44 | release_id: @release.id,
45 | )
46 |
47 | @first_story = Story.create(
48 | name: "Story 1",
49 | description: "Story 1 us14",
50 | assign: "Lucas",
51 | pipeline: "In Progress",
52 | initial_date: "01/01/2017",
53 | issue_number: "8",
54 | sprint_id: @first_sprint.id,
55 | story_points: "5"
56 | )
57 |
58 | @second_story = Story.create(
59 | name: "Story 2",
60 | description: "Story 2 us14",
61 | assign: "Lucas",
62 | pipeline: "Done",
63 | initial_date: "01/01/2017",
64 | final_date: "01/02/2017",
65 | issue_number: "9",
66 | sprint_id: @first_sprint.id,
67 | story_points: "8"
68 | )
69 |
70 | @third_story = Story.create(
71 | name: "Story 3",
72 | description: "Story 3 us14",
73 | assign: "Lucas",
74 | pipeline: "Done",
75 | initial_date: "01/01/2017",
76 | final_date: "01/02/2017",
77 | issue_number: "10",
78 | sprint_id: @first_sprint.id,
79 | story_points: "3"
80 | )
81 |
82 | @another_first_story = Story.create(
83 | name: "Story 1",
84 | description: "Story 1 us13",
85 | assign: "Lucas",
86 | pipeline: "Done",
87 | initial_date: "01/02/2017",
88 | final_date: "01/03/2017",
89 | issue_number: "1",
90 | sprint_id: @second_sprint.id,
91 | story_points: "13"
92 | )
93 |
94 | @another_second_story = Story.create(
95 | name: "Story 1",
96 | description: "Story 1 us13",
97 | assign: "Lucas",
98 | pipeline: "Done",
99 | initial_date: "01/02/2017",
100 | final_date: "01/03/2017",
101 | issue_number: "2",
102 | sprint_id: @second_sprint.id,
103 | story_points: "5"
104 | )
105 |
106 | @token = AuthenticateUser.call(@user.email, @user.password)
107 | end
108 |
109 | test "should get correct sprints informations" do
110 | sprints = []
111 | sprints.push(@first_sprint)
112 |
113 | velocity = get_sprints_informations(sprints, @first_sprint)
114 |
115 | total_points = @first_story.story_points + @second_story.story_points + @third_story.story_points
116 |
117 | total_completed_points = @second_story.story_points + @third_story.story_points
118 |
119 | assert_equal [@first_sprint.name], velocity[:names]
120 | assert_equal [total_points], velocity[:total_points]
121 | assert_equal [total_completed_points], velocity[:completed_points]
122 | assert_equal [calculate_velocity([total_completed_points])], velocity[:velocities]
123 | end
124 |
125 | test "should calculate velocity" do
126 | sprint_1_points = @second_story.story_points +
127 | @third_story.story_points
128 |
129 | sprint_2_points = @another_first_story.story_points +
130 | @another_second_story.story_points
131 |
132 | completed_points = []
133 | completed_points.push(sprint_1_points)
134 | completed_points.push(sprint_2_points)
135 |
136 | velocity = Float(sprint_1_points + sprint_2_points) / 2
137 |
138 | assert_equal velocity, calculate_velocity(completed_points)
139 | end
140 |
141 | test "Should calculate total points of one Release" do
142 | sprint_1_points = @first_story.story_points +
143 | @second_story.story_points +
144 | @third_story.story_points
145 |
146 | sprint_2_points = @another_first_story.story_points +
147 | @another_second_story.story_points
148 |
149 | total_points = sprint_1_points + sprint_2_points
150 |
151 | assert_equal total_points, get_total_points_release(@release)
152 | end
153 | end
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Falko
7 |
8 |
9 | See beyond
10 |
11 | Visit our Wiki »
12 |
13 |
14 | Access Falko
15 | ·
16 | FrontEnd Repository
17 | ·
18 | Report an Issue
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | ## Introduction
30 |
31 | Falko is an web application, developed using Ruby on Rails API and Vue.js. It is a Freen Software Project, developed initially to GPP/MDS subjects of UnB - University of Brasília - Engineering Campus (FGA). Falko aims to promote a platform that makes easier for managers _to manage_ agile projects through displaying metrics and relevant info regarding the project, also contributing to more eficient decision making.
32 |
33 | ## Development setup
34 |
35 | Development environment uses the containers architecture through _Docker_. To install, simply follow the guide [How to Use Docker](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/wiki/How-to-Use-Docker).
36 | If you're not familiar with docker, you can just simplify some default commands with _make_.
37 |
38 | ### *_Make_ usage*
39 | #### Enters the Rails Console
40 | `$ make console`
41 |
42 | #### Creates the development and test database
43 | `$ make create-db`
44 |
45 | #### Drops down the docker environment
46 | `$ make down`
47 |
48 | #### Migrates the Rails database
49 | `$ make migrate`
50 |
51 | #### Lists all your running services
52 | `$ make ps`
53 |
54 | #### Removes all your docker networks
55 | `$ make rm-network`
56 |
57 | #### Removes all your docker mapped volumes
58 | `$ make rm-volume`
59 |
60 | #### Creates the Falko environment and run it
61 | `$ make run`
62 |
63 | #### Creates the Falko environment and run it as daemon
64 | `$ make quiet-run`
65 |
66 | #### Creates only the API environment, without database service
67 | `$ make run-api`
68 |
69 | #### Creates only the Postgres database environment, without API service
70 | `$ make run-db`
71 |
72 | #### Seeds the Rails database environment
73 | `$ make seed`
74 |
75 | #### Executes all of Rails' tests
76 | `$ make test`
77 |
78 | ### Docker Usage
79 | * Download and install Docker CE at the [official site](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-from-a-package).
80 |
81 | * Download and install Docker Compose at the [official site](https://docs.docker.com/compose/install/#master-builds).
82 |
83 | #### Clone the repository and enter it
84 | ```
85 | git clone https://github.com/falko-org/Falko-API && cd Falko-API/
86 | ```
87 |
88 | #### Make sure you're at _devel_ branch
89 | ```
90 | git checkout devel
91 | ```
92 |
93 | #### Lift your environment
94 | ```
95 | docker-compose up
96 | ```
97 |
98 | #### Useful commands
99 | #### How to download a docker image
100 | ```
101 | docker pull imageYouWant
102 | ```
103 |
104 | #### Listing local images
105 | ```
106 | docker images
107 | ```
108 |
109 | #### Deleting images
110 | ```
111 | docker rmi -f imageId
112 | ```
113 |
114 | #### Listing running containers
115 | ```
116 | docker ps
117 | ```
118 |
119 | #### Removing containers
120 | ```
121 | docker rm [-f] containerNameOrId
122 | ```
123 |
124 | #### Executing commands from outside the container
125 | ```
126 | docker exec
127 | ```
128 | Example:
129 | ```
130 | docker exec falko-api rails generate model User name:string
131 | ```
132 |
133 | ## Documentation
134 |
135 | Additional documentation is avaiable at [Official Wiki](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/wiki).
136 |
137 | ## How to Contribute
138 |
139 | To contribute with us, the colaborator must _fork_ and send a [pull request](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/pulls) with his/her contribution to _devel_ branch.
140 | The code will be analized by one of the project's owners and, if approved, included to the application's core.
141 |
142 | ## License
143 |
144 | [MIT](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/blob/devel/LICENSE)
145 |
146 | Copyright (c) 2018 Falko Organization
147 |
--------------------------------------------------------------------------------
/app/docs/stories_doc.rb:
--------------------------------------------------------------------------------
1 | module StoriesDoc
2 | extend Apipie::DSL::Concern
3 |
4 | def_param_group :storie do
5 | param :name, String, "Storie's name"
6 | param :description, String, "Storie's description"
7 | param :assign, String, "Storie's assigned person"
8 | param :pipeline, String, "Storie's pipeline"
9 | param :created_at, Date, "Storie's time of creation", allow_nil: false
10 | param :updated_at, Date, "Storie's time of edition", allow_nil: false
11 | param :sprint_id, :number, "Id of sprint that the storie belongs"
12 | param :initial_date, Date, "Storie's initial date"
13 | param :final_date, Date, "Storie's final date"
14 | param :story_points, Integer, "Storie's quantity of points"
15 | param :issue_number, String, "Issue number of the storie"
16 | param :issue_id, Integer, "Issue's id"
17 | end
18 |
19 | api :GET, "/sprints/:sprint_id/stories", "Show stories for a sprint"
20 | error code: 401, desc: "Unauthorized"
21 | error code: 404, desc: "Not Found"
22 | error code: 500, desc: "Internal Server Error"
23 | description "Show all stories of a specific sprint"
24 | returns code: 200, desc: "Ok" do
25 | param_group :storie
26 | end
27 | example <<-EOS
28 | [
29 | {
30 | "id": 2,
31 | "name": "Story 2",
32 | "description": "Story 2 us14",
33 | "assign": "Alax",
34 | "pipeline": "Done",
35 | "created_at": "2019-04-11T15:42:34.807Z",
36 | "updated_at": "2019-04-11T15:42:34.807Z",
37 | "sprint_id": 1,
38 | "initial_date": "2017-01-01",
39 | "final_date": "2017-01-08",
40 | "story_points": 3,
41 | "issue_number": "2",
42 | "issue_id": null
43 | },
44 | {
45 | "id": 1,
46 | "name": "Story 1",
47 | "description": "Story 1 us14",
48 | "assign": "Lucas",
49 | "pipeline": "Backlog",
50 | "created_at": "2019-04-11T15:42:34.788Z",
51 | "updated_at": "2019-04-11T15:42:34.788Z",
52 | "sprint_id": 1,
53 | "initial_date": "2017-01-01",
54 | "final_date": "2017-01-02",
55 | "story_points": 2,
56 | "issue_number": "1",
57 | "issue_id": null
58 | }
59 | ]
60 | EOS
61 | def index
62 | end
63 |
64 | api :GET, "/sprints/:id/to_do_stories", "Show stories to do"
65 | error code: 401, desc: "Unauthorized"
66 | error code: 404, desc: "Not Found"
67 | error code: 500, desc: "Internal Server Error"
68 | description "Show all stories to do of a specific sprint"
69 | returns code: 200, desc: "Ok" do
70 | param_group :storie
71 | end
72 | example <<-EOS
73 | {
74 | "stories_infos": [
75 | {
76 | "name": "Story 1",
77 | "description": "Story 1 us14",
78 | "assign": "Lucas",
79 | "pipeline": "To Do",
80 | "initial_date": "2017-01-01",
81 | "story_points": 2,
82 | "final_date": "2017-01-02",
83 | "issue_number": "1",
84 | "id": 46
85 | }
86 | ]
87 | }
88 | EOS
89 | def to_do_list
90 | end
91 |
92 | api :GET, "/sprints/:id/doing_stories", "Show doing stories"
93 | error code: 401, desc: "Unauthorized"
94 | error code: 404, desc: "Not Found"
95 | error code: 500, desc: "Internal Server Error"
96 | description "Show all doing stories of a specific sprint"
97 | returns code: 200, desc: "Ok" do
98 | param_group :storie
99 | end
100 | example <<-EOS
101 | {
102 | "stories_infos": [
103 | {
104 | "name": "Story 1",
105 | "description": "Story 1 us14",
106 | "assign": "Matheus B",
107 | "pipeline": "Doing",
108 | "initial_date": "2017-01-01",
109 | "story_points": 5,
110 | "final_date": "2017-01-04",
111 | "issue_number": "3",
112 | "id": 47
113 | }
114 | ]
115 | }
116 | EOS
117 | def doing_list
118 | end
119 |
120 | api :GET, "/sprints/:id/done_stories", "Show done stories"
121 | error code: 401, desc: "Unauthorized"
122 | error code: 404, desc: "Not Found"
123 | error code: 500, desc: "Internal Server Error"
124 | description "Show all done stories of a specific sprint"
125 | returns code: 200, desc: "Ok" do
126 | param_group :storie
127 | end
128 | example <<-EOS
129 | {
130 | "stories_infos": [
131 | {
132 | "name": "Story 2",
133 | "description": "Story 2 us14",
134 | "assign": "Alax",
135 | "pipeline": "Done",
136 | "initial_date": "2017-01-01",
137 | "story_points": 3,
138 | "final_date": "2017-01-08",
139 | "issue_number": "2",
140 | "id": 2
141 | }
142 | ]
143 | }
144 | EOS
145 | def done_list
146 | end
147 |
148 | api :GET, "/stories/:id", "Show a storie"
149 | error code: 401, desc: "Unauthorized"
150 | error code: 404, desc: "Not Found"
151 | error code: 500, desc: "Internal Server Error"
152 | description "Show a specific storie"
153 | returns code: 200, desc: "Ok" do
154 | param_group :storie
155 | end
156 | example <<-EOS
157 | {
158 | "id": 1,
159 | "name": "Story 1",
160 | "description": "Story 1 us14",
161 | "assign": "Lucas",
162 | "pipeline": "Backlog",
163 | "created_at": "2019-04-11T15:42:34.788Z",
164 | "updated_at": "2019-04-11T15:42:34.788Z",
165 | "sprint_id": 1,
166 | "initial_date": "2017-01-01",
167 | "final_date": "2017-01-02",
168 | "story_points": 2,
169 | "issue_number": "1",
170 | "issue_id": null
171 | }
172 | EOS
173 | def show
174 | end
175 |
176 | api :POST, "/sprints/:sprint_id/stories", "Create a storie"
177 | error code: 401, desc: "Unauthorized"
178 | error code: 404, desc: "Not Found"
179 | error code: 500, desc: "Internal Server Error"
180 | description "Create a specific storie"
181 | param_group :storie
182 | def create
183 | end
184 |
185 | api :PATCH, "/stories/:id", "Update a storie"
186 | error code: 401, desc: "Unauthorized"
187 | error code: 404, desc: "Not Found"
188 | error code: 500, desc: "Internal Server Error"
189 | description "Update a specific storie"
190 | param_group :storie
191 | def update
192 | end
193 |
194 | api :DELETE, "/stories/:id", "Delete a storie"
195 | error code: 401, desc: "Unauthorized"
196 | error code: 404, desc: "Not Found"
197 | error code: 500, desc: "Internal Server Error"
198 | description "Delete a specific storie"
199 | param_group :storie
200 | def destroy
201 | end
202 | end
203 |
--------------------------------------------------------------------------------
/app/helpers/validations_helper.rb:
--------------------------------------------------------------------------------
1 | module ValidationsHelper
2 | def current_user
3 | @current_user = AuthorizeApiRequest.call(request.headers).result
4 | end
5 |
6 | def user
7 | @user = User.find(@project.user_id)
8 | end
9 |
10 | def project
11 | @project = Project.find(@release.project_id)
12 | end
13 |
14 | def project_grade
15 | @project = Project.find(@grade.project.id)
16 | end
17 |
18 | def release
19 | @release = Release.find(@sprint.release_id)
20 | end
21 |
22 | def sprint(component_type)
23 | if component_type == "story"
24 | @sprint = Sprint.find(@story.sprint_id)
25 | elsif component_type == "revision"
26 | @sprint = Sprint.find(@revision.sprint_id)
27 | elsif component_type == "retrospective"
28 | @sprint = Sprint.find(@retrospective.sprint_id)
29 | end
30 | end
31 |
32 | def verifies_id(current_id, previous_id, component_type)
33 | if component_type == "user" && current_id != 0
34 | id = current_id
35 | @user = User.find(params[:id].to_i)
36 | elsif component_type == "user" && previous_id != 0
37 | user_id = previous_id
38 | @user = User.find(params[:user_id].to_i)
39 | elsif component_type == "project" && current_id != 0
40 | id = current_id
41 | @project = Project.find(params[:id].to_i)
42 | elsif component_type == "project" && previous_id != 0
43 | project_id = previous_id
44 | @project = Project.find(params[:project_id].to_i)
45 | elsif component_type == "release" && current_id != 0
46 | id = current_id
47 | @release = Release.find(params[:id].to_i)
48 | elsif component_type == "release" && previous_id != 0
49 | release_id = previous_id
50 | @release = Release.find(params[:release_id].to_i)
51 | elsif component_type == "grade" && current_id != 0
52 | id = current_id
53 | @grade = Grade.find(params[:id].to_i)
54 | elsif component_type == "grade" && previous_id != 0
55 | grade_id = previous_id
56 | @grade = Grade.find(params[:grade_id].to_i)
57 | elsif component_type == "sprint" && current_id != 0
58 | id = current_id
59 | @sprint = Sprint.find(params[:id].to_i)
60 | elsif component_type == "sprint" && previous_id != 0
61 | sprint_id = previous_id
62 | @sprint = Sprint.find(params[:sprint_id].to_i)
63 | end
64 | end
65 |
66 | def validate_user(id, user_id)
67 | current_user
68 | verifies_id(id, user_id, "user")
69 |
70 | if @current_user.id == @user.id
71 | return true
72 | else
73 | render json: { error: "Not Authorized" }, status: 401
74 | end
75 | end
76 |
77 | def validate_project(id, project_id)
78 | current_user
79 | verifies_id(id, project_id, "project")
80 | user
81 |
82 | if @current_user.id == @user.id
83 | return true
84 | else
85 | render json: { error: "Not Authorized" }, status: 401
86 | end
87 | end
88 |
89 |
90 | def validate_grade(id, grade_id)
91 | current_user
92 | verifies_id(id, grade_id, "grade")
93 | project_grade
94 | user
95 |
96 | if @current_user.id == @user.id
97 | return true
98 | else
99 | render json: { error: "Not Authorized" }, status: 401
100 | end
101 | end
102 |
103 | def validate_release(id, release_id)
104 | current_user
105 | verifies_id(id, release_id, "release")
106 | project
107 | user
108 |
109 | if @current_user.id == @user.id
110 | return true
111 | else
112 | render json: { error: "Not Authorized" }, status: 401
113 | end
114 | end
115 |
116 | def validate_sprint(id, sprint_id)
117 | current_user
118 | verifies_id(id, sprint_id, "sprint")
119 | release
120 | project
121 | user
122 |
123 | if @current_user.id == @user.id
124 | return true
125 | else
126 | render json: { error: "Not Authorized" }, status: 401
127 | end
128 | end
129 |
130 | def validate_sprints_date(component_type, component_params)
131 | if @release.initial_date > @sprint.initial_date ||
132 | @release.final_date < @sprint.initial_date
133 | false
134 | elsif @release.final_date < @sprint.final_date ||
135 | @release.initial_date > @sprint.final_date
136 | false
137 | else
138 | return true
139 | end
140 | end
141 |
142 |
143 | def validate_stories(story_points, id, sprint_id)
144 | current_user
145 | verifies_id(id, sprint_id, "sprint")
146 | release
147 | project
148 | user
149 |
150 | if @project.is_scoring
151 | if story_points != nil
152 | return true
153 | else
154 | return false
155 | end
156 | else
157 | if story_points != nil
158 | return false
159 | else
160 | return true
161 | end
162 | end
163 | end
164 |
165 | def validate_sprint_dependencies(id, component_type)
166 | current_user
167 | if component_type == "story"
168 | @story = Story.find(params[:id].to_i)
169 | sprint("story")
170 | elsif component_type == "revision"
171 | @revision = Revision.find(params[:id].to_i)
172 | sprint("revision")
173 | elsif component_type == "retrospective"
174 | @retrospective = Retrospective.find(params[:id].to_i)
175 | sprint("retrospective")
176 | end
177 | release
178 | project
179 | user
180 |
181 | if @current_user.id == @user.id
182 | return true
183 | else
184 | render json: { error: "Not Authorized" }, status: 401
185 | end
186 | end
187 |
188 | def create_sprint_dependencies(component_type, component_params)
189 | if component_type == "revision" && @sprint.revision == nil
190 | @component = Revision.create(component_params)
191 | save_component(@component)
192 | elsif component_type == "retrospective" && @sprint.retrospective == nil
193 | @component = Retrospective.create(component_params)
194 | save_component(@component)
195 | else
196 | render json: { error: "Cannot create multiple #{component_type}" }, status: 403
197 | end
198 | end
199 |
200 | def save_component(component)
201 | @component = component
202 | @component.sprint_id = @sprint.id
203 | if @component.save
204 | render json: @component, status: :created
205 | else
206 | render json: @component.errors, status: :unprocessable_entity
207 | end
208 | end
209 |
210 | def update_amount_of_sprints
211 | @release.amount_of_sprints = @release.sprints.count
212 | @release.save
213 | end
214 | end
215 |
--------------------------------------------------------------------------------
/test/controllers/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class UsersControllerTest < ActionDispatch::IntegrationTest
4 | def setup
5 | @user = User.create(
6 | name: "Ronaldo",
7 | email: "ronaldofenomeno@gmail.com",
8 | password: "123456789",
9 | password_confirmation: "123456789"
10 | )
11 |
12 | @token = AuthenticateUser.call(@user.email, @user.password)
13 | end
14 |
15 | test "should login in" do
16 | post "/authenticate", params: {
17 | email: "ronaldofenomeno@gmail.com",
18 | password: "123456789"
19 | }
20 |
21 | assert_response :success
22 | end
23 |
24 | test "should create valid user" do
25 | assert_difference("User.count") do
26 | post "/users", params: {
27 | "user": {
28 | "email": "robakasdddi@email.com",
29 | "name": "Fulvvano",
30 | "password": "123456789",
31 | "password_confirmation": "123456789"
32 | }
33 | }, headers: { Authorization: @token.result }
34 |
35 | assert_response :success
36 | end
37 | end
38 |
39 | test "should login with rights params" do
40 | post "/authenticate", params: {
41 | email: "ronaldofenomeno@gmail.com",
42 | password: "123456789"
43 | }
44 |
45 | assert_response :success
46 | end
47 |
48 | test "should not login with wrongs params" do
49 | post "/authenticate", params: {
50 | email: "fenomeno@gmail.com",
51 | password: "123456789"
52 | }
53 |
54 | assert_response :unauthorized
55 | end
56 |
57 | test "should create a user with valids params" do
58 | assert_difference("User.count") do
59 | post "/users", params: {
60 | "user": {
61 | "email": "robakasdddi@email.com",
62 | "name": "Fulvvano",
63 | "password": "123456789",
64 | "password_confirmation": "123456789"
65 | }
66 | }
67 |
68 | assert_response :success
69 | end
70 | end
71 |
72 | test "should not create invalid user with short name" do
73 | assert_no_difference("User.count") do
74 | post "/users", params: {
75 | "user": {
76 | "email": "robakasdddi@email.com",
77 | "name": "Fu",
78 | "password": "123456789",
79 | "password_confirmation": "123456789"
80 | }
81 | }
82 |
83 | assert_response :unprocessable_entity
84 | end
85 | end
86 |
87 | test "should not create invalid user with too large name" do
88 | assert_no_difference("User.count") do
89 | post "/users", params: {
90 | "user": {
91 | "email": "robakasdddi@email.com",
92 | "name": "F" * 81,
93 | "password": "123456789",
94 | "password_confirmation": "123456789"
95 | }
96 | }
97 |
98 | assert_response :unprocessable_entity
99 | end
100 | end
101 |
102 |
103 | test "should delete user" do
104 | delete "/users/#{@user.id}", headers: { Authorization: @token.result }
105 |
106 | assert_response :success
107 | end
108 |
109 | test "should show user" do
110 | get "/users/#{@user.id}", headers: { Authorization: @token.result }
111 |
112 | assert_response :success
113 | end
114 |
115 | test "should update user" do
116 | patch "/users/#{@user.id}", params: {
117 | "user": {
118 | "name": "Fulvvano123",
119 | }
120 | }, headers: { Authorization: @token.result }
121 |
122 | @user.reload
123 |
124 | assert_response :success
125 | end
126 |
127 | test "should not update user without authentication" do
128 | patch "/users/#{@user.id}", params: {
129 | "user": {
130 | "email": "robakasdddi@email.com",
131 | "name": "Fulvvano",
132 | }
133 | }
134 | @user.reload
135 |
136 | assert_response :unauthorized
137 | end
138 |
139 | test "should not update user with invalid params" do
140 | patch "/users/#{@user.id}", params: {
141 | "user": {
142 | "email": "robakasdddi@email.com",
143 | "name": "Fulvvano",
144 | "password": "12345",
145 | "password_confirmation": "123456789"
146 | }
147 | }, headers: { Authorization: @token.result }
148 | @user.reload
149 |
150 | assert_response :unprocessable_entity
151 | assert response.parsed_body["access_token"] != "bad_verification_code"
152 | end
153 |
154 | test "should authenticate user with github" do
155 | RestClient.stub :post, "access_token=token123&something_else=anothervalue" do
156 | post "/request_github_token", params: {
157 | "code": "code123",
158 | "id": "#{@user.id}"
159 | }, headers: { Authorization: @token.result }
160 |
161 | assert_response :success
162 | assert response.parsed_body["access_token"] != "bad_verification_code"
163 | end
164 | end
165 |
166 | test "should not authenticate with bad verification code" do
167 | RestClient.stub :post, "access_token=bad_verification_code&something_else=anothervalue" do
168 | post "/request_github_token", params: {
169 | "code": "code123",
170 | "id": "#{@user.id}"
171 | }, headers: { Authorization: @token.result }
172 |
173 | assert_response :bad_request
174 | end
175 | end
176 |
177 | test "should not authenticate with another id" do
178 | RestClient.stub :post, "access_token=token123&something_else=anothervalue" do
179 |
180 | exception = assert_raises ActiveRecord::RecordNotFound do
181 | post "/request_github_token", params: {
182 | "code": "code123",
183 | "id": "2"
184 | }, headers: { Authorization: @token.result }
185 |
186 | assert_response 500
187 | end
188 | assert_equal("Couldn't find User with 'id'=2", exception.message)
189 | end
190 | end
191 |
192 | test "should render unprocessable entity" do
193 | RestClient.stub :post, "access_token=&something_else=anothervalue" do
194 | post "/request_github_token", params: {
195 | "code": "code123",
196 | "id": "#{@user.id}"
197 | }, headers: { Authorization: @token.result }
198 |
199 | assert_response :bad_request
200 | end
201 | end
202 |
203 | test "should remove github token" do
204 | post "/remove_github_token", params: {
205 | "id": "#{@user.id}"
206 | }, headers: { Authorization: @token.result }
207 |
208 | assert_response :success
209 | end
210 |
211 | test "should not remove github token without authentication" do
212 | post "/remove_github_token", params: {
213 | "id": "#{@user.id}"
214 | }
215 |
216 | assert_response :unauthorized
217 | end
218 | end
219 |
--------------------------------------------------------------------------------
/test/controllers/releases_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ReleasesControllerTest < ActionDispatch::IntegrationTest
4 | def setup
5 | @user = User.create(
6 | name: "Robert",
7 | email: "robert@email.com",
8 | password: "123123",
9 | password_confirmation: "123123"
10 | )
11 |
12 | @project = Project.create(
13 | name: "Falko",
14 | description: "Description.",
15 | user_id: @user.id,
16 | is_project_from_github: true,
17 | is_scoring: false
18 | )
19 |
20 | @release = Release.create(
21 | name: "R1",
22 | description: "Description",
23 | initial_date: "2018-01-01",
24 | final_date: "2019-01-01",
25 | amount_of_sprints: "20",
26 | project_id: @project.id
27 | )
28 |
29 | @token = AuthenticateUser.call(@user.email, @user.password)
30 |
31 | @another_user = User.create(name: "Ronaldo",
32 | email: "ronaldo@email.com",
33 | password: "123123",
34 | password_confirmation: "123123"
35 | )
36 |
37 | @another_project = Project.create(
38 | name: "Futebol",
39 | description: "Description.",
40 | user_id: @another_user.id,
41 | is_project_from_github: true
42 | )
43 |
44 | @another_release = Release.create(
45 | name: "Real Madrid",
46 | description: "Descriptions",
47 | initial_date: "2018-01-01",
48 | final_date: "2019-01-01",
49 | amount_of_sprints: "20",
50 | project_id: @another_project.id
51 | )
52 |
53 | @another_token = AuthenticateUser.call(@another_user.email, @another_user.password)
54 | end
55 |
56 | test "should create release" do
57 | post "/projects/#{@project.id}/releases", params: {
58 | "release": {
59 | "name": "Release 01",
60 | "description": "First Release",
61 | "amount_of_sprints": "20",
62 | "initial_date": "2018-01-01",
63 | "final_date": "2019-01-01"
64 | }
65 | }, headers: { Authorization: @token.result }
66 |
67 | assert_response :created
68 | end
69 |
70 | test "should not create release without correct params" do
71 | # Final date before initial date
72 | post "/projects/#{@project.id}/releases", params: {
73 | "release": {
74 | "name": "Release 01",
75 | "description": "First Release",
76 | "amount_of_sprints": "20",
77 | "initial_date": "2018-01-01",
78 | "final_date": "1900-01-01"
79 | }
80 | }, headers: { Authorization: @token.result }
81 |
82 | assert_response :unprocessable_entity
83 | end
84 |
85 |
86 | test "should not create release without authentication" do
87 | post "/projects/#{@project.id}/releases", params: {
88 | "release": {
89 | "name": "Release 01",
90 | "description": "First Release",
91 | "amount_of_sprints": "20",
92 | "initial_date": "2018-01-01",
93 | "final_date": "2019-01-01"
94 | }
95 | }
96 |
97 | assert_response :unauthorized
98 | end
99 |
100 | test "should not get releases index without authentication" do
101 | get "/projects/#{@project.id}/releases"
102 | assert_response :unauthorized
103 | end
104 |
105 | test "should get releases index" do
106 | get "/projects/#{@project.id}/releases", headers: { Authorization: @token.result }
107 | assert_response :success
108 | end
109 |
110 | test "should not get releases show without authentication" do
111 | get "/releases/#{@release.id}"
112 | assert_response :unauthorized
113 | end
114 |
115 | test "should get releases show" do
116 | get "/releases/#{@release.id}", headers: { Authorization: @token.result }
117 | assert_response :success
118 | end
119 |
120 | test "should edit releases" do
121 | @old_name_release = @release.name
122 | @old_description_release = @release.description
123 | @old_initial_date_release = @release.initial_date
124 |
125 | patch "/releases/#{@release.id}", params: {
126 | release: {
127 | name: "Daniboy",
128 | description: "CBlacku",
129 | initial_date: "2010-05-06"
130 | }
131 | }, headers: { Authorization: @token.result }
132 |
133 | @release.reload
134 |
135 | assert_response :ok
136 | assert_not_equal @old_name_release, @release.name
137 | assert_not_equal @old_description_release, @release.description
138 | assert_not_equal @old_initial_date_release, @release.initial_date
139 | end
140 |
141 | test "should not edit releases without authenticantion" do
142 | @old_name_release = @release.name
143 | @old_description_release = @release.description
144 | @old_initial_date_release = @release.initial_date
145 |
146 | patch "/releases/#{@release.id}", params: {
147 | release: {
148 | name: "Daniboy",
149 | description: "CBlacku",
150 | initial_date: "2010-05-06"
151 | }
152 | }
153 |
154 | @release.reload
155 |
156 | assert_response :unauthorized
157 | assert_equal @old_name_release, @release.name
158 | assert_equal @old_description_release, @release.description
159 | assert_equal @old_initial_date_release, @release.initial_date
160 | end
161 |
162 | test "should not edit releases with blank params" do
163 | @old_name_release = @release.name
164 | @old_description_release = @release.description
165 | @old_initial_date_release = @release.initial_date
166 |
167 | patch "/releases/#{@release.id}", params: {
168 | release: {
169 | name: "",
170 | description: "",
171 | initial_date: ""
172 | }
173 | }, headers: { Authorization: @token.result }
174 |
175 | @release.reload
176 |
177 | assert_response :unprocessable_entity
178 | assert_equal @old_name_release, @release.name
179 | assert_equal @old_description_release, @release.description
180 | assert_equal @old_initial_date_release, @release.initial_date
181 | end
182 |
183 | test "should destroy release" do
184 | assert_difference("Release.count", -1) do
185 | delete "/releases/#{@release.id}", headers: { Authorization: @token.result }
186 | end
187 |
188 | assert_response :no_content
189 | end
190 |
191 | test "should not destroy release without authentication" do
192 | assert_no_difference "Release.count" do
193 | delete "/releases/#{@release.id}"
194 | end
195 |
196 | assert_response :unauthorized
197 | end
198 |
199 | test "should not destroy release of another user" do
200 | delete "/releases/#{@release.id}", headers: { Authorization: @another_token.result }
201 |
202 | assert_response :unauthorized
203 | end
204 | end
205 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (5.1.6)
5 | actionpack (= 5.1.6)
6 | nio4r (~> 2.0)
7 | websocket-driver (~> 0.6.1)
8 | actionmailer (5.1.6)
9 | actionpack (= 5.1.6)
10 | actionview (= 5.1.6)
11 | activejob (= 5.1.6)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.1.6)
15 | actionview (= 5.1.6)
16 | activesupport (= 5.1.6)
17 | rack (~> 2.0)
18 | rack-test (>= 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.1.6)
22 | activesupport (= 5.1.6)
23 | builder (~> 3.1)
24 | erubi (~> 1.4)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.1.6)
28 | activesupport (= 5.1.6)
29 | globalid (>= 0.3.6)
30 | activemodel (5.1.6)
31 | activesupport (= 5.1.6)
32 | activerecord (5.1.6)
33 | activemodel (= 5.1.6)
34 | activesupport (= 5.1.6)
35 | arel (~> 8.0)
36 | activesupport (5.1.6)
37 | concurrent-ruby (~> 1.0, >= 1.0.2)
38 | i18n (>= 0.7, < 2)
39 | minitest (~> 5.1)
40 | tzinfo (~> 1.1)
41 | addressable (2.4.0)
42 | ansi (1.5.0)
43 | apipie-rails (0.5.15)
44 | rails (>= 4.1)
45 | arel (8.0.0)
46 | ast (2.4.0)
47 | backports (3.11.3)
48 | bcrypt (3.1.11)
49 | builder (3.2.3)
50 | byebug (10.0.2)
51 | carrierwave (1.2.2)
52 | activemodel (>= 4.0.0)
53 | activesupport (>= 4.0.0)
54 | mime-types (>= 1.16)
55 | carrierwave-base64 (2.7.0)
56 | carrierwave (>= 0.8.0)
57 | mime-types (~> 3.0)
58 | codeclimate-test-reporter (1.0.8)
59 | simplecov (<= 0.13)
60 | concurrent-ruby (1.0.5)
61 | crass (1.0.4)
62 | devise (4.4.3)
63 | bcrypt (~> 3.0)
64 | orm_adapter (~> 0.1)
65 | railties (>= 4.1.0, < 6.0)
66 | responders
67 | warden (~> 1.2.3)
68 | docile (1.1.5)
69 | domain_name (0.5.20180417)
70 | unf (>= 0.0.5, < 1.0.0)
71 | erubi (1.7.1)
72 | ethon (0.11.0)
73 | ffi (>= 1.3.0)
74 | faraday (0.15.0)
75 | multipart-post (>= 1.2, < 3)
76 | faraday_middleware (0.12.2)
77 | faraday (>= 0.7.4, < 1.0)
78 | ffi (1.9.23)
79 | gh (0.15.1)
80 | addressable (~> 2.4.0)
81 | backports
82 | faraday (~> 0.8)
83 | multi_json (~> 1.0)
84 | net-http-persistent (~> 2.9)
85 | net-http-pipeline
86 | globalid (0.4.1)
87 | activesupport (>= 4.2.0)
88 | highline (1.7.10)
89 | http-cookie (1.0.3)
90 | domain_name (~> 0.5)
91 | i18n (1.0.1)
92 | concurrent-ruby (~> 1.0)
93 | json (2.1.0)
94 | jwt (1.5.6)
95 | launchy (2.4.3)
96 | addressable (~> 2.3)
97 | listen (3.1.5)
98 | rb-fsevent (~> 0.9, >= 0.9.4)
99 | rb-inotify (~> 0.9, >= 0.9.7)
100 | ruby_dep (~> 1.2)
101 | loofah (2.2.2)
102 | crass (~> 1.0.2)
103 | nokogiri (>= 1.5.9)
104 | mail (2.7.0)
105 | mini_mime (>= 0.1.1)
106 | method_source (0.9.0)
107 | mime-types (3.1)
108 | mime-types-data (~> 3.2015)
109 | mime-types-data (3.2016.0521)
110 | mini_mime (1.0.0)
111 | mini_portile2 (2.3.0)
112 | minitest (5.11.3)
113 | minitest-reporters (1.2.0)
114 | ansi
115 | builder
116 | minitest (>= 5.0)
117 | ruby-progressbar
118 | multi_json (1.13.1)
119 | multipart-post (2.0.0)
120 | net-http-persistent (2.9.4)
121 | net-http-pipeline (1.0.1)
122 | netrc (0.11.0)
123 | nio4r (2.3.1)
124 | nokogiri (1.8.2)
125 | mini_portile2 (~> 2.3.0)
126 | octokit (4.8.0)
127 | sawyer (~> 0.8.0, >= 0.5.3)
128 | orm_adapter (0.5.0)
129 | parallel (1.12.1)
130 | parser (2.5.1.0)
131 | ast (~> 2.4.0)
132 | pg (1.0.0)
133 | powerpack (0.1.1)
134 | puma (3.11.4)
135 | pusher-client (0.6.2)
136 | json
137 | websocket (~> 1.0)
138 | rack (2.0.5)
139 | rack-cors (1.0.2)
140 | rack-test (1.0.0)
141 | rack (>= 1.0, < 3)
142 | rails (5.1.6)
143 | actioncable (= 5.1.6)
144 | actionmailer (= 5.1.6)
145 | actionpack (= 5.1.6)
146 | actionview (= 5.1.6)
147 | activejob (= 5.1.6)
148 | activemodel (= 5.1.6)
149 | activerecord (= 5.1.6)
150 | activesupport (= 5.1.6)
151 | bundler (>= 1.3.0)
152 | railties (= 5.1.6)
153 | sprockets-rails (>= 2.0.0)
154 | rails-dom-testing (2.0.3)
155 | activesupport (>= 4.2.0)
156 | nokogiri (>= 1.6)
157 | rails-html-sanitizer (1.0.4)
158 | loofah (~> 2.2, >= 2.2.2)
159 | rails_12factor (0.0.3)
160 | rails_serve_static_assets
161 | rails_stdout_logging
162 | rails_serve_static_assets (0.0.5)
163 | rails_stdout_logging (0.0.5)
164 | railties (5.1.6)
165 | actionpack (= 5.1.6)
166 | activesupport (= 5.1.6)
167 | method_source
168 | rake (>= 0.8.7)
169 | thor (>= 0.18.1, < 2.0)
170 | rainbow (3.0.0)
171 | rake (12.3.1)
172 | rb-fsevent (0.10.3)
173 | rb-inotify (0.9.10)
174 | ffi (>= 0.5.0, < 2)
175 | responders (2.4.0)
176 | actionpack (>= 4.2.0, < 5.3)
177 | railties (>= 4.2.0, < 5.3)
178 | rest-client (2.0.2)
179 | http-cookie (>= 1.0.2, < 2.0)
180 | mime-types (>= 1.16, < 4.0)
181 | netrc (~> 0.8)
182 | rubocop (0.56.0)
183 | parallel (~> 1.10)
184 | parser (>= 2.5)
185 | powerpack (~> 0.1)
186 | rainbow (>= 2.2.2, < 4.0)
187 | ruby-progressbar (~> 1.7)
188 | unicode-display_width (~> 1.0, >= 1.0.1)
189 | ruby-progressbar (1.9.0)
190 | ruby_dep (1.5.0)
191 | sawyer (0.8.1)
192 | addressable (>= 2.3.5, < 2.6)
193 | faraday (~> 0.8, < 1.0)
194 | simple_command (0.0.9)
195 | simple_token_authentication (1.15.1)
196 | actionmailer (>= 3.2.6, < 6)
197 | actionpack (>= 3.2.6, < 6)
198 | devise (>= 3.2, < 6)
199 | simplecov (0.13.0)
200 | docile (~> 1.1.0)
201 | json (>= 1.8, < 3)
202 | simplecov-html (~> 0.10.0)
203 | simplecov-html (0.10.2)
204 | spring (2.0.2)
205 | activesupport (>= 4.2)
206 | spring-watcher-listen (2.0.1)
207 | listen (>= 2.7, < 4.0)
208 | spring (>= 1.2, < 3.0)
209 | sprockets (3.7.1)
210 | concurrent-ruby (~> 1.0)
211 | rack (> 1, < 3)
212 | sprockets-rails (3.2.1)
213 | actionpack (>= 4.0)
214 | activesupport (>= 4.0)
215 | sprockets (>= 3.0.0)
216 | thor (0.20.0)
217 | thread_safe (0.3.6)
218 | travis (1.8.8)
219 | backports
220 | faraday (~> 0.9)
221 | faraday_middleware (~> 0.9, >= 0.9.1)
222 | gh (~> 0.13)
223 | highline (~> 1.6)
224 | launchy (~> 2.1)
225 | pusher-client (~> 0.4)
226 | typhoeus (~> 0.6, >= 0.6.8)
227 | typhoeus (0.8.0)
228 | ethon (>= 0.8.0)
229 | tzinfo (1.2.5)
230 | thread_safe (~> 0.1)
231 | unf (0.1.4)
232 | unf_ext
233 | unf_ext (0.0.7.5)
234 | unicode-display_width (1.3.2)
235 | warden (1.2.7)
236 | rack (>= 1.0)
237 | websocket (1.2.5)
238 | websocket-driver (0.6.5)
239 | websocket-extensions (>= 0.1.0)
240 | websocket-extensions (0.1.3)
241 |
242 | PLATFORMS
243 | ruby
244 |
245 | DEPENDENCIES
246 | activesupport (~> 5.1, >= 5.1.4)
247 | apipie-rails (~> 0.5.11)
248 | bcrypt (~> 3.1.7)
249 | byebug
250 | carrierwave (~> 1.1)
251 | carrierwave-base64 (~> 2.5, >= 2.5.3)
252 | codeclimate-test-reporter
253 | jwt (~> 1.5.6)
254 | listen (>= 3.0.5, < 3.2)
255 | minitest (~> 5.8, >= 5.8.4)
256 | minitest-reporters
257 | octokit (~> 4.0)
258 | pg (~> 1.0.0)
259 | puma (~> 3.7)
260 | rack-cors
261 | rails (~> 5.1.6)
262 | rails_12factor
263 | rest-client
264 | rubocop
265 | simple_command
266 | simple_token_authentication (~> 1.0)
267 | simplecov
268 | spring
269 | spring-watcher-listen (~> 2.0.0)
270 | travis (~> 1.8, >= 1.8.8)
271 | tzinfo-data
272 |
273 | RUBY VERSION
274 | ruby 2.4.1p111
275 |
276 | BUNDLED WITH
277 | 1.16.2
278 |
--------------------------------------------------------------------------------