Merge pull request #657 from warpc/648-add-filters-to-projects-page

[refs #648]: Filters by owners in projects page
This commit is contained in:
Vladimir Sharshov 2012-09-25 03:41:11 -07:00
commit 06099a15b6
15 changed files with 117 additions and 82 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View File

@ -0,0 +1,17 @@
$(document).ready(function() {
$(".div-filter-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).css("color","#FFFFFF");
checkbox.attr('checked', 'checked');
} else {
$(this).css("background-color","rgb(247, 247, 247)").css("color","#565657");
checkbox.removeAttr('checked');
}
});
});

View File

@ -30,19 +30,6 @@ $(document).ready(function() {
}); });
$("div.div-tracker-labels").live('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");
checkbox.attr('checked', 'checked');
} else {
$(this).css("background-color","rgb(247, 247, 247)");
$(this).css("color","#565657");
checkbox.removeAttr('checked');
}
return send_index_tracker_request('GET'); return send_index_tracker_request('GET');
}); });

View File

@ -698,48 +698,19 @@ div.toolbar a.button {
float: left; float: left;
} }
div.toolbar div.legend { div.toolbar, table.dataTable {
float: left; > .group_owner, > .user_owner, > .group, > .user { margin-right: 15px; }
margin-left: 50px; .group_owner, .user_owner, .group, .user { padding-left: 20px; }
padding: 4px 0 6px; .user_owner { background: image-url('user16g.png') no-repeat 0 0 transparent; }
.group_owner { background: image-url('group16g.png') no-repeat 0 0 transparent; }
.user { background: image-url('user16.png') no-repeat 0 0 transparent; }
.group { background: image-url('group16.png') no-repeat 0 0 transparent; }
} }
table.dataTable tr td.rights span.group, .div-filter-labels {
div.toolbar div.legend.rights span.group { .group { background: image-url('group16b.png') no-repeat 0 0 transparent; }
background: image-url('group16.png') no-repeat 0 0 transparent; .user { background: image-url('user16b.png') no-repeat 0 0 transparent; }
} .user, .group { padding-left: 20px; }
table.dataTable tr td.rights span.user,
div.toolbar div.legend.rights span.user {
background: image-url('user16.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.group_owner,
div.toolbar div.legend.rights span.group_owner {
background: image-url('group16g.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.user_owner,
div.toolbar div.legend.rights span.user_owner {
background: image-url('user16g.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.group_owner,
div.toolbar div.legend.rights span.group_owner,
table.dataTable tr td.rights span.user_owner,
div.toolbar div.legend.rights span.user_owner,
table.dataTable tr td.rights span.group,
div.toolbar div.legend.rights span.group,
table.dataTable tr td.rights span.user,
div.toolbar div.legend.rights span.user {
padding-left: 20px;
}
div.toolbar div.legend.rights span.group_owner,
div.toolbar div.legend.rights span.user_owner,
div.toolbar div.legend.rights span.group,
div.toolbar div.legend.rights span.user {
margin: 0 10px;
} }
.root { .root {

View File

@ -1931,7 +1931,7 @@ a.button.width100 {
width: 100px; width: 100px;
} }
.div-tracker-labels { .div-filter-labels {
margin: 2px 13px 2px 0px; margin: 2px 13px 2px 0px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;

View File

@ -1,5 +1,6 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
class Projects::ProjectsController < Projects::BaseController class Projects::ProjectsController < Projects::BaseController
include ProjectsHelper
before_filter :authenticate_user! before_filter :authenticate_user!
load_and_authorize_resource :id_param => :project_name # to force member actions load load_and_authorize_resource :id_param => :project_name # to force member actions load
@ -7,8 +8,17 @@ class Projects::ProjectsController < Projects::BaseController
@projects = Project.accessible_by(current_ability, :membered) @projects = Project.accessible_by(current_ability, :membered)
respond_to do |format| respond_to do |format|
format.html { @projects = @projects.recent.paginate(:page => params[:page], :per_page => 25) } format.html {
format.json { @projects = prepare_list(@projects) } @all_projects = @projects
@groups = current_user.groups
@owners = User.where(:id => @projects.where(:owner_type => 'User').uniq.pluck(:owner_id))
@projects = @projects.recent.paginate(:page => params[:page], :per_page => 25)
}
format.json {
selected_groups = params[:groups] || []
selected_owners = params[:users] || []
@projects = prepare_list(@projects, selected_groups, selected_owners)
}
end end
end end
@ -99,7 +109,7 @@ class Projects::ProjectsController < Projects::BaseController
protected protected
def prepare_list(projects) def prepare_list(projects, groups, owners)
res = {} res = {}
colName = ['name'] colName = ['name']
@ -108,7 +118,13 @@ class Projects::ProjectsController < Projects::BaseController
order = "#{colName[sort_col.to_i]} #{sort_dir}" order = "#{colName[sort_col.to_i]} #{sort_dir}"
res[:total_count] = projects.count res[:total_count] = projects.count
if groups.present? || owners.present?
projects = projects.by_owners(groups, owners)
end
projects = projects.search(params[:sSearch]).search_order if params[:sSearch].present? projects = projects.search(params[:sSearch]).search_order if params[:sSearch].present?
res[:filtered_count] = projects.count res[:filtered_count] = projects.count
projects = projects.order(order) projects = projects.order(order)

View File

@ -1,5 +1,25 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
module ProjectsHelper module ProjectsHelper
def options_for_filters(all_projects, groups, owners)
projects_count_by_groups = all_projects.where(:owner_id => groups, :owner_type => 'Group').
group(:owner_id).count
projects_count_by_owners = all_projects.where(:owner_id => owners, :owner_type => 'User').
group(:owner_id).count
(groups + owners).map do |o|
class_name = o.class.name
{
:id => "#{class_name.downcase}-#{o.id}",
:color => '0054a6',
:selected => false,
:check_box_name => class_name.downcase.pluralize,
:check_box_value => o.id,
:name => content_tag(:div, content_tag(:span, o.uname, :class => class_name.downcase)),
:uname => o.uname, # only for sorting
:count => o.is_a?(User) ? projects_count_by_owners[o.id] : projects_count_by_groups[o.id]
}
end.sort_by{ |f| f[:uname] }
end
def git_repo_url(name) def git_repo_url(name)
if current_user if current_user
"#{request.protocol}#{current_user.uname}@#{request.host_with_port}/#{name}.git" "#{request.protocol}#{current_user.uname}@#{request.host_with_port}/#{name}.git"

View File

@ -49,6 +49,10 @@ class Project < ActiveRecord::Base
WHERE (ptr.repository_id = #{ repository_id }) WHERE (ptr.repository_id = #{ repository_id })
) )
) } ) }
scope :by_owners, lambda { |group_owner_ids, user_owner_ids|
where("(projects.owner_id in (?) AND projects.owner_type = 'Group') OR (projects.owner_id in (?) AND projects.owner_type = 'User')", group_owner_ids, user_owner_ids)
}
before_create :set_maintainer before_create :set_maintainer
after_save :attach_to_personal_repository after_save :attach_to_personal_repository

View File

@ -3,16 +3,15 @@
#labels-stock #labels-stock
=form_tag project_issues_path(@project), :id => 'filter_labels', :method => :get do =form_tag project_issues_path(@project), :id => 'filter_labels', :method => :get do
- @project.labels.each_with_index do |label, index| - @project.labels.each_with_index do |label, index|
.div-tracker-labels{:id => "label-#{label.name.parameterize}", :style => @labels.include?(label.name) ? "background-color:##{label.color};color:#FFF" : ''} =render 'projects/shared/filter_label',
.div-label-left :id => label.name.parameterize,
.label :selected => @labels.include?(label.name),
.flag{:id => "flag-#{label.name.parameterize}", :style => "background-color: ##{label.color};"} :extra_classes => 'div-tracker-labels',
.labeltext=label.name :color => label.color,
=check_box_tag 'labels[]', label.name, @labels.include?(label.name), :style => 'display:none' :check_box_name => 'labels',
.both :check_box_value => label.name,
.div-label-right=Labeling.joins(:label).where(:labels => {:name => label.name, :project_id => @project.id}).count :name => label.name,
.both :count => Labeling.joins(:label).where(:labels => {:name => label.name, :project_id => @project.id}).count
.both
- if can? :write, @project - if can? :write, @project
%a#manage-labels.button.tmargin10{:href => "#labels-stock"}=t('layout.issues.label_manage') %a#manage-labels.button.tmargin10{:href => "#labels-stock"}=t('layout.issues.label_manage')
#labels-edit{:style => "display: none;"} #labels-edit{:style => "display: none;"}

View File

@ -0,0 +1,12 @@
-content_for :sidebar do
- if current_user
=form_tag projects_path, :id => 'filter_projects', :method => :get do
.bordered.bpadding20
=tracker_search_field(:search, t('layout.find_project'))
- if can?(:create, Project)
.bordered.bpadding20
= link_to t('layout.projects.new'), new_project_path, :class => 'button'
.bordered.bpadding20
%h3=t('layout.relations.filters')
- options_for_filters(@all_projects, @groups, @owners).each do |options|
=render 'projects/shared/filter_label', options

View File

@ -1,12 +1,8 @@
-set_meta_tags :title => t('layout.projects.list_header') -set_meta_tags :title => t('layout.projects.list_header')
-render 'filters'
.toolbar .toolbar
= link_to t('layout.projects.new'), new_project_path, :class => 'button' if can?(:create, Project) -%w(user_owner group_owner user group).each do |el|
%span{:class => el}=t "layout.relations.#{el}"
.legend.rights
%span.user_owner= t("layout.relations.user_owner")
%span.group_owner= t("layout.relations.group_owner")
%span.user= t("layout.relations.user")
%span.group= t("layout.relations.group")
.both .both
- columns = [{:type => 'html'}, - columns = [{:type => 'html'},
{:type => 'html', :sortable => false, :searchable => false}, {:type => 'html', :sortable => false, :searchable => false},
@ -29,15 +25,11 @@
%th.th2= t("activerecord.attributes.project.description") %th.th2= t("activerecord.attributes.project.description")
%th.th3= t("layout.projects.role") %th.th3= t("layout.projects.role")
%th.th4= t("layout.projects.remove_user") %th.th4= t("layout.projects.remove_user")
%tr.search
%th{:colspan => 4}
<input class="gray" type="text" value="#{ t('layout.find_project') }">
%tbody= render :partial => 'projects/projects/project', :collection => @projects %tbody= render :partial => 'projects/projects/project', :collection => @projects
:javascript :javascript
$(document).ready(function() { $(document).ready(function() {
var isUpdateDataTable = null;
var JsonParser = function (json) { var JsonParser = function (json) {
var firstColumn = function(row) { var firstColumn = function(row) {
@ -99,16 +91,17 @@
#{ format_columns_for_datatable(columns) } #{ format_columns_for_datatable(columns) }
], ],
"fnServerData": function ( sSource, aoData, fnCallback ) { "fnServerData": function ( sSource, aoData, fnCallback ) {
$.getJSON( sSource, aoData, function (json) { if (isUpdateDataTable != null) { isUpdateDataTable.abort(); }
_.each($('#filter_projects').serializeArray(), function(dv) { aoData.push(dv); });
isUpdateDataTable = $.getJSON( sSource, aoData, function (json) {
json.aaData = JsonParser(json); json.aaData = JsonParser(json);
fnCallback(json); fnCallback(json);
} ); } );
} }
}); });
$('#datatable_wrapper').append("<div class='both'></div>");
var $search = $('tr.search input[type="text"]'); var $search = $('#search');
$search.live('blur', function() { $search.live('blur', function() {
$this = $(this); $this = $(this);
if ($this.val() == '') { if ($this.val() == '') {
@ -128,6 +121,9 @@
$search.live('keyup', function() { $search.live('keyup', function() {
oTable.fnFilter(this.value); oTable.fnFilter(this.value);
}); });
$(".div-filter-labels").live('click', function() {
oTable.dataTable().fnDraw();
});
}); });
=# will_paginate =# will_paginate

View File

@ -0,0 +1,11 @@
- extra_classes ||= ''
.div-filter-labels{:id => "label-#{id}", :class => extra_classes, :style => selected ? "background-color:##{color};color:#FFF" : ''}
.div-label-left
.label
.flag{:id => "flag-#{id}", :style => "background-color: ##{color};"}
.labeltext=name
=check_box_tag "#{check_box_name}[]", check_box_value, selected, :style => 'display:none'
.both
.div-label-right=count
.both
.both

View File

@ -1,6 +1,7 @@
en: en:
layout: layout:
relations: relations:
filters: Filters
user_owner: I'm owner user_owner: I'm owner
group_owner: I'm member of owner group group_owner: I'm member of owner group
user: I'm collaborator user: I'm collaborator

View File

@ -1,6 +1,7 @@
ru: ru:
layout: layout:
relations: relations:
filters: Фильтры
user_owner: Я - владелец user_owner: Я - владелец
group_owner: Я состою в группе-владельце group_owner: Я состою в группе-владельце
user: Я - участник user: Я - участник