Commit 23d95085 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Milestone basic scaffold

parent 667edcdd
...@@ -2,6 +2,7 @@ function switchToNewIssue(form){ ...@@ -2,6 +2,7 @@ function switchToNewIssue(form){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){ $(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$(".issues_content").after(form); $(".issues_content").after(form);
$('select#issue_assignee_id').chosen(); $('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150); $("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide(); $('.top-tabs .add_new').hide();
}); });
...@@ -11,6 +12,7 @@ function switchToEditIssue(form){ ...@@ -11,6 +12,7 @@ function switchToEditIssue(form){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){ $(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$(".issues_content").after(form); $(".issues_content").after(form);
$('select#issue_assignee_id').chosen(); $('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide(); $('.add_new').hide();
}); });
......
...@@ -45,6 +45,13 @@ a { ...@@ -45,6 +45,13 @@ a {
&:hover { &:hover {
} }
&.primary {
background:$link_color;
&:hover {
background:$blue_link;
}
}
} }
a:focus { a:focus {
......
/**
* 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;
}
}
...@@ -116,3 +116,9 @@ $hover: #FDF5D9; ...@@ -116,3 +116,9 @@ $hover: #FDF5D9;
* *
*/ */
@import "highlight.black.scss"; @import "highlight.black.scss";
/**
* JQUERY UI ext
*
*/
@import "jquery_ui.scss";
...@@ -15,7 +15,7 @@ class IssuesController < ApplicationController ...@@ -15,7 +15,7 @@ class IssuesController < ApplicationController
before_filter :authorize_write_issue!, :only => [:new, :create] before_filter :authorize_write_issue!, :only => [:new, :create]
# Allow modify issue # 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 # Allow destroy issue
before_filter :authorize_admin_issue!, :only => [:destroy] before_filter :authorize_admin_issue!, :only => [:destroy]
...@@ -28,8 +28,10 @@ class IssuesController < ApplicationController ...@@ -28,8 +28,10 @@ class IssuesController < ApplicationController
when 2 then @project.issues.closed when 2 then @project.issues.closed
when 3 then @project.issues.opened.assigned(current_user) when 3 then @project.issues.opened.assigned(current_user)
else @project.issues.opened 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") @issues = @issues.includes(:author, :project).order("critical, updated_at")
respond_to do |format| respond_to do |format|
...@@ -51,13 +53,6 @@ class IssuesController < ApplicationController ...@@ -51,13 +53,6 @@ class IssuesController < ApplicationController
def show def show
@note = @project.notes.new(:noteable => @issue) @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| respond_to do |format|
format.html format.html
format.js format.js
...@@ -102,6 +97,8 @@ class IssuesController < ApplicationController ...@@ -102,6 +97,8 @@ class IssuesController < ApplicationController
end end
def sort def sort
return render_404 unless can?(current_user, :admin_issue, @project)
@issues = @project.issues.where(:id => params['issue']) @issues = @project.issues.where(:id => params['issue'])
@issues.each do |issue| @issues.each do |issue|
issue.position = params['issue'].index(issue.id.to_s) + 1 issue.position = params['issue'].index(issue.id.to_s) + 1
......
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
class MilestoneDecorator < ApplicationDecorator
decorates :milestone
end
...@@ -17,6 +17,7 @@ class Ability ...@@ -17,6 +17,7 @@ class Ability
:read_project, :read_project,
:read_wiki, :read_wiki,
:read_issue, :read_issue,
:read_milestone,
:read_snippet, :read_snippet,
:read_team_member, :read_team_member,
:read_merge_request, :read_merge_request,
...@@ -42,6 +43,7 @@ class Ability ...@@ -42,6 +43,7 @@ class Ability
:modify_merge_request, :modify_merge_request,
:admin_project, :admin_project,
:admin_issue, :admin_issue,
:admin_milestone,
:admin_snippet, :admin_snippet,
:admin_team_member, :admin_team_member,
:admin_merge_request, :admin_merge_request,
......
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :milestone
belongs_to :author, :class_name => "User" belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User" belongs_to :assignee, :class_name => "User"
has_many :notes, :as => :noteable, :dependent => :destroy has_many :notes, :as => :noteable, :dependent => :destroy
......
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
...@@ -12,6 +12,7 @@ class Project < ActiveRecord::Base ...@@ -12,6 +12,7 @@ class Project < ActiveRecord::Base
has_many :events, :dependent => :destroy has_many :events, :dependent => :destroy
has_many :merge_requests, :dependent => :destroy has_many :merge_requests, :dependent => :destroy
has_many :issues, :dependent => :destroy, :order => "position" has_many :issues, :dependent => :destroy, :order => "position"
has_many :milestones, :dependent => :destroy
has_many :users_projects, :dependent => :destroy has_many :users_projects, :dependent => :destroy
has_many :notes, :dependent => :destroy has_many :notes, :dependent => :destroy
has_many :snippets, :dependent => :destroy has_many :snippets, :dependent => :destroy
......
...@@ -9,8 +9,13 @@ ...@@ -9,8 +9,13 @@
%li= msg %li= msg
.clearfix .clearfix
= f.label :title, "Issue Subject" = f.label :title, "Issue Subject *"
.input= f.text_field :title, :maxlength => 255, :class => "xxlarge" .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 .clearfix
= f.label :description, "Issue Details" = f.label :description, "Issue Details"
...@@ -18,9 +23,11 @@ ...@@ -18,9 +23,11 @@
= f.text_area :description, :maxlength => 2000, :class => "xxlarge", :rows => 10 = f.text_area :description, :maxlength => 2000, :class => "xxlarge", :rows => 10
%p.hint Markdown is enabled. %p.hint Markdown is enabled.
.clearfix .clearfix
= f.label :assignee_id = f.label :milestone_id
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Assign to user" }) .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { :include_blank => "Select milestone" })
.clearfix .clearfix
= f.label :critical, "Critical" = f.label :critical, "Critical"
......
.tabs .tabs
%li{:class => "#{'active' if current_page?(project_issues_path(@project))}"} %li{:class => "#{'active' if current_page?(project_issues_path(@project))}"}
= link_to project_issues_path(@project), :class => "tab" do = 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
...@@ -10,3 +10,6 @@ ...@@ -10,3 +10,6 @@
.span10= paginate @issues, :remote => true, :theme => "gitlab" .span10= paginate @issues, :remote => true, :theme => "gitlab"
.span4.right .span4.right
%span.cgray.right #{@issues.total_count} issues for this filter %span.cgray.right #{@issues.total_count} issues for this filter
- else
%li
%p.padded Nothing to show here
...@@ -3,5 +3,6 @@ ...@@ -3,5 +3,6 @@
:javascript :javascript
$(function(){ $(function(){
$('select#issue_assignee_id').chosen(); $('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
}); });
= render "issues/head"
.issues_content .issues_content
%h3 %h3
Issues Issues
...@@ -5,14 +6,22 @@ ...@@ -5,14 +6,22 @@
= link_to project_issues_path(@project, :atom, { :private_token => current_user.private_token }) do = link_to project_issues_path(@project, :atom, { :private_token => current_user.private_token }) do
= image_tag "Rss-UI.PNG", :width => 16, :title => "feed" = image_tag "Rss-UI.PNG", :width => 16, :title => "feed"
.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 - 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 = link_to new_project_issue_path(@project), :class => "right btn small", :title => "New Issue", :remote => true do
New Issue New Issue
%br %br
%div#issues-table-holder.ui-box %div#issues-table-holder.ui-box
.title .title
.row .row
.span8 .span6
%ul.pills.left %ul.pills.left
%li{:class => ("active" if (params[:f] == "0" || !params[:f]))} %li{:class => ("active" if (params[:f] == "0" || !params[:f]))}
= link_to project_issues_path(@project, :f => 0) do = link_to project_issues_path(@project, :f => 0) do
...@@ -27,17 +36,13 @@ ...@@ -27,17 +36,13 @@
= link_to project_issues_path(@project, :f => 1) do = link_to project_issues_path(@project, :f => 1) do
All All
.span3.right .span6.right
= form_tag search_project_issues_path(@project), :method => :get, :remote => true, :id => "issue_search_form", :class => :right do = form_tag project_issues_path(@project), :method => :get, :class => :right do
= hidden_field_tag :project_id, @project.id, { :id => 'project_id' } = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Select milestone")
= hidden_field_tag :status, params[:f]
= search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search' }
%ul#issues-table.unstyled %ul#issues-table.unstyled
= render "issues" = render "issues"
- if @issues.blank?
%li
%p.padded Nothing to show here
:javascript :javascript
var href = $('.issue_search').parent().attr('action'); var href = $('.issue_search').parent().attr('action');
var last_terms = ''; var last_terms = '';
...@@ -65,9 +70,8 @@ ...@@ -65,9 +70,8 @@
$('#issues-table').sortable({ $('#issues-table').sortable({
axis: 'y', axis: 'y',
dropOnEmpty: false, dropOnEmpty: false,
handle: '.handle', handle: '.avatar',
cursor: 'crosshair', items: 'li',
items: 'tr',
opacity: 0.4, opacity: 0.4,
scroll: true, scroll: true,
update: function(){ update: function(){
...@@ -85,4 +89,8 @@ ...@@ -85,4 +89,8 @@
$(function(){ $(function(){
setSortable(); setSortable();
$("#milestone_id").chosen();
$("#milestone_id").live("change", function(){
$(this).closest("form").submit();
});
}); });
...@@ -3,5 +3,6 @@ ...@@ -3,5 +3,6 @@
:javascript :javascript
$(function(){ $(function(){
$('select#issue_assignee_id').chosen(); $('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
}); });
%h3= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}"
.back_link
= link_to project_milestones_path(@project) do
&larr; 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) }
});
});
%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}
});
});
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
});
= 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
%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
&larr; 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
&ndash;
#{@milestone.issues.closed.count} closed
- if @milestone.description.present?
.bottom_box_content
= markdown @milestone.description
:javascript
$(function() {
$( ".progress" ).progressbar({
value: #{@milestone.percent_complete}
});
});
...@@ -122,6 +122,7 @@ Gitlab::Application.routes.draw do ...@@ -122,6 +122,7 @@ Gitlab::Application.routes.draw do
end end
end end
resources :team_members resources :team_members
resources :milestones
resources :issues do resources :issues do
collection do collection do
post :sort post :sort
......
class CreateMilestones < ActiveRecord::Migration
def change
create_table :milestones do |t|
t.string :title, :null => false
t.integer :project_id, :null => false
t.text :description
t.date :due_date
t.boolean :closed, :default => false, :null => false
t.timestamps
end
end
end
class AddMilestoneIdToIssue < ActiveRecord::Migration
def change
add_column :issues, :milestone_id, :integer, :null => true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120405211750) do ActiveRecord::Schema.define(:version => 20120408181910) do
create_table "events", :force => true do |t| create_table "events", :force => true do |t|
t.string "target_type" t.string "target_type"
...@@ -37,6 +37,7 @@ ActiveRecord::Schema.define(:version => 20120405211750) do ...@@ -37,6 +37,7 @@ ActiveRecord::Schema.define(:version => 20120405211750) do
t.boolean "critical", :default => false, :null => false t.boolean "critical", :default => false, :null => false
t.string "branch_name" t.string "branch_name"
t.text "description" t.text "description"
t.integer "milestone_id"
end end
add_index "issues", ["project_id"], :name => "index_issues_on_project_id" add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
...@@ -69,6 +70,15 @@ ActiveRecord::Schema.define(:version => 20120405211750) do ...@@ -69,6 +70,15 @@ ActiveRecord::Schema.define(:version => 20120405211750) do
add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
create_table "milestones", :force => true do |t|
t.string "title", :null => false
t.text "description"
t.date "due_date", :null => false
t.integer "project_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "notes", :force => true do |t| create_table "notes", :force => true do |t|
t.text "note" t.text "note"
t.string "noteable_id" t.string "noteable_id"
......
require 'spec_helper'
describe Milestone do
pending "add some examples to (or delete) #{__FILE__}"
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment