Merge pull request #226 from warpc/194-tracker

194 tracker
This commit is contained in:
Pasha 2012-02-28 08:48:11 -08:00
commit 816f0ce7ee
44 changed files with 2346 additions and 2388 deletions

View File

@ -1,35 +0,0 @@
function addPeople(num) {
$("#people"+num).fadeOut(0);
$("#people-sections"+num).fadeIn("slow");
$("#people-sections-list"+num).fadeIn("slow");
if ($("#people-span").css("display") != "none") {
$("#people-span").fadeOut(0);
}
}
function remPeople(num) {
$("#people"+num).fadeIn("slow");
$("#people-sections"+num).fadeOut(0);
$("#people-sections-list"+num).fadeOut(0);
if (($("#people-sections-list1").css("display") == "none") && ($("#people-sections-list2").css("display") == "none") && ($("#people-sections-list3").css("display") == "none") && ($("#people-sections-list4").css("display") == "none")) {
$("#people-span").fadeIn("slow");
}
}
function addFlag(num) {
$("#flag"+num).fadeOut(0);
$("#flag-list"+num).fadeIn("slow");
$("#flag-list-sections"+num).fadeIn("slow");
if ($("#flag-span").css("display") != "none") {
$("#flag-span").fadeOut(0);
}
}
function remFlag(num) {
$("#flag"+num).fadeIn("slow");
$("#flag-list"+num).fadeOut(0);
$("#flag-list-sections"+num).fadeOut(0);
if (($("#flag-list-sections1").css("display") == "none") && ($("#flag-list-sections2").css("display") == "none") && ($("#flag-list-sections3").css("display") == "none") && ($("#flag-list-sections4").css("display") == "none")) {
$("#flag-span").fadeIn("slow");
}
}

View File

@ -102,7 +102,11 @@ var el = el,
el.next().find("input").eq(0).attr("disabled","disabled");
}
el.next().bind("mousedown", function(e) { changeRadio(jQuery(this)) });
el.next().bind("mousedown", function(e) {
changeRadio(jQuery(this));
$(this).find("input:radio").change();
});
if(jQuery.browser.msie) el.next().find("input").eq(0).bind("click", function(e) { changeVisualRadio(jQuery(this)) });
else el.next().find("input").eq(0).bind("change", function(e) { changeVisualRadio(jQuery(this)) });
el.remove();

View File

@ -1,54 +0,0 @@
function switchThis() {
var doc = document.getElementById("switcher");
if (doc.className == "switcher") {
doc.className = "switcher-off";
$("#open-comment").fadeOut(0);
$("#closed-comment").fadeIn("slow");
} else {
doc.className = "switcher";
$("#closed-comment").fadeOut(0);
$("#open-comment").fadeIn("slow");
}
}
function preload() {
if (document.images) {
var imgsrc = preload.arguments;
arr=new Array(imgsrc.length);
for (var j=0; j<imgsrc.length; j++) {
arr[j] = new Image;
arr[j].src = imgsrc[j];
}
}
}
function manage(elem) {
if (elem == "people") {
var doc = document.getElementById("people-manage");
if (doc.className == "view") {
doc.className = "non-view";
$("#people-manage").fadeOut(0);
$("#people-manage-list").fadeIn("slow");
}
else {
$("#people-manage-list").fadeOut(0);
$("#people-manage").fadeIn("slow");
doc.className = "view";
}
}
if (elem == "labels") {
var doc = document.getElementById("labels-manage");
if (doc.className == "view") {
doc.className = "non-view";
$("#labels-manage").fadeOut(0);
$("#labels-manage-list").fadeIn("slow");
}
else {
$("#labels-manage-list").fadeOut(0);
$("#labels-manage").fadeIn("slow");
doc.className = "view";
}
}
}

View File

@ -1,138 +1,237 @@
$(document).ready(function() {
$("#closed-switcher").click(function() {
$("#closed-switcher").live('click', function() {
if ($("#blue-switch-select").css("margin-left") != "130px") {
$("#blue-switch-select").animate({"margin-left": "+=130px"}, "fast");
$("#table1").fadeOut(0);
$("#table2").fadeIn("slow");
$('#issues_status').val('closed');
}
else {
$("#blue-switch-select").animate({"margin-left": "-=130px"}, "fast");
$("#table2").fadeOut(0);
$("#table1").fadeIn("slow");
$('#issues_status').val('open');
}
});
return send_index_tracker_request('GET');
});
$(document).ready(function() {
$("#myTable").tablesorter({
headers: {
1: {
sorter: false
}
}
});
});
$(document).ready(function() {
$("#myTable2").tablesorter({
headers: {
1: {
sorter: false
}
}
});
});
$(document).ready(function() {
$("#manage-labels").click(function() {
$("#manage-labels").live('click', function () {
var toggled = $(this).data('toggled');
$(this).data('toggled', !toggled);
if (!toggled) {
$("#labels-stock").fadeOut(0);
$("#labels-edit").fadeIn("slow");
});
}
else {
$("#labels-edit").fadeOut(0);
$("#labels-stock").fadeIn("slow");
}
});
$(document).ready(function() {
$("div.delete").click(function() {
var div = "#label-"+this.id;
$(div).fadeOut("slow");
});
});
$(document).ready(function() {
$("div.div-tracker-lables").click(function() {
$("div.div-tracker-labels").live('click', function() {
var flag = this.id;
flag = flag.replace("label-","flag-");
var bg = $("#"+flag).css("background-color");
var checkbox = $(this).find(':checkbox');
if ($(this).css("background-color") != bg) {
$(this).css("background-color",bg);
$(this).css("color","#FFFFFF");
var labels = document.getElementsByName("label");
var rows = document.getElementsByName("row");
var arrayLabels;
var rowState = 0;
for (var r in rows) {
for (var l in labels) {
var ro = document.getElementById(rows[r].id);
var cls = ro.className;
var clsLabel = labels[l].id.split("label-")[1];
if (($("#"+labels[l].id).css("background-color") != "rgb(247, 247, 247)")&&($("#"+labels[l].id).css("background-color") != "transparent")) {
if (cls.indexOf(clsLabel) != -1) {
rowState = 1;
}
}
}
if (rowState == 1) {
showRow(rows[r].id);
rowState = 0;
}
else {
hideRow(rows[r].id);
}
}
checkbox.attr('checked', 'checked');
} else {
$(this).css("background-color","rgb(247, 247, 247)");
$(this).css("color","#565657");
var labels = document.getElementsByName("label");
var rows = document.getElementsByName("row");
var rowState = 0;
var labelState = 0;
for (var l in labels) {
if (($("#"+labels[l].id).css("background-color") != "rgb(247, 247, 247)")&&($("#"+labels[l].id).css("background-color") != "transparent")) {
labelState = 1;
checkbox.removeAttr('checked');
}
}
if (labelState == 1) {
for (var r in rows) {
for (var l in labels) {
var ro = document.getElementById(rows[r].id);
var cls = ro.className;
var clsLabel = labels[l].id.split("label-")[1];
if (($("#"+labels[l].id).css("background-color") != "rgb(247, 247, 247)")&&($("#"+labels[l].id).css("background-color") != "transparent")) {
if (cls.indexOf(clsLabel) != -1) {
rowState = 1;
}
}
}
if (rowState == 1) {
showRow(rows[r].id);
rowState = 0;
}
else {
hideRow(rows[r].id);
}
}
} else {
for (var r in rows) {
showRow(rows[r].id);
}
}
}
});
return send_index_tracker_request('GET');
});
$("#myradio1").live('change', function(event) {
return send_index_tracker_request('GET');
});
function showRow(elem) {
if ($("#"+elem).css("display") == "none") {
$("#"+elem).fadeIn("slow");
} else {
//$("#"+elem).fadeOut(0);
$('#search_issue').live('submit', function() {
return send_index_tracker_request('GET', $(this).attr("action"), $(this).serialize());
});
$('#add_label').live('click', function() {
return send_index_tracker_request('POST', $(this).attr("href"), $('#new_label').serialize());
});
$('.righter #update_label').live('click', function() {
return send_index_tracker_request('POST', $(this).attr("href"), $(this).parents('#update_label').serialize());
});
$('.colors .choose').live('click', function() {
var parent = $(this).parents('.colors');
parent.find('.choose.selected').removeClass('selected');
$(this).addClass('selected');
parent.siblings('.lefter').find('#label_color').val($(this).attr('value'));
return false;
});
$('.custom_color').live('click', function() {
$(this).siblings('#label_color').toggle();
return false;
});
$('article a.edit_label').live('click', function() {
$(this).parents('.label.edit').siblings('.label.edit').find('.edit_label_form').hide();
$(this).parents('.label.edit').find('.edit_label_form').toggle();
return false;
});
$('.delete_label').live('click', function() {
return send_index_tracker_request('POST', $(this).attr('href'));
});
function send_index_tracker_request(type_request, url, data) {
data = data || '';
var filter_form = $('#filter_issues');
url = url || filter_form.attr("action");
var label_form = $('#filter_labels');
var status = 'status=' + $('#issues_status').attr('value');
$.ajax({
type: type_request,
url: url,
data: filter_form.serialize() + '&' + label_form.serialize() + '&' + status + '&' + data,
success: function(data){
$('article').html(data);
$(".niceRadio").each(function() { changeRadioStart(jQuery(this)) });
},
error: function(data){
alert('error') // TODO remove
}
});
return false;
};
$('#search_user, #search_labels').live('submit', function() {
var id = $(this).attr('id');
if(id.indexOf('user') != -1) { // FIXME
var which = 'users';
}
else if (id.indexOf('labels') != -1) {
var which = 'labels';
}
$.ajax({
type: 'GET',
url: $(this).attr("action"),
data: $(this).serialize(),
success: function(data){
var tmp = $('#create_issue_'+ which +'_list');
$('#create_issue_'+ which +'_list').html(data);
},
error: function(data){
alert('error') // TODO remove
}
});
return false;
});
function remExecutor(form) {
var el = form.find('.people.selected.remove_executor');
var id = el.attr('id');
$('#'+id+'.add_executor.people.selected').removeClass('select');
el.remove();
}
function hideRow(elem) {
if ($("#"+elem).css("display") != "none") {
$("#"+elem).fadeOut("fast");
} else {
//$("#"+elem).fadeOut(0);
$('.add_executor.people.selected').live('click', function() {
var form = $('.form.issue');
form.find('#people-span').fadeOut(0);
remExecutor(form);
form.find('#issue_executor').html($(this).clone().removeClass('add_executor').addClass('remove_executor'));
$(this).addClass('select');
});
$('.remove_executor.people.selected').live('click', function() {
var form = $('.form.issue');
form.find('#people-span').fadeIn(0);
remExecutor(form);
});
function remLabel(form, id) {
var el = form.find('.label.remove_label'+'#'+id);
var label = $('#'+id+'.remove_label.label.selected');
label.find('.flag').fadeIn(0);
label.find('.labeltext.selected').removeClass('selected').attr('style', '');
label.fadeIn('slow');
el.fadeOut('slow').remove();
}
$('.add_label.label').live('click', function() {
$(this).addClass('selected').removeClass('add_label').addClass('remove_label');
$(this).find('.labeltext').addClass('selected');
var style = $(this).find('.flag').attr('style');
$(this).find('.flag').fadeOut(0);
$(this).find('.labeltext.selected').attr('style', style);
var form = $('.form.issue');
form.find('#flag-span').fadeOut(0);
form.find('#issue_labels').append($(this).clone());
});
$('.remove_label.label.selected').live('click', function() {
var form = $('.form.issue');
if(form.find('.remove_label.label.selected').length == 1) {
form.find('#flag-span').fadeIn(0);
}
var str = '.label.remove_label'+'#'+$(this).attr('id');
form.find(str).remove();
var label = $(str);
label.removeClass('selected').addClass('add_label').removeClass('remove_label');
label.find('.labeltext.selected').attr('style', '').removeClass('selected');
label.find('.flag').fadeIn(0);
});
$('.issue_status.switch_issue_status').live('click', function () {
var button = $(this);
var status = (button.hasClass('switcher')) ? 'closed' : 'open';
var form = $('#update_issue_status');
form.find('#issue_status').attr('value', status);
$.ajax({
type: 'POST',
url: form.attr("action"),
data: form.serialize(),
success: function(data){
if (status == "open") { button.addClass('switcher').removeClass("switcher-off"); }
else { button.removeClass('switcher').addClass("switcher-off"); }
$('#closed_issue_text').html(data);
},
error: function(data){
alert('error') // TODO remove
}
});
return false;
});
$('#edit_issue_content').live('click', function() {
$('.edit_form.issue').fadeIn('fast');
$(this).fadeOut('fast');
});
$('#cancel_edit_issue_content').live('click', function() {
$('.edit_form.issue').fadeOut('fast');
$('#edit_issue_content').fadeIn('fast');
});
$('.edit_form.issue').live('submit', function() {
var form = $(this);
$.ajax({
type: 'POST',
url: form.attr("action"),
data: form.serialize(),
success: function(data){
form.fadeOut('slow');
$('#edit_issue_content').fadeIn('slow');
$('h3.issue_title').html(form.find('#issue_title').attr('value'));
$('.fulltext.view.issue_body').html(form.find('#issue_body').attr('value'));
},
error: function(data){
alert('error') // TODO remove
}
});
return false;
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,118 @@
/* tables */
table.tablesorter {
font-family:arial;
margin:10px 0pt 15px;
font-size: 12px;
width: 100%;
text-align: left;
border: 1px solid #DDD;
border-bottom: none;
}
table.tablesorter thead th{
padding-left: 5px;
}
table.tablesorter thead tr th, table.tablesorter tfoot tr th {
font-size: 12px;
margin: 0px;
}
table.tablesorter thead tr .header {
background-image: url("bg.png");
background-repeat: no-repeat;
background-position: center left;
cursor: pointer;
}
table.tablesorter tbody td {
color: #3D3D3D;
padding: 5px;
margin: 0px;
background-color: #FFF;
vertical-align: top;
}
table.tablesorter tbody tr.odd td {
background-color:#F0F0F6;
}
table.tablesorter thead tr .headerSortUp {
background-image: url("asc.png");
}
table.tablesorter thead tr .headerSortDown {
background-image: url("desc.png");
}
table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
}
table.tablesorter thead tr {
height: 38px;
background: #ededed;
-webkit-box-shadow: 0px 3px 3px -1px rgba(18, 86, 135, 0.2);
-moz-box-shadow: 0px 3px 3px -1px rgba(18, 86, 135, 0.2);
box-shadow: 0px 3px 3px -1px rgba(18, 86, 135, 0.2);
position: relative;
z-index: 99;
}
table.tablesorter thead tr th{
border-bottom: 1px solid #FFF;
}
table.tablesorter tr {
position: relative;
z-index: 70;
}
table.tablesorter tbody tr td {
border-bottom: 1px solid #DDD;
}
table.tablesorter thead th {
color: #575756;
font-weight: normal;
}
table.tablesorter tbody td {
color: #58595b;
padding-top: 8px;
padding-bottom: 8px;
}
table.tablesorter .th1 {
width: 180px;
/*padding-left: 17px;*/
}
table.tablesorter .th2 {
width: 390px;
}
table.tablesorter .th3 {
width: 110px;
/*padding-left: 17px;*/
}
table.tablesorter .td2 {
padding-right: 20px;
}
table.tablesorter .th4 {
width: 120px;
}
table.tablesorter .td5 {
text-align: center;
}
table.tablesorter .td5 img{
cursor: pointer;
}
.table-sort-left {
float: left;
width: 25px;
}
.table-sort-right {
float: left;
}

View File

@ -1,4 +1,12 @@
// PUT custom styles here ONLY
a#manage-labels {
margin-bottom: 10px;
}
article a.edit_label {
color: #FFF;
}
header div.information div.user {
float: left;
margin-left: 5px;
@ -36,7 +44,7 @@ article div.messages div.activity {
}
article div.activity .top div.image {
position: absolute;
//position: absolute; // TODO broken issue page
float: left;
width: 40px;
height: 40px;

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,38 @@
# -*- encoding : utf-8 -*-
class IssuesController < ApplicationController
NON_RESTFUL_ACTION = [:create_label, :update_label, :destroy_label, :search_collaborators]
before_filter :authenticate_user!
before_filter :find_project
before_filter :find_issue_by_serial_id, :only => [:show, :edit, :update, :destroy]
load_and_authorize_resource :project
load_and_authorize_resource :issue, :through => :project, :find_by => :serial_id
load_and_authorize_resource :project, :except => NON_RESTFUL_ACTION
load_and_authorize_resource :issue, :through => :project, :find_by => :serial_id, :only => [:show, :edit, :update, :destroy]
before_filter :load_and_authorize_label, :only => NON_RESTFUL_ACTION
autocomplete :user, :uname
layout 'application'
def index
def index(status = 200)
logger.debug "!!!!!!!!!!!!!!!!!!"
logger.debug "request format is #{request.format}"
@is_assigned_to_me = params[:filter] == 'to_me'
@status = params[:status] == 'closed' ? 'closed' : 'open'
@labels = params[:labels] || []
@issues = @project.issues
case params[:status]
when 'open'
@issues = @issues.where(:status => 'open')
when 'closed'
@issues = @issues.where(:status => 'closed')
@issues = @issues.where(:user_id => current_user.id) if @is_assigned_to_me
@issues = @issues.joins(:labels).where(:labels => {:name => @labels}) unless @labels == []
if params[:search_issue]
@issues = @issues.where('issues.title ILIKE ?', "%#{params[:search_issue].mb_chars.downcase}%")
end
@opened_issues = @issues.opened.count
@closed_issues = @issues.closed.count
@issues = @issues.where(:status => @status)
@issues = @issues.includes(:creator, :user).order('serial_id desc').uniq.paginate :per_page => 10, :page => params[:page]
if status == 200
render 'index', :layout => request.format == '*/*' ? 'issues' : 'application' # maybe FIXME '*/*'?
else
render :status => status, :nothing => true
end
@issues = @issues.paginate :per_page => 10, :page => params[:page]
end
def new
@ -29,6 +44,7 @@ class IssuesController < ApplicationController
@user_uname = params[:user_uname]
@issue = Issue.new(params[:issue])
@issue.creator_id = current_user.id
@issue.user_id = @user_id
@issue.project_id = @project.id
@ -43,21 +59,18 @@ class IssuesController < ApplicationController
end
end
def edit
@user_id = @issue.user_id
@user_uname = @issue.assign_uname
end
def update
@user_id = params[:user_id].blank? ? @issue.user_id : params[:user_id]
@user_uname = params[:user_uname].blank? ? @issue.assign_uname : params[:user_uname]
if @issue.update_attributes( params[:issue].merge({:user_id => @user_id}) )
flash[:notice] = I18n.t("flash.issue.saved")
redirect_to [@project, @issue]
if status = params[:issue][:status]
action = 'issues/_status'
@issue.set_close(current_user) if status == 'closed'
@issue.set_open if status == 'open'
status = 200 if @issue.save
render action, :status => (status || 500), :layout => false
else
flash[:error] = I18n.t("flash.issue.save_error")
render :action => :new
@issue.title = params[:issue][:title]
@issue.body = params[:issue][:body]
status = 200 if @issue.save
render :nothing => true, :status => (status || 500), :layout => false
end
end
@ -68,13 +81,39 @@ class IssuesController < ApplicationController
redirect_to root_path
end
def create_label
status = @project.labels.create(:name => params[:name], :color => params[:color]) ? 200 : 500
index(status)
end
def update_label
status = @label.update_attributes( :name => params[:name], :color => params[:color]) ? 200 : 500
index(status)
end
def destroy_label
status = (@label && @label_destroy) ? 200 : 500
index(status)
end
def search_collaborators
search = "%#{params[:search_user]}%"
users = User.joins(:groups => :projects).where(:projects => {:id => @project.id}).where("users.uname ILIKE ?", search)
users2 = @project.collaborators.where("users.uname ILIKE ?", search)
@users = (users + users2).uniq.sort {|x,y| x.uname <=> y.uname}.first(10)
render 'issues/_search_collaborators', :layout => false
end
def search_labels
@labels = @project.labels.where("labels.name ILIKE ?", "%#{params[:search_labels]}%").order('labels.name').limit(10)
render 'issues/_search_labels', :layout => false
end
private
def find_project
def load_and_authorize_label
@project = Project.find(params[:project_id])
end
def find_issue_by_serial_id
@issue = @project.issues.find_by_serial_id!(params[:id])
@label = Label.find(params[:label_id]) if params[:label_id]
authorize! :write, @project
end
end

View File

@ -1,3 +1,9 @@
# -*- encoding : utf-8 -*-
module IssuesHelper
def tracker_search_field(name, txt)
str = "<input name='#{name}' id='#{name}' type='text' value='#{txt}'"
str << "onblur=\"if(this.value==''){this.value='#{txt}';this.className='gray';}\""
str << "onclick=\"if(this.value=='#{txt}'){this.value='';this.className='black';}\" class=\"gray\">"
str.html_safe
end
end

View File

@ -4,9 +4,13 @@ class Issue < ActiveRecord::Base
belongs_to :project
belongs_to :user
belongs_to :creator, :class_name => 'User', :foreign_key => 'creator_id'
belongs_to :closer, :class_name => 'User', :foreign_key => 'closed_by'
has_many :comments, :as => :commentable, :dependent => :destroy #, :finder_sql => proc { "comments.commentable_id = '#{self.id}' AND comments.commentable_type = '#{self.class.name}'"}
has_many :subscribes, :as => :subscribeable, :dependent => :destroy #, :finder_sql => proc { "subscribes.subscribeable_id = '#{self.id}' AND subscribes.subscribeable_type = '#{self.class.name}'"}
has_many :labels, :through => :labelings
has_many :labelings
validates :title, :body, :project_id, :presence => true
@ -19,6 +23,12 @@ class Issue < ActiveRecord::Base
after_update :deliver_issue_assign_notification
after_update :subscribe_issue_assigned_user
attr_accessible :labelings_attributes, :title, :body, :project, :project_id, :closed_at, :closed_by
accepts_nested_attributes_for :labelings, :allow_destroy => true
scope :opened, where(:status => 'open', :closed_by => nil, :closed_at => nil)
scope :closed, where(:status => 'closed').where("closed_by is not null and closed_at is not null")
def assign_uname
user.uname if user
end
@ -33,6 +43,21 @@ class Issue < ActiveRecord::Base
end
end
def closed?
closed_by && closed_at && status == 'closed'
end
def set_close(closed_by)
self.closed_at = Time.now
self.closer = closed_by
self.status = 'closed'
end
def set_open
self.closed_at = self.closed_by = nil
self.status = 'open'
end
protected
def set_serial_id
@ -81,4 +106,5 @@ class Issue < ActiveRecord::Base
end
end
end
end

9
app/models/label.rb Normal file
View File

@ -0,0 +1,9 @@
class Label < ActiveRecord::Base
has_many :labelings, :dependent => :destroy
has_many :issues, :through => :labelings
belongs_to :project
validates :name, :uniqueness => { :scope => :project_id}
validates :name, :color, :presence => true
validates :color, :format => { :with => /\A([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, :message => I18n.t('layout.issues.invalid_labels')}
end

5
app/models/labeling.rb Normal file
View File

@ -0,0 +1,5 @@
class Labeling < ActiveRecord::Base
belongs_to :issue
belongs_to :label
end

View File

@ -17,6 +17,7 @@ class Project < ActiveRecord::Base
has_many :relations, :as => :target, :dependent => :destroy
has_many :collaborators, :through => :relations, :source => :object, :source_type => 'User'
has_many :groups, :through => :relations, :source => :object, :source_type => 'Group'
has_many :labels
validates :name, :uniqueness => {:scope => [:owner_id, :owner_type], :case_sensitive => false}, :presence => true, :format => {:with => /^[a-zA-Z0-9_\-\+\.]+$/}
validates :owner, :presence => true

View File

@ -93,6 +93,10 @@ class User < ActiveRecord::Base
email.downcase == commit.committer.email.downcase
end
def avatar(size)
"https://secure.gravatar.com/avatar/#{Digest::MD5.hexdigest(email.downcase)}?s=#{size}&r=pg"
end
private
def create_settings_notifier

View File

@ -0,0 +1,6 @@
-current_color ||= '0054a6'
.colors
- ['0054a6', '00a651', 'ed1c24', 'e65c00', '9e005d', '464646', '8c6239'].each do |color|
.color{:style => "background: ##{color};"}
#choose1.choose{:value => color, :class => current_color == color ? 'selected' : ''}
.both

View File

@ -0,0 +1,39 @@
-content_for :sidebar do
- if @issue.persisted?
.bordered.nopadding
%h3=t('activerecord.attributes.issue.status')
#switcher.issue_status{:class => "#{@issue.closed? ? 'switcher-off' : 'switcher'} #{can?(:write, @issue.project) ? "switch_issue_status" : ''}"}
.swleft=t('layout.issues.status.open')
.swright=t('layout.issues.status.closed')
- if can? :write, @issue.project
=form_tag [@project, @issue], :id => 'update_issue_status', :method => :put do
=hidden_field_tag "issue_status", @issue.closed? ? 'closed' : 'open', :name => "issue[status]"
.bordered.nopadding
%h3=t('layout.issues.executor')
- if @issue.persisted? && @issue.user
.bordered.nopadding
.people.nopointer
.avatar=image_tag(@issue.user.avatar(25), :alt => 'avatar')
.name="#{@issue.user.uname} (#{@issue.user.name})"
=hidden_field_tag "user-0", @issue.user.id, :name => 'user_id'
.both
- else
=form_tag search_collaborators_project_issues_path(@project), :id => 'search_user', :method => :get do
=tracker_search_field(:search_user, t('layout.issues.search_user'))
#create_issue_users_list
=render 'issues/search_collaborators'
.block
%h3=t('layout.issues.labels')
- if @issue.new_record?
=form_tag search_labels_project_issues_path(@project), :id => 'search_labels', :method => :get do
=tracker_search_field(:search_labels, t('layout.issues.search_labels'))
#create_issue_labels_list
=render 'issues/search_labels'
- else
- (@issue.labels || []).each_with_index do |label|
.label.selected.nopointer
.labeltext.selected{:style => "background: ##{label.color};"}
=label.name
.both

View File

@ -1,24 +1,19 @@
.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
- unless @issue.new_record?
.group
= f.label :status, :class => :label
= f.select :status, Issue::STATUSES, :class => 'text_field'
.group
= label_tag "", t("activerecord.attributes.issue.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("choose.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"
.leftlist= t('activerecord.attributes.issue.title') + ':'
.rightlist= f.text_field :title
.leftlist= t('activerecord.attributes.issue.body') + ':'
.rightlist= f.text_area :body
.both
.leftlist= t('activerecord.attributes.issue.user') + ':'
.rightlist
%span#people-span.small-text= t('layout.issues.choose_user_on_left')
#issue_executor
.both
.leftlist= t('layout.issues.labels')
.rightlist
%span#flag-span.small-text= t('layout.issues.choose_labels_on_left')
#issue_labels
.both
.leftlist
.rightlist
%input{:type => "submit", :value => t(@issue.new_record? ? 'layout.create' : 'layout.update')}
.both

View File

@ -0,0 +1,20 @@
-content_for :sidebar do
=form_tag project_issues_path(@project), :id => 'filter_issues', :method => :get do
.bordered.nopadding
%h3=t("layout.issues.accessory")
%table
%tr
%td.width18=radio_button_tag :myradio, 'all', !@is_assigned_to_me, {:id => 'myradio1', :class => 'niceRadio', :name => 'filter'}
%td.width135=t("layout.issues.all")
%td.width30.right=@project.issues.count
%tr
%td=radio_button_tag :myradio, 'to_me', @is_assigned_to_me, {:id => 'myradio1', :class => 'niceRadio', :name => 'filter'}
%td=t("layout.issues.to_me")
%td.width30.right=@project.issues.where(:user_id => current_user).count
=form_tag project_issues_path(@project), :id => 'search_issue', :method => :get do
.bordered.bpadding20
=tracker_search_field(:search_issue, t('layout.issues.search'))
.bordered.nopadding
%h3.bmargin10=t('layout.issues.new')
= link_to t("layout.add"), new_project_issue_path(@project), :class => 'button' if can? :new, Issue.new(:project_id => @project.id)
=render :partial => 'labels'

View File

@ -0,0 +1,25 @@
%tr#row1{:name => "row", :class => issue.labels.map(&:name).compact}
%td.td0
%span{:style => "display: none;"}=issue.serial_id
%td.td1=issue.serial_id
%td
%div=issue.title
.smalltext
=issue.created_at.to_s(:long)
=t("layout.issues.by") if issue.creator
=link_to(issue.creator.uname, user_path(issue.creator)) if issue.creator
.label.selected.tracker
-issue.labels.each do |label|
.labeltext.selected{:style => "background: ##{label.color};"}=label.name
.both
%td.td3
%a{:href => project_issue_path(@project.id, issue.serial_id)}
.code='#'
.avatar
=link_to image_tag(issue.user.avatar(22), :alt => 'avatar'), user_path(issue.user) if issue.user
%a{:href => "#{project_issue_path @project, issue}#block-list"}
.answers
.pic
%img{:alt => '', :src => "/assets/answers.png"}
.count=issue.comments.count
.both

View File

@ -0,0 +1,46 @@
.block
%h3=t('layout.issues.labels')
#labels-stock
=form_tag project_issues_path(@project), :id => 'filter_labels', :method => :get do
- @project.labels.each_with_index do |label, index|
.div-tracker-labels{:id => "label-#{label.name}", :style => @labels.include?(label.name) ? "background-color:##{label.color};color:'#FFF'" : ''}
.div-label-left
.label
.flag{:id => "flag-#{label.name}", :style => "background-color: ##{label.color};"}
.labeltext=label.name
=check_box_tag 'labels[]', label.name, @labels.include?(label.name), :style => 'display:none'
.both
.div-label-right=Labeling.joins(:label).where(:labels => {:name => label.name, :project_id => @project.id}).count
.both
.both
- if can? :write, @project
%a#manage-labels.button.tmargin10{:href => "#labels-stock"}=t('layout.issues.label_manage')
#labels-edit{:style => "display: none;"}
- @project.labels.each_with_index do |label, index|
.label.edit{:id => "label-#{index}"}
.labeltext.edit{:style => "background: ##{label.color};"}
.text=link_to(label.name, project_issues_update_label_path(@project, label.id), :class => 'edit_label')
.delete{:id => "delete#{index}"}
%a{:href => project_issues_delete_label_path(@project, label.id), :class => 'delete_label'}
%img{:alt => "x", :src => "/assets/x-label.png"}
.both
.edit_label_form{:style => 'display:none'}
=form_tag project_issues_update_label_path(@project, label.id), :id => 'update_label', :method => :post do
%input.gray{:name => 'name', :type => "text", :value => label.name}
=render :partial => 'issues/colors_chooser', :locals => {:current_color => label.color}
.lefter
%a{:href => "#custom_color-#{label.name}", :id => "custom_color-#{label.name}", :class => 'custom_color'}=t('layout.issues.label_custom_color')
=text_field_tag :color, label.color, :id => 'label_color', :class => 'gray', :style => 'display:none', :maxlength => 6
.righter
=link_to t('layout.update'), project_issues_update_label_path(@project, label.id), :id => 'update_label', :class => 'button'
.both
=form_tag create_label_project_issues_path(@project), :id => 'new_label', :method => :post do
=tracker_search_field(:name, t('layout.issues.new_label'))
=render :partial => 'issues/colors_chooser'
.lefter
%a{:href => "#custom_color", :id => 'custom_color', :class => 'custom_color'}=t('layout.issues.label_custom_color')
=text_field_tag :color, '0054a6', :id => 'label_color', :class => 'gray', :style => 'display:none', :maxlength => 6
.righter
=link_to t('layout.add'), create_label_project_issues_path(@project), :id => 'add_label', :class => 'button'
.both

View File

@ -1,18 +0,0 @@
%table.table
%tr
%th.first= t("activerecord.attributes.issue.title")
%th.first= t("activerecord.attributes.issue.user")
%th.first= t("activerecord.attributes.issue.status")
%th.last &nbsp;
- @issues.each do |issue|
%tr{:class => cycle("odd", "even")}
%td
= link_to issue.title, [@project, issue]
%td
= link_to issue.user.uname, user_path(issue.user) if issue.user
%td
= issue.status
%td.last
= link_to t("layout.show"), [@project, issue]
|
= link_to t("layout.delete"), project_issue_path(@project, issue), :method => :delete, :confirm => t("layout.issues.confirm_delete") if can? :destroy, issue

View File

@ -0,0 +1,6 @@
- (@users || []).each_with_index do |user, index|
.people.selected{:id => "user-#{index}", :class => 'add_executor'}
.avatar=image_tag(user.avatar(25), :alt => 'avatar')
.name="#{user.uname} (#{user.name})"
=hidden_field_tag "user-#{index}", user.id, :name => 'user_id'
.both

View File

@ -0,0 +1,6 @@
- (@labels || []).each_with_index do |label, index|
.add_label.label{:id => "flag#{index}"}
.flag{:style => "background: ##{label.color};"}
.labeltext=label.name
=hidden_field_tag "label-#{index}", label.id, :name => "issue[labelings_attributes][#{index}][label_id]"
.both

View File

@ -0,0 +1,9 @@
#closed_issue_text
- if @issue.status == 'closed' && @issue.closed_at && @issue.closed_by
#closed-comment.comment-closed{:style => 'display: block;'}
.state=t('layout.issues.status.closed')
.text
.avatar=image_tag(@issue.closer.avatar(25), :alt => 'avatar')
.name="#{@issue.closer.uname} (#{@issue.closer.name}) #{t('layout.issues.at')} #{@issue.closed_at.to_s(:long)}"
.both
%br/

View File

@ -1,11 +0,0 @@
.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)
.content
%h2.title
= t("layout.issues.edit_header")
.inner
= form_for @issue, :url => project_issue_path(@project, @issue), :html => { :class => :form } do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -1,22 +1,21 @@
.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? :new, Issue.new(:project_id => @project.id)
.secondary-navigation
%ul.wat-cf
%li{:class => "first " + (params[:status].blank? ? "active" : "")}
= link_to t("layout.issues.statuses.any"), project_issues_path(@project)
%li{:class => "first " + (params[:status] == 'open' ? "active" : "")}
= link_to t("layout.issues.statuses.open"), project_issues_path(@project, :status => 'open')
%li{:class => "first " + (params[:status] == 'closed' ? "active" : "")}
= link_to t("layout.issues.statuses.closed"), project_issues_path(@project, :status => 'closed')
.content
%h2.title
= t("layout.issues.list_header")
.inner
= render :partial => 'shared/search_form'
= render :partial => 'issues/list'
.actions-bar.wat-cf
.actions
= will_paginate @issues#, :param_name => :issue_page
-render :partial => 'projects/submenu'
-render :partial => 'issues/index_sidebar'
#closed-switcher.blue-switcher
=hidden_field_tag :issues_status, @status, :id => 'issues_status'
.open
="#{t('layout.issues.statuses.open')} (#{@opened_issues})"
#closed-tasks.closed
="#{t('layout.issues.statuses.closed')} (#{@closed_issues})"
#blue-switch-select.selected{:style => "margin-left: #{@status == 'open' ? '0' : '130'}px;"}
.both
.both
#table1
%table#myTable.tablesorter.tracker{:cellpadding => "0", :cellspacing => "0"}
%thead
%tr
%th.th1{:colspan => "2"}=t('layout.issues.number')
%th{:colspan => "2"}=t('layout.issues.description')
%tbody
= render :partial => 'issues/issue', :collection => @issues
= will_paginate @issues

View File

@ -1,11 +1,8 @@
.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 => 'projects/submenu'
-render :partial => 'issues/create_sidebar'
-content_for :right_nopadding do
dummy
%h3.bpadding10= t("layout.issues.create_header")
= form_for :issue, :url => project_issues_path(@project), :html => { :class => 'form issue' } do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -1,32 +1,31 @@
.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('activerecord.attributes.issue.status')
\:
= @issue.status
%p
%b
= t('layout.issues.subscribe')
\:
- if @issue.subscribes.exists? :user_id => current_user.id
= link_to t('layout.issues.unsubscribe_btn'), project_issue_subscribe_path(@project, @issue, current_user.id), :method => :delete
- else
= link_to t('layout.issues.subscribe_btn'), project_issue_subscribes_path(@project, @issue), :method => :post
-render :partial => 'projects/submenu'
-render :partial => 'issues/create_sidebar'
-content_for :right_nopadding do
dummy
%h3.issue_title=@issue.title
.activity
.top
.image
=image_tag(@issue.creator.avatar(42), :alt => 'avatar') if @issue.creator
.text
%span.name=link_to(@issue.creator.uname, user_path(@issue.creator)) if @issue.creator
%br/
%span.date=@issue.created_at.to_s(:long)
%br/
.both
.fulltext.view.issue_body=@issue.body
.both
%br
- if can? :write, @issue.project
=link_to t('layout.edit'), '#', :id => 'edit_issue_content', :class => 'button'
=form_for :issue, :url => [@project, @issue], :method => :put, :html => { :class => 'edit_form issue', :style => 'display:none;' } do |f|
.leftlist= t('activerecord.attributes.issue.title') + ':'
.rightlist= f.text_field :title
.leftlist= t('activerecord.attributes.issue.body') + ':'
.rightlist= f.text_area :body
.both
=f.submit t('layout.update'), :id => 'update_issue_content'
=link_to t('layout.issues.cancel_button'), '#', :id => 'cancel_edit_issue_content', :class => 'button'
.hr
= render :partial => "comments/list", :locals => {:list => @issue.comments.order(:created_at), :project => @project, :commentable => @issue}
=render :partial => 'issues/status'

View File

@ -43,7 +43,7 @@
%article
- if content_for?(:sidebar)
%aside= yield :sidebar
.right= yield
.right{:class => content_for?(:right_nopadding) ? ' nopadding' : ''}= yield
- else
.all= yield
.both

View File

@ -0,0 +1,4 @@
- if content_for?(:sidebar)
%aside= yield :sidebar
.right= yield
.both

View File

@ -8,7 +8,7 @@
%li= link_to t("project_menu.project"), project_path(@project), {:class => (act == :show && contr == :projects) ? 'active' : ''}
%li= link_to t("project_menu.commits"), commits_path(@project), {:class => (act == :index && contr == :commits) ? 'active' : ''}
%li= link_to t("project_menu.builds"), project_build_lists_path(@project), {:class => (act == :index && contr == :builds) ? 'active' : ''}
%li= link_to t("project_menu.tracker"), project_issues_path(@project), {:class => (act == :index && contr == :issues) ? 'active' : ''}
%li= link_to t("project_menu.tracker"), project_issues_path(@project), {:class => contr == :issues ? 'active' : ''}
%li= link_to t("project_menu.wiki"), project_wiki_index_path(@project), {:class => contr == :wiki ? 'active' : ''}
%li= link_to t("project_menu.readme"), "#" #pending
%li= link_to t("project_menu.settings"), edit_project_path(@project), {:class => (act == :edit && contr == :projects) ? 'active' : ''}

View File

@ -23,6 +23,7 @@ en:
show: View
cancel: Cancel
create: Create
update: Update
delete: Erase
save: Save
search: Search
@ -108,23 +109,6 @@ en:
edit_header: Edit category
confirm_delete: Are you sure to delete this category?
issues:
list: List
edit: Edit
comments_header: Comments
new: New task
list_header: List
confirm_delete: Are you sure to delete this task?
edit_header: Task edit
new_header: New task
statuses:
open: Opened
closed: Closed
any: Any
subscribe: Subscribe
subscribe_btn: Subscribe
unsubscribe_btn: Unsubscribe
comments:
confirm_delete: Are you sure to delete the comment?
new_header: New comment
@ -356,23 +340,6 @@ en:
edit_header: Edit category
confirm_delete: Are you sure to delete this category?
issues:
list: List
edit: Edit
comments_header: Comments
new: New task
list_header: List
confirm_delete: Are you sure to delete this task?
edit_header: Task edit
new_header: New task
statuses:
open: Opened
closed: Closed
any: Any
subscribe: Subscribe
subscribe_btn: Subscribe
unsubscribe_btn: Unsubscribe
commits:
subscribe_btn: Subscribe to commit
unsubscribe_btn: Unsubscribe from commit
@ -538,11 +505,6 @@ en:
save_error: Comment saves error
destroyed: Comment deleted
issue:
saved: Task saved
save_error: Task saves error
destroyed: Task deleted
project:
saved: Project saved
save_error: Project saves error
@ -685,14 +647,6 @@ en:
body: Content
user: Author
issue:
title: Title
body: Content
user: Assigned
user_id: Assigned
project: Project
status: Status
private_user:
login: Login
password: Password

View File

@ -0,0 +1,57 @@
en:
activerecord:
attributes:
issue:
title: Name
body: Description
user: Assigned
user_id: Assigned
project: Project
status: Status
layout:
issues:
accessory: Accessory issues
list: List
all: All
to_me: Assigned to me
edit: Edit
search: Search issue...
comments_header: Comments
new: New issue
list_header: List
confirm_delete: Are you sure to delete this task?
edit_header: Task edit
create_header: Create task
statuses:
open: Opened
closed: Closed
any: Any
status:
open: Открыто
closed: Closed
subscribe: Subscribe
subscribe_btn: Subscribe
unsubscribe_btn: Unsubscribe
number: Number
description: Description
by: by
labels: Labels
invalid_labels: Invalid hex color code
new_label: New label name
update_label: Update label
label_custom_color: Custom color
label_manage: Manage
executor: Executor
search_user: Search user...
search_labels: Search labels...
choose_user_on_left: Choose executor on the left
choose_labels_on_left: Choose labels on the left
at: at
cancel_button: Cancel
flash:
issue:
saved: Task saved
save_error: Task saves error
destroyed: Task deleted

View File

@ -0,0 +1,57 @@
ru:
activerecord:
attributes:
issue:
title: Название
body: Описание
user: Назначена
user_id: Назначена
project: Проект
status: Статус
layout:
issues:
accessory: Принадлежность заданий
list: Список
all: Все
to_me: Назначенные мне
edit: Редактировать
search: Найти задачу...
comments_header: Комментарии
new: Добавить задачу
list_header: Список
confirm_delete: Вы уверены, что хотите удалить эту задачу?
edit_header: Редактирование задачи
create_header: Создать задачу
statuses:
open: Открытые
closed: Закрытые
any: Все
status:
open: Открыто
closed: Закрыто
subscribe: Подписка на уведомления
subscribe_btn: Подписаться
unsubscribe_btn: Отписаться
number: Номер
description: Описание
by: ' '
labels: Метки
invalid_labels: Неверный hex код
new_label: Название новой метки
update_label: Обновить метку
label_custom_color: Свой цвет
label_manage: Управление
executor: Исполнитель
search_user: Найти пользователя...
search_labels: Найти метки...
choose_user_on_left: выберите исполнителя слева
choose_labels_on_left: выберите метки слева
at: в
cancel_button: Отменить
flash:
issue:
saved: Задача успешно сохранена
save_error: Не удалось сохранить задачу
destroyed: Задача успешно удалена

View File

@ -37,6 +37,7 @@ ru:
false_: Нет
publish: Опубликовать
add: Добавить
update: Обновить
upload: Загрузить
not_access: Нет доступа!
owner: Владелец
@ -108,23 +109,6 @@ ru:
edit_header: Редактировать категорию
confirm_delete: Вы уверены, что хотите удалить эту категорию?
issues:
list: Список
edit: Редактировать
comments_header: Комментарии
new: Новая задача
list_header: Список
confirm_delete: Вы уверены, что хотите удалить эту задачу?
edit_header: Редактирование задачи
new_header: Новая задача
statuses:
open: Открытые
closed: Закрытые
any: Все
subscribe: Подписка на уведомления
subscribe_btn: Подписаться
unsubscribe_btn: Отписаться
comments:
confirm_delete: Вы уверены, что хотите удалить комментарий?
new_header: Новый комментарий
@ -410,11 +394,6 @@ ru:
save_error: Ошибка сохранения комментария
destroyed: Комментарий удален
issue:
saved: Задача успешно сохранена
save_error: Не удалось сохранить задачу
destroyed: Задача успешно удалена
project:
saved: Проект успешно сохранен
save_error: Не удалось сохранить проект
@ -547,14 +526,6 @@ ru:
body: Содержание
user: Автор
issue:
title: Заголовок
body: Содержание
user: Назначена
user_id: Назначена
project: Проект
status: Статус
private_user:
login: Логин
password: Пароль

View File

@ -117,10 +117,18 @@ Rosa::Application.routes.draw do
match 'compare/*versions' => 'wiki#compare', :as => :compare_versions, :via => :get
end
end
resources :issues do
resources :issues, :except => :edit do
resources :comments, :only => [:edit, :create, :update, :destroy]
resources :subscribes, :only => [:create, :destroy]
collection do
post :create_label
get :search_collaborators
get :search_labels
end
end
post "labels/:label_id" => "issues#destroy_label", :as => :issues_delete_label
post "labels/:label_id/update" => "issues#update_label", :as => :issues_update_label
resource :repo, :controller => "git/repositories", :only => [:show]
resources :build_lists, :only => [:index, :new, :create]

View File

@ -0,0 +1,5 @@
class AddCreatorToIssue < ActiveRecord::Migration
def change
add_column :issues, :creator_id, :integer
end
end

View File

@ -0,0 +1,21 @@
class CreateLabels < ActiveRecord::Migration
def change
create_table :labels do |t|
t.string :name, :null => false
t.string :color, :null => false
t.references :project
t.timestamps
end
create_table :labelings do |t|
t.references :label, :null => false
t.references :issue
t.timestamps
end
add_index :labelings, :issue_id
add_index :labels, :project_id
end
end

View File

@ -0,0 +1,5 @@
class AddClosedAtToIssue < ActiveRecord::Migration
def change
add_column :issues, :closed_at, :datetime
end
end

View File

@ -0,0 +1,6 @@
class AddClosedByToIssue < ActiveRecord::Migration
def change
add_column :issues, :closed_by, :integer
end
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe Label do
pending "add some examples to (or delete) #{__FILE__}"
end