diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2012-04-09 00:28:58 +0300 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2012-04-09 00:28:58 +0300 |
commit | 23d950855d6d2524d00b1f0618c008e2529f06a4 (patch) | |
tree | adbf9d77a4ec9e437d285266de6892e0f17b960b /app | |
parent | 667edcdd7534206761fc9524e6eaa17f1c27b815 (diff) | |
download | gitlab-ce-23d950855d6d2524d00b1f0618c008e2529f06a4.tar.gz |
Milestone basic scaffold
Diffstat (limited to 'app')
23 files changed, 385 insertions, 30 deletions
diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index ded66b1c540..19de614fd62 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -2,6 +2,7 @@ function switchToNewIssue(form){ $(".issues_content").hide("fade", { direction: "left" }, 150, function(){ $(".issues_content").after(form); $('select#issue_assignee_id').chosen(); + $('select#issue_milestone_id').chosen(); $("#new_issue_dialog").show("fade", { direction: "right" }, 150); $('.top-tabs .add_new').hide(); }); @@ -11,6 +12,7 @@ function switchToEditIssue(form){ $(".issues_content").hide("fade", { direction: "left" }, 150, function(){ $(".issues_content").after(form); $('select#issue_assignee_id').chosen(); + $('select#issue_milestone_id').chosen(); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $('.add_new').hide(); }); diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index ee466348d7e..89729e5ca8f 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -45,6 +45,13 @@ a { &:hover { } + + &.primary { + background:$link_color; + &:hover { + background:$blue_link; + } + } } a:focus { diff --git a/app/assets/stylesheets/jquery_ui.scss b/app/assets/stylesheets/jquery_ui.scss new file mode 100644 index 00000000000..70401539ac1 --- /dev/null +++ b/app/assets/stylesheets/jquery_ui.scss @@ -0,0 +1,31 @@ +/** + * JQUERY UI datepicker + * + */ +.ui-datepicker { + border-color:#eee; + padding:20px; + + .ui-state-default { + background:#f1f1f1; + padding:5px; + } + .ui-state-active { + background:#fff; + } +} + +/** + * JQUERY UI progressbar + * + */ +.ui-progressbar { + border:1px solid #ddd; + height:6px; + + .ui-progressbar-value { + background-color: #62C462;//$blue_link; + margin:0; + } +} + diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 10388bcf223..4fec0633d86 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -116,3 +116,9 @@ $hover: #FDF5D9; * */ @import "highlight.black.scss"; + +/** + * JQUERY UI ext + * + */ +@import "jquery_ui.scss"; diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 8736427873b..64be20ff503 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -15,7 +15,7 @@ class IssuesController < ApplicationController before_filter :authorize_write_issue!, :only => [:new, :create] # Allow modify issue - before_filter :authorize_modify_issue!, :only => [:close, :edit, :update, :sort] + before_filter :authorize_modify_issue!, :only => [:close, :edit, :update] # Allow destroy issue before_filter :authorize_admin_issue!, :only => [:destroy] @@ -28,8 +28,10 @@ class IssuesController < ApplicationController when 2 then @project.issues.closed when 3 then @project.issues.opened.assigned(current_user) else @project.issues.opened - end.page(params[:page]).per(20) + end + @issues = @issues.where(:milestone_id => params[:milestone_id]) if params[:milestone_id].present? + @issues = @issues.page(params[:page]).per(20) @issues = @issues.includes(:author, :project).order("critical, updated_at") respond_to do |format| @@ -51,13 +53,6 @@ class IssuesController < ApplicationController def show @note = @project.notes.new(:noteable => @issue) - @commits = if @issue.branch_name && @project.repo.heads.map(&:name).include?(@issue.branch_name) - @project.repo.commits_between("master", @issue.branch_name) - else - [] - end - - respond_to do |format| format.html format.js @@ -102,6 +97,8 @@ class IssuesController < ApplicationController end def sort + return render_404 unless can?(current_user, :admin_issue, @project) + @issues = @project.issues.where(:id => params['issue']) @issues.each do |issue| issue.position = params['issue'].index(issue.id.to_s) + 1 diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb new file mode 100644 index 00000000000..5e23426f32b --- /dev/null +++ b/app/controllers/milestones_controller.rb @@ -0,0 +1,94 @@ +class MilestonesController < ApplicationController + before_filter :authenticate_user! + before_filter :project + before_filter :module_enabled + before_filter :milestone, :only => [:edit, :update, :destroy, :show] + layout "project" + + # Authorize + before_filter :add_project_abilities + + # Allow read any milestone + before_filter :authorize_read_milestone! + + # Allow admin milestone + before_filter :authorize_admin_milestone!, :except => [:index, :show] + + respond_to :html + + def index + @milestones = case params[:f].to_i + when 1; @project.milestones + else @project.milestones.active + end + + @milestones = @milestones.includes(:project).order("due_date") + @milestones = @milestones.page(params[:page]).per(20) + end + + def new + @milestone = @project.milestones.new + respond_with(@milestone) + end + + def edit + respond_with(@milestone) + end + + def show + respond_to do |format| + format.html + format.js + end + end + + def create + @milestone = @project.milestones.new(params[:milestone]) + + if @milestone.save + redirect_to project_milestone_path(@project, @milestone) + else + render "new" + end + end + + def update + @milestone.update_attributes(params[:milestone]) + + respond_to do |format| + format.js + format.html do + if @milestone.valid? + redirect_to [@project, @milestone] + else + render :edit + end + end + end + end + + def destroy + return access_denied! unless can?(current_user, :admin_milestone, @milestone) + + @milestone.destroy + + respond_to do |format| + format.html { redirect_to project_milestones_path } + format.js { render :nothing => true } + end + end + + protected + + def milestone + @milestone ||= @project.milestones.find(params[:id]) + end + + def authorize_admin_milestone! + return render_404 unless can?(current_user, :admin_milestone, @project) + end + + def module_enabled + return render_404 unless @project.issues_enabled + end +end diff --git a/app/decorators/milestone_decorator.rb b/app/decorators/milestone_decorator.rb new file mode 100644 index 00000000000..c881da0e9ad --- /dev/null +++ b/app/decorators/milestone_decorator.rb @@ -0,0 +1,4 @@ +class MilestoneDecorator < ApplicationDecorator + decorates :milestone + +end diff --git a/app/models/ability.rb b/app/models/ability.rb index e97b662b8ce..5792948fa51 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -17,6 +17,7 @@ class Ability :read_project, :read_wiki, :read_issue, + :read_milestone, :read_snippet, :read_team_member, :read_merge_request, @@ -42,6 +43,7 @@ class Ability :modify_merge_request, :admin_project, :admin_issue, + :admin_milestone, :admin_snippet, :admin_team_member, :admin_merge_request, diff --git a/app/models/issue.rb b/app/models/issue.rb index ce4944a60b7..5ca257960a2 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1,5 +1,6 @@ class Issue < ActiveRecord::Base belongs_to :project + belongs_to :milestone belongs_to :author, :class_name => "User" belongs_to :assignee, :class_name => "User" has_many :notes, :as => :noteable, :dependent => :destroy diff --git a/app/models/milestone.rb b/app/models/milestone.rb new file mode 100644 index 00000000000..20bc2486e2f --- /dev/null +++ b/app/models/milestone.rb @@ -0,0 +1,29 @@ +class Milestone < ActiveRecord::Base + belongs_to :project + has_many :issues + + validates_presence_of :project_id + validates_presence_of :title + + def self.active + where("due_date > ? ", Date.today) + end + + def percent_complete + @percent_complete ||= begin + total_i = self.issues.count + closed_i = self.issues.closed.count + if total_i > 0 + (closed_i * 100) / total_i + else + 100 + end + rescue => ex + 0 + end + end + + def expires_at + "expires at #{due_date.stamp("Aug 21, 2011")}" if due_date + end +end diff --git a/app/models/project.rb b/app/models/project.rb index e2646ead503..72cc833c253 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -12,6 +12,7 @@ class Project < ActiveRecord::Base has_many :events, :dependent => :destroy has_many :merge_requests, :dependent => :destroy has_many :issues, :dependent => :destroy, :order => "position" + has_many :milestones, :dependent => :destroy has_many :users_projects, :dependent => :destroy has_many :notes, :dependent => :destroy has_many :snippets, :dependent => :destroy diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 543bab1f7c0..4698bed21a5 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -9,18 +9,25 @@ %li= msg .clearfix - = f.label :title, "Issue Subject" - .input= f.text_field :title, :maxlength => 255, :class => "xxlarge" - + = f.label :title, "Issue Subject *" + .input + = f.text_field :title, :maxlength => 255, :class => "xxlarge" + + .clearfix + = f.label :assignee_id, "Assign to *" + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Assign to user" }) + .clearfix = f.label :description, "Issue Details" .input = f.text_area :description, :maxlength => 2000, :class => "xxlarge", :rows => 10 %p.hint Markdown is enabled. + .clearfix - = f.label :assignee_id - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Assign to user" }) + = f.label :milestone_id + .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { :include_blank => "Select milestone" }) + .clearfix = f.label :critical, "Critical" diff --git a/app/views/issues/_head.html.haml b/app/views/issues/_head.html.haml index d539025296a..9fede71d59f 100644 --- a/app/views/issues/_head.html.haml +++ b/app/views/issues/_head.html.haml @@ -1,4 +1,7 @@ .tabs %li{:class => "#{'active' if current_page?(project_issues_path(@project))}"} = link_to project_issues_path(@project), :class => "tab" do - Issues + Browse Issues + %li{:class => "#{'active' if current_page?(project_milestones_path(@project))}"} + = link_to project_milestones_path(@project), :class => "tab" do + Milestones diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml index f82aee94a8f..ee5dc79585f 100644 --- a/app/views/issues/_issues.html.haml +++ b/app/views/issues/_issues.html.haml @@ -10,3 +10,6 @@ .span10= paginate @issues, :remote => true, :theme => "gitlab" .span4.right %span.cgray.right #{@issues.total_count} issues for this filter +- else + %li + %p.padded Nothing to show here diff --git a/app/views/issues/edit.html.haml b/app/views/issues/edit.html.haml index bada72459c9..aa0c931b34d 100644 --- a/app/views/issues/edit.html.haml +++ b/app/views/issues/edit.html.haml @@ -3,5 +3,6 @@ :javascript $(function(){ $('select#issue_assignee_id').chosen(); + $('select#issue_milestone_id').chosen(); }); diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index 7146027b1d5..e0afa71f4cd 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -1,3 +1,4 @@ += render "issues/head" .issues_content %h3 Issues @@ -5,14 +6,22 @@ = link_to project_issues_path(@project, :atom, { :private_token => current_user.private_token }) do = image_tag "Rss-UI.PNG", :width => 16, :title => "feed" - - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project), :class => "right btn small", :title => "New Issue", :remote => true do - New Issue + .right + .span4.left + = form_tag search_project_issues_path(@project), :method => :get, :remote => true, :id => "issue_search_form", :class => :left do + = hidden_field_tag :project_id, @project.id, { :id => 'project_id' } + = hidden_field_tag :status, params[:f] + = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search' } + + - if can? current_user, :write_issue, @project + .span2.left + = link_to new_project_issue_path(@project), :class => "right btn small", :title => "New Issue", :remote => true do + New Issue %br %div#issues-table-holder.ui-box .title .row - .span8 + .span6 %ul.pills.left %li{:class => ("active" if (params[:f] == "0" || !params[:f]))} = link_to project_issues_path(@project, :f => 0) do @@ -27,17 +36,13 @@ = link_to project_issues_path(@project, :f => 1) do All - .span3.right - = form_tag search_project_issues_path(@project), :method => :get, :remote => true, :id => "issue_search_form", :class => :right do - = hidden_field_tag :project_id, @project.id, { :id => 'project_id' } - = hidden_field_tag :status, params[:f] - = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search' } + .span6.right + = form_tag project_issues_path(@project), :method => :get, :class => :right do + = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Select milestone") %ul#issues-table.unstyled = render "issues" - - if @issues.blank? - %li - %p.padded Nothing to show here + :javascript var href = $('.issue_search').parent().attr('action'); var last_terms = ''; @@ -65,9 +70,8 @@ $('#issues-table').sortable({ axis: 'y', dropOnEmpty: false, - handle: '.handle', - cursor: 'crosshair', - items: 'tr', + handle: '.avatar', + items: 'li', opacity: 0.4, scroll: true, update: function(){ @@ -85,4 +89,8 @@ $(function(){ setSortable(); + $("#milestone_id").chosen(); + $("#milestone_id").live("change", function(){ + $(this).closest("form").submit(); + }); }); diff --git a/app/views/issues/new.html.haml b/app/views/issues/new.html.haml index bada72459c9..aa0c931b34d 100644 --- a/app/views/issues/new.html.haml +++ b/app/views/issues/new.html.haml @@ -3,5 +3,6 @@ :javascript $(function(){ $('select#issue_assignee_id').chosen(); + $('select#issue_milestone_id').chosen(); }); diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml new file mode 100644 index 00000000000..6c3e37a79ff --- /dev/null +++ b/app/views/milestones/_form.html.haml @@ -0,0 +1,54 @@ +%h3= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}" +.back_link + = link_to project_milestones_path(@project) do + ← To milestones + +%hr + += form_for [@project, @milestone] do |f| + -if @milestone.errors.any? + .alert-message.block-message.error + %ul + - @milestone.errors.full_messages.each do |msg| + %li= msg + .row + .span7 + .clearfix + = f.label :title, "Title" + .input + = f.text_field :title, :maxlength => 255, :class => "xlarge" + %p.hint Required + .clearfix + = f.label :description, "Description" + .input + = f.text_area :description, :maxlength => 2000, :class => "xlarge", :rows => 10 + %p.hint Markdown is enabled. + .span8 + .clearfix + = f.label :due_date, "Due Date" + .input= f.hidden_field :due_date + .input + .datepicker + + + .actions + - if @milestone.new_record? + = f.submit 'Create milestone', :class => "primary btn" + -else + = f.submit 'Save changes', :class => "primary btn" + + - if request.xhr? + = link_to "Cancel", "#back", :onclick => "backToIssues();", :class => "btn" + - else + - if @milestone.new_record? + = link_to "Cancel", project_milestones_path(@project), :class => "btn" + - else + = link_to "Cancel", project_milestone_path(@project, @milestone), :class => "btn" + +:javascript + $(function() { + $( ".datepicker" ).datepicker({ + dateFormat: "yy-mm-dd", + onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } + }); + }); diff --git a/app/views/milestones/_milestone.html.haml b/app/views/milestones/_milestone.html.haml new file mode 100644 index 00000000000..a21df7e2d51 --- /dev/null +++ b/app/views/milestones/_milestone.html.haml @@ -0,0 +1,21 @@ +%li{:class => "wll", :id => dom_id(milestone) } + .right + - if milestone.issues.count > 0 + = link_to 'Browse Issues', project_issues_path(milestone.project, :milestone_id => milestone.id), :class => "btn small" + - if milestone.issues.any? + %span.btn.small.disabled.padded= pluralize milestone.issues.count, 'issues' + - if can? current_user, :admin_milestone, milestone.project + = link_to 'Edit', edit_project_milestone_path(milestone.project, milestone), :class => "btn small edit-milestone-link" + = link_to project_milestone_path(milestone.project, milestone) do + %h4.row_title + = truncate(milestone.title, :length => 100) + %small= milestone.expires_at + + .progress.span4 + + :javascript + $(function() { + $( "##{dom_id(milestone)} .progress" ).progressbar({ + value: #{milestone.percent_complete} + }); + }); diff --git a/app/views/milestones/edit.html.haml b/app/views/milestones/edit.html.haml new file mode 100644 index 00000000000..bada72459c9 --- /dev/null +++ b/app/views/milestones/edit.html.haml @@ -0,0 +1,7 @@ += render "form" + +:javascript + $(function(){ + $('select#issue_assignee_id').chosen(); + }); + diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml new file mode 100644 index 00000000000..e55e58b2f23 --- /dev/null +++ b/app/views/milestones/index.html.haml @@ -0,0 +1,25 @@ += render "issues/head" +.milestones_content + %h3 + Milestones + - if can? current_user, :admin_milestone, @project + = link_to "New Milestone", new_project_milestone_path(@project), :class => "right btn small", :title => "New Milestone" + %br + %div.ui-box + .title + %ul.pills + %li{:class => ("active" if (params[:f] == "0" || !params[:f]))} + = link_to project_milestones_path(@project, :f => 0) do + Active + %li{:class => ("active" if params[:f] == "1")} + = link_to project_milestones_path(@project, :f => 1) do + All + + %ul.unstyled + = render @milestones + + - if @milestones.present? + %li.bottom= paginate @milestones, :remote => true, :theme => "gitlab" + - else + %li + %p.padded Nothing to show here diff --git a/app/views/milestones/new.html.haml b/app/views/milestones/new.html.haml new file mode 100644 index 00000000000..b1bc3ba0eba --- /dev/null +++ b/app/views/milestones/new.html.haml @@ -0,0 +1 @@ += render "form" diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml new file mode 100644 index 00000000000..bac48aa54af --- /dev/null +++ b/app/views/milestones/show.html.haml @@ -0,0 +1,50 @@ +%h3 + Milestone ##{@milestone.id} + %small + = @milestone.expires_at + + %span.right + - if can?(current_user, :admin_milestone, @project) + = link_to edit_project_milestone_path(@project, @milestone), :class => "btn" do + Edit + +.back_link + = link_to project_milestones_path(@project) do + ← To milestones list + +.main_box + .top_box_content + %h5 + - if @milestone.closed + .alert-message.error.status_info Closed + - else + .alert-message.success.status_info Open + = @milestone.title + + .middle_box_content + .row + .span2 + = link_to 'Browse Issues', project_issues_path(@milestone.project, :milestone_id => @milestone.id), :class => "btn small edit-milestone-link" + .span4 + %span + = @milestone.expires_at + + .span4.right + .progress + %br + %span + #{@milestone.issues.opened.count} open + – + #{@milestone.issues.closed.count} closed + + - if @milestone.description.present? + .bottom_box_content + = markdown @milestone.description + + +:javascript + $(function() { + $( ".progress" ).progressbar({ + value: #{@milestone.percent_complete} + }); + }); |