Merge pull request #2980 from evazion/feat-bans

Improve /bans listing
This commit is contained in:
Albert Yi
2017-04-20 14:04:10 -07:00
committed by GitHub
10 changed files with 126 additions and 13 deletions

View File

@@ -27,6 +27,9 @@ $preview_flagged_color: #F00;
$preview_sample_warning_color: hsl(0, 100%, 90%); // light red $preview_sample_warning_color: hsl(0, 100%, 90%); // light red
$preview_quality_warning_color: hsl(50, 100%, 90%); // light yellow $preview_quality_warning_color: hsl(50, 100%, 90%); // light yellow
$error_color: hsl(0, 100%, 95%); // light red
$success_color: hsl(120, 100%, 95%); // light green
@mixin border-radius($val) { @mixin border-radius($val) {
-moz-border-radius: $val; -moz-border-radius: $val;
-webkit-border-radius: $val; -webkit-border-radius: $val;

View File

@@ -40,6 +40,22 @@ table.striped {
} }
} }
/*
* A table where one column expands to fill the screen, while the
* other columns shrink to fit their contents.
*/
table.autofit {
td, th, .col-fit {
white-space: nowrap;
padding-right: 2em;
}
.col-expand {
white-space: normal;
width: 100%;
}
}
table.search { table.search {
tr { tr {
height: 2em; height: 2em;

View File

@@ -0,0 +1,19 @@
@import "../common/000_vars.scss";
#c-bans #a-index {
tr[data-expired="true"] {
background-color: $success_color !important;
&:hover {
background-color: darken($success_color, 5%) !important;
}
}
tr[data-expired="false"] {
background-color: $error_color !important;
&:hover {
background-color: darken($error_color, 5%) !important;
}
}
}

View File

@@ -11,9 +11,10 @@ class BansController < ApplicationController
end end
def index def index
@search = Ban.search(params[:search]).order("id desc") @bans = Ban.search(params[:search]).paginate(params[:page], :limit => params[:limit])
@bans = @search.paginate(params[:page], :limit => params[:limit]) respond_with(@bans) do |fmt|
respond_with(@bans) fmt.html { @bans = @bans.includes(:user, :banner) }
end
end end
def show def show

View File

@@ -81,6 +81,14 @@ module ApplicationHelper
content_tag(:time, content || datetime, :datetime => datetime, :title => time.to_formatted_s) content_tag(:time, content || datetime, :datetime => datetime, :title => time.to_formatted_s)
end end
def humanized_duration(from, to)
duration = distance_of_time_in_words(from, to)
datetime = from.iso8601 + "/" + to.iso8601
title = "#{from.strftime("%Y-%m-%d %H:%M")} to #{to.strftime("%Y-%m-%d %H:%M")}"
raw content_tag(:time, duration, datetime: datetime, title: title)
end
def time_ago_in_words_tagged(time) def time_ago_in_words_tagged(time)
raw time_tag(time_ago_in_words(time) + " ago", time) raw time_tag(time_ago_in_words(time) + " ago", time)
end end

View File

@@ -10,13 +10,23 @@ class Ban < ActiveRecord::Base
validates_presence_of :user_id, :reason, :duration validates_presence_of :user_id, :reason, :duration
before_validation :initialize_banner_id, :on => :create before_validation :initialize_banner_id, :on => :create
scope :unexpired, -> { where("bans.expires_at > ?", Time.now) }
scope :expired, -> { where("bans.expires_at <= ?", Time.now) }
def self.is_banned?(user) def self.is_banned?(user)
exists?(["user_id = ? AND expires_at > ?", user.id, Time.now]) exists?(["user_id = ? AND expires_at > ?", user.id, Time.now])
end end
def self.reason_matches(query)
if query =~ /\*/
where("lower(bans.reason) LIKE ?", query.to_escaped_for_sql_like)
else
where("bans.reason @@ plainto_tsquery(?)", query)
end
end
def self.search(params) def self.search(params)
q = where("true") q = where("true")
return q if params.blank?
if params[:banner_name] if params[:banner_name]
q = q.where("banner_id = (select _.id from users _ where lower(_.name) = ?)", params[:banner_name].mb_chars.downcase) q = q.where("banner_id = (select _.id from users _ where lower(_.name) = ?)", params[:banner_name].mb_chars.downcase)
@@ -34,6 +44,22 @@ class Ban < ActiveRecord::Base
q = q.where("user_id = ?", params[:user_id].to_i) q = q.where("user_id = ?", params[:user_id].to_i)
end end
if params[:reason_matches].present?
q = q.reason_matches(params[:reason_matches])
end
case params[:expired]
when "true" then q = q.expired
when "false" then q = q.unexpired
end
case params[:order]
when "expires_at_desc"
q = q.order("bans.expires_at desc")
else
q = q.order("bans.id desc")
end
q q
end end

View File

@@ -74,7 +74,7 @@ class UserFeedback < ActiveRecord::Base
extend SearchMethods extend SearchMethods
def initialize_creator def initialize_creator
self.creator_id = CurrentUser.id self.creator_id ||= CurrentUser.id
end end
def user_name def user_name

View File

@@ -0,0 +1,8 @@
<%= simple_form_for(:search, method: :get, url: bans_path, defaults: { required: false }, html: { class: "inline-form" }) do |f| %>
<%= f.input :user_name, label: "User", input_html: { value: params[:search][:user_name] } %>
<%= f.input :banner_name, label: "Banner", input_html: { value: params[:search][:banner_name] } %>
<%= f.input :reason_matches, label: "Reason", hint: "Use * for wildcard", input_html: { value: params[:search][:reason_matches] } %>
<%= f.input :expired, label: "Expired?", collection: [["Yes", true], ["No", false]], include_blank: true, selected: params[:search][:expired] %>
<%= f.input :order, include_blank: false, collection: [%w[Created id_desc], %w[Expiration expires_at_desc]], selected: params[:search][:order] %>
<%= f.submit "Search" %>
<% end %>

View File

@@ -1,22 +1,34 @@
<div class="bans"> <div id="c-bans">
<div class="index"> <div id="a-index">
<h1>Bans</h1> <h1>Bans</h1>
<table class="striped" width="100%"> <%= render "search" %>
<table class="striped autofit">
<thead> <thead>
<tr> <tr>
<th>User</th> <th>User</th>
<th>Expires</th> <th>Banner</th>
<th>Banned</th>
<th>Duration</th>
<th>Reason</th> <th>Reason</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% @bans.each do |ban| %> <% @bans.each do |ban| %>
<tr id="ban-<%= ban.id %>"> <tr id="ban-<%= ban.id %>" data-expired="<%= ban.expired? %>">
<td><%= link_to_user(ban.user) %></td> <td>
<td><%= ban.expires_at %></td> <%= link_to_user(ban.user) %>
<td><%= format_text ban.reason, :ragel => true %></td> <%= link_to "»", bans_path(search: params[:search].merge(user_name: ban.user.name)) %>
</td>
<td>
<%= link_to_user(ban.banner) %>
<%= link_to "»", bans_path(search: params[:search].merge(banner_name: ban.banner.name)) %>
</td>
<td><%= time_ago_in_words_tagged(ban.created_at) %></td>
<td><%= humanized_duration(ban.created_at, ban.expires_at) %></td>
<td class="col-expand"><%= format_text ban.reason, :ragel => true %></td>
<td> <td>
<% if CurrentUser.is_moderator? %> <% if CurrentUser.is_moderator? %>
<%= link_to "Edit", edit_ban_path(ban) %> <%= link_to "Edit", edit_ban_path(ban) %>

View File

@@ -165,6 +165,26 @@ class BanTest < ActiveSupport::TestCase
end end
context "Searching for a ban" do context "Searching for a ban" do
should "find a given ban" do
CurrentUser.user = FactoryGirl.create(:admin_user)
CurrentUser.ip_addr = "127.0.0.1"
user = FactoryGirl.create(:user)
ban = FactoryGirl.create(:ban, user: user)
params = {
user_name: user.name,
banner_name: ban.banner.name,
reason: ban.reason,
expired: false,
order: :id_desc
}
bans = Ban.search(params)
assert_equal(1, bans.length)
assert_equal(ban.id, bans.first.id)
end
context "by user id" do context "by user id" do
setup do setup do
@admin = FactoryGirl.create(:admin_user) @admin = FactoryGirl.create(:admin_user)