diff --git a/Gemfile b/Gemfile index bd75f3d66..8712951b3 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem 'paperclip', "~> 2.3" gem "will_paginate", "~> 3.0.2" gem 'meta-tags', '~> 1.2.4', :require => 'meta_tags' gem "russian" +gem "friendly_id", "~> 4.0.0.beta14" # gem 'ghoul_grack', '~> 0.0.1' gem 'grack', :git => 'git://github.com/rdblue/grack.git', :require => 'git_http' diff --git a/Gemfile.lock b/Gemfile.lock index 33a001a38..3bf319e35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,6 +77,7 @@ GEM factory_girl_rails (1.4.0) factory_girl (~> 2.3.0) railties (>= 3.0.0) + friendly_id (4.0.0.beta8) grit (2.4.1) diff-lcs (~> 1.1) mime-types (~> 1.15) @@ -219,6 +220,7 @@ DEPENDENCIES delayed_job devise (~> 1.5.2) factory_girl_rails (~> 1.4.0) + friendly_id (~> 4.0.0.beta14) grack! grit haml-rails (~> 0.3.4) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 000000000..50b0be70f --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,50 @@ +class CommentsController < ApplicationController + before_filter :authenticate_user! + + def index + @commentable = find_commentable + @comments = @commentable.comments + end + + def create + @commentable = find_commentable + @comment = @commentable.comments.build(params[:comment]) + if @comment.save + flash[:notice] = I18n.t("flash.comment.saved") + redirect_to :id => nil + else + flash[:error] = I18n.t("flash.comment.saved_error") + render :action => 'new' + end + end + + def update + @comment = Comment.find(params[:id]) + if @comment.update_attributes(params[:comment]) + flash[:notice] = I18n.t("flash.comment.saved") + redirect_to :id => nil + else + flash[:error] = I18n.t("flash.comment.saved_error") + render :action => 'new' + end + end + + def destroy + @comment = Comment.find(params[:id]) + @comment.destroy + + flash[:notice] = t("flash.comment.destroyed") + redirect_to root_path + end + + private + + def find_commentable + params.each do |name, value| + if name =~ /(.+)_id$/ + return $1.classify.constantize.find(value) + end + end + nil + end +end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb new file mode 100644 index 000000000..014d86dc4 --- /dev/null +++ b/app/controllers/issues_controller.rb @@ -0,0 +1,58 @@ +class IssuesController < ApplicationController + before_filter :authenticate_user! + before_filter :find_project + before_filter :find_issue, :only => [:show, :edit, :update, :destroy] + + load_and_authorize_resource + autocomplete :user, :uname + + def index + @issues = @project.issues.paginate :per_page => 10, :page => params[:page] + end + + def create + @user_id = params[:user_id] + @user_uname = params[:user_uname] + + @issue = Issue.new(params[:issue]) + @issue.user_id = @user_id + @issue.project_id = @project.id + if @issue.save! + flash[:notice] = I18n.t("flash.issue.saved") + redirect_to project_issues_path(@project) + else + flash[:error] = I18n.t("flash.issue.saved_error") + render :action => :new + end + end + + def update + @user_id = params[:user_id] + @user_uname = params[:user_uname] + + if @issue.update_attributes( params[:issue].merge({:user_id => @user_id}) ) + flash[:notice] = I18n.t("flash.issue.saved") + redirect_to @issue + else + flash[:error] = I18n.t("flash.issue.saved_error") + render :action => :new + end + end + + def destroy + @issue.destroy + + flash[:notice] = t("flash.issue.destroyed") + redirect_to root_path + end + + private + + def find_project + @project = Project.find(params[:project_id]) + end + + def find_issue + @issue = Issue.find(params[:id]) + end +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 000000000..0ec9ca5f2 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,2 @@ +module CommentsHelper +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb new file mode 100644 index 000000000..bfb9d25e5 --- /dev/null +++ b/app/helpers/issues_helper.rb @@ -0,0 +1,2 @@ +module IssuesHelper +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 000000000..2d523d32f --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,5 @@ +class Comment < ActiveRecord::Base + belongs_to :commentable + + validates :body, :commentable_id, :commentable_type, :presence => true +end diff --git a/app/models/issue.rb b/app/models/issue.rb new file mode 100644 index 000000000..f1825ab27 --- /dev/null +++ b/app/models/issue.rb @@ -0,0 +1,24 @@ +class Issue < ActiveRecord::Base + STATUSES = ['open', 'close'] + + extend FriendlyId + friendly_id :serial_id + + belongs_to :project + belongs_to :user + + has_many :comments, :as => :commentable + + validates :title, :body, :project_id, :user_id, :presence => true + + attr_readonly :serial_id + + after_create :set_serial_id + + protected + + def set_serial_id + serial_id = project.issues.count + 1 + save + end +end diff --git a/app/models/project.rb b/app/models/project.rb index f0e66c885..56e861308 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -4,6 +4,7 @@ class Project < ActiveRecord::Base belongs_to :category, :counter_cache => true belongs_to :owner, :polymorphic => true + has_many :issues, :dependent => :destroy has_many :build_lists, :dependent => :destroy has_many :auto_build_lists, :dependent => :destroy diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml new file mode 100644 index 000000000..65d4e0704 --- /dev/null +++ b/app/views/comments/_form.html.haml @@ -0,0 +1,10 @@ +.group + = f.label :body, :class => :label + = f.text_area :body, :class => 'text_field', :cols => 80 + +.group.navform.wat-cf + %button.button{:type => "submit"} + = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) + = t("layout.save") + %span.text_button_padding= t("layout.or") + = link_to t("layout.cancel"), project_issue_comment_path(@issue.project, @issue), :class => "text_button_padding link_button" diff --git a/app/views/comments/edit.html.haml b/app/views/comments/edit.html.haml new file mode 100644 index 000000000..516ddd476 --- /dev/null +++ b/app/views/comments/edit.html.haml @@ -0,0 +1,10 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to t("layout.issues.list"), project_issue_path(@project, @issue) + .content + %h2.title + = t("layout.issues.edit_header") + .inner + = form_for @comment, :url => project_issue_comment_path(@project, @issue, @comment), :html => { :class => :form } do |f| + = render :partial => "form", :locals => {:f => f} diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml new file mode 100644 index 000000000..bb1bc72ed --- /dev/null +++ b/app/views/issues/_form.html.haml @@ -0,0 +1,25 @@ += javascript_include_tag "autocomplete-rails.js" + +.group + = f.label :title, :class => :label + = f.text_field :title, :class => 'text_field' + +.group + = f.label :body, :class => :label + = f.text_area :body, :class => 'text_field', :cols => 80 + +.group + = f.label :status, :class => :label + = f.select :status, Issue::STATUSES, :class => 'text_field' + +.group + = label_tag "", t("layout.issues.user_id"), :class => :label + = autocomplete_field_tag 'user_id', @user_uname, autocomplete_user_uname_platforms_path, :id_element => '#user_id_field' + = hidden_field_tag 'user_id', @user_id, :id => 'user_id_field' + +.group.navform.wat-cf + %button.button{:type => "submit"} + = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) + = t("layout.save") + %span.text_button_padding= t("layout.or") + = link_to t("layout.cancel"), project_path(@project), :class => "text_button_padding link_button" diff --git a/app/views/issues/_list.html.haml b/app/views/issues/_list.html.haml new file mode 100644 index 000000000..e0a9757ee --- /dev/null +++ b/app/views/issues/_list.html.haml @@ -0,0 +1,15 @@ +%table.table + %tr + %th.first= t("activerecord.attributes.issue.title") + %th.first= t("activerecord.attributes.issue.status") + %th.last   + - @project.issues.each do |issue| + %tr{:class => cycle("odd", "even")} + %td + = link_to issue.title, project_issue_path(@project, issue) + %td + = issue.status + %td.last + = link_to t("layout.show"), project_issue_path(@project, issue) + | + = link_to t("layout.delete"), project_issue_path(@project, issue), :method => :delete, :confirm => t("layout.issues.confirm_delete") if can? :destroy, issue diff --git a/app/views/issues/edit.html.haml b/app/views/issues/edit.html.haml new file mode 100644 index 000000000..99a3533b0 --- /dev/null +++ b/app/views/issues/edit.html.haml @@ -0,0 +1,12 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to t("layout.issues.list"), project_issues_path(@project) + %li= link_to t("layout.issues.new"), new_project_issue_path(@project) + %li.active= link_to t("layout.issues.edit"), edit_project_issue_path(@project) + .content + %h2.title + = t("layout.issues.edit_header") + .inner + = form_for @issue, :url => project_issue_path(@issue), :html => { :class => :form } do |f| + = render :partial => "form", :locals => {:f => f} diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml new file mode 100644 index 000000000..9b8661458 --- /dev/null +++ b/app/views/issues/index.html.haml @@ -0,0 +1,14 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first.active= link_to t("layout.issues.list"), project_issues_path(@project) + %li= link_to t("layout.issues.new"), new_project_issue_path(@project) if can? :create, Issue + .content + %h2.title + = t("layout.issues.list_header") + .inner + = render :partial => 'shared/search_form' + = render :partial => 'issues/list', :object => @issues + .actions-bar.wat-cf + .actions + = will_paginate @issues, :param_name => :issue_page diff --git a/app/views/issues/new.html.haml b/app/views/issues/new.html.haml new file mode 100644 index 000000000..cb94f0ac4 --- /dev/null +++ b/app/views/issues/new.html.haml @@ -0,0 +1,11 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to "#{t("layout.issues.list")}", project_issues_path(@project) + %li.active= link_to "#{t("layout.issues.new")}", new_project_issue_path(@project) + .content + %h2.title + = t("layout.issues.new_header") + .inner + = form_for :issue, :url => project_issues_path(@project), :html => { :class => :form } do |f| + = render :partial => "form", :locals => {:f => f} diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml new file mode 100644 index 000000000..5092edc54 --- /dev/null +++ b/app/views/issues/show.html.haml @@ -0,0 +1,53 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to t("layout.issues.list"), project_issues_path(@project) + %li= link_to t("layout.issues.edit"), edit_project_issue_path(@project, @issue) if can? :edit, @issue + .content + .inner + %p + %b + = t("activerecord.attributes.issue.title") + \: + = @issue.title + %p + %b + = t("activerecord.attributes.issue.body") + \: + = @issue.body + %p + %b + = t('layout.issues.status') + \: + = @issue.status + +%a{ :name => "comments" } +.block + .secondary-navigation + %ul.wat-cf + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %table.table + %tr + %th.first= t("activerecord.attributes.user.uname") + %th.first= t("activerecord.attributes.comment.body") + %th.last   + - @issue.comments.each do |comment| + %tr{:class => cycle("odd", "even")} + %td + = comment.user.uname + %td + = comment.body + %td.last + = link_to t("layout.edit"), edit_project_issue_comment_path(@project, @issue, comment) if can? :update, comment + = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_issue_comment_path(@project, @issue, comment), :method => "delete", :class => "button", :confirm => t("layout.comment.confirm_delete") if can? :delete, comment + +.block + .content + %h2.title + = t("layout.comments.new_header") + .inner + = form_for :comment, :url => project_issue_comments_path(@project, @issue), :html => { :class => :form } do |f| + = render :partial => "comments/form", :locals => {:f => f} diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 3930d97a2..7bbc69176 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -6,6 +6,7 @@ %li.active= link_to t("layout.projects.show"), project_path(@project) %li= link_to t("layout.git.repositories.source"), project_repo_path(@project) %li= link_to t("layout.projects.build"), build_project_path(@project) + %li= link_to t("layout.projects.issues"), project_issues_path(@project) .content .inner diff --git a/config/routes.rb b/config/routes.rb index 5d22a7cfe..721c6f254 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,6 +68,9 @@ Rosa::Application.routes.draw do end resources :projects do + resources :issues do + resources :comments, :only => [:edit, :create, :update, :destroy] + end resource :repo, :controller => "git/repositories", :only => [:show] resources :build_lists, :only => [:index, :show] do collection do diff --git a/db/migrate/20111216134039_create_issues.rb b/db/migrate/20111216134039_create_issues.rb new file mode 100644 index 000000000..8d6a47315 --- /dev/null +++ b/db/migrate/20111216134039_create_issues.rb @@ -0,0 +1,20 @@ +class CreateIssues < ActiveRecord::Migration + def self.up + create_table :issues do |t| + t.integer :serial_id + t.integer :project_id + t.integer :user_id + t.string :title + t.text :body + t.string :status + + t.timestamps + end + + add_index :issues, [:project_id, :serial_id], :unique => true + end + + def self.down + drop_table :issues + end +end diff --git a/db/migrate/20111216140849_create_comments.rb b/db/migrate/20111216140849_create_comments.rb new file mode 100644 index 000000000..d05b53eb7 --- /dev/null +++ b/db/migrate/20111216140849_create_comments.rb @@ -0,0 +1,16 @@ +class CreateComments < ActiveRecord::Migration + def self.up + create_table :comments do |t| + t.integer :commentable_id + t.string :commentable_type + t.integer :user_id + t.text :body + + t.timestamps + end + end + + def self.down + drop_table :comments + end +end diff --git a/db/migrate/20111219073859_add_has_issues_to_projects.rb b/db/migrate/20111219073859_add_has_issues_to_projects.rb new file mode 100644 index 000000000..f0ea1e42c --- /dev/null +++ b/db/migrate/20111219073859_add_has_issues_to_projects.rb @@ -0,0 +1,9 @@ +class AddHasIssuesToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :has_issues, :boolean, :default => true + end + + def self.down + remove_column :projects, :has_issues + end +end diff --git a/db/schema.rb b/db/schema.rb index 17c70bd11..5acde5031 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20111130181101) do +ActiveRecord::Schema.define(:version => 20111219073859) do create_table "arches", :force => true do |t| t.string "name", :null => false @@ -83,6 +83,15 @@ ActiveRecord::Schema.define(:version => 20111130181101) do t.datetime "updated_at" end + create_table "comments", :force => true do |t| + t.integer "commentable_id" + t.string "commentable_type" + t.integer "user_id" + t.text "body" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "containers", :force => true do |t| t.string "name", :null => false t.integer "project_id", :null => false @@ -140,6 +149,19 @@ ActiveRecord::Schema.define(:version => 20111130181101) do t.string "uname" end + create_table "issues", :force => true do |t| + t.integer "serial_id" + t.integer "project_id" + t.integer "user_id" + t.string "title" + t.text "body" + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "issues", ["project_id", "serial_id"], :name => "index_issues_on_project_id_and_serial_id", :unique => true + create_table "platforms", :force => true do |t| t.string "description" t.string "name" @@ -211,6 +233,7 @@ ActiveRecord::Schema.define(:version => 20111130181101) do t.integer "category_id" t.text "description" t.string "ancestry" + t.boolean "has_issues", :default => true end add_index "projects", ["category_id"], :name => "index_projects_on_category_id" diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb new file mode 100644 index 000000000..562bf1aff --- /dev/null +++ b/spec/controllers/comments_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe CommentsController do + +end diff --git a/spec/controllers/issues_controller_spec.rb b/spec/controllers/issues_controller_spec.rb new file mode 100644 index 000000000..c27154bda --- /dev/null +++ b/spec/controllers/issues_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe IssuesController do + +end diff --git a/spec/factories/comments.rb b/spec/factories/comments.rb new file mode 100644 index 000000000..f9df3b695 --- /dev/null +++ b/spec/factories/comments.rb @@ -0,0 +1,6 @@ +# Read about factories at http://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :comment do + end +end \ No newline at end of file diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb new file mode 100644 index 000000000..5e4c566b7 --- /dev/null +++ b/spec/factories/issues.rb @@ -0,0 +1,6 @@ +# Read about factories at http://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :issue do + end +end \ No newline at end of file diff --git a/spec/helpers/comments_helper_spec.rb b/spec/helpers/comments_helper_spec.rb new file mode 100644 index 000000000..cc93aa9b6 --- /dev/null +++ b/spec/helpers/comments_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the CommentsHelper. For example: +# +# describe CommentsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe CommentsHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb new file mode 100644 index 000000000..2e20f1cf1 --- /dev/null +++ b/spec/helpers/issues_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the IssuesHelper. For example: +# +# describe IssuesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe IssuesHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb new file mode 100644 index 000000000..505e33d36 --- /dev/null +++ b/spec/models/comment_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Comment do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb new file mode 100644 index 000000000..881318921 --- /dev/null +++ b/spec/models/issue_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Issue do + pending "add some examples to (or delete) #{__FILE__}" +end