Merge remote-tracking branch 'upstream/pull/1576'

This commit is contained in:
Tom Hughes 2018-06-16 11:45:23 +01:00
commit 7441f15b4f
40 changed files with 1887 additions and 5 deletions

View file

@ -67,7 +67,7 @@ Metrics/AbcSize:
# Offense count: 41
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 247
Max: 257
# Offense count: 12
# Configuration parameters: CountBlocks.

View file

@ -73,6 +73,9 @@ gem "omniauth-windowslive"
# Markdown formatting support
gem "redcarpet"
# For status transitions of Issues
gem "aasm"
# Load libxml support for XML parsing and generation
gem "libxml-ruby", ">= 2.0.5", :require => "libxml"

View file

@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
SystemTimer (1.2.3)
aasm (4.1.0)
actioncable (5.1.5)
actionpack (= 5.1.5)
nio4r (~> 2.0)
@ -361,6 +362,7 @@ PLATFORMS
DEPENDENCIES
SystemTimer (>= 1.1.3)
aasm
actionpack-page_caching
annotate
autoprefixer-rails

View file

@ -2820,3 +2820,59 @@ input.richtext_title[type="text"] {
display: none;
}
}
.read-reports {
background: #eee;
opacity: 0.7;
}
.report-related-block {
display:inline-block;
}
.report-block {
width:475px;
float:left;
margin-right:100px;
}
.related-reports {
width: 280px;
float: right;
ul {
padding-left: $lineheight;
margin-bottom: 0;
li {
list-style: disc;
}
}
}
.issue-comments {
width:475px;
}
.issues-list {
td:nth-child(2) {
white-space: nowrap;
}
}
.report-disclaimer {
background: #fff1f0;
color: #d85030;
border-color: rgba(216, 80, 48, 0.3);
padding: 10px 20px;
margin-bottom: $lineheight;
ul {
padding-left: $lineheight;
margin-bottom: 0;
li {
list-style: disc;
}
}
}

View file

@ -0,0 +1,37 @@
class IssueCommentsController < ApplicationController
layout "site"
before_action :authorize_web
before_action :require_user
before_action :check_permission
def create
@issue = Issue.find(params[:issue_id])
comment = @issue.comments.build(issue_comment_params)
comment.user = current_user
comment.save!
notice = t(".comment_created")
reassign_issue(@issue) if params[:reassign]
redirect_to @issue, :notice => notice
end
private
def issue_comment_params
params.require(:issue_comment).permit(:body)
end
def check_permission
unless current_user.administrator? || current_user.moderator?
flash[:error] = t("application.require_moderator_or_admin.not_a_moderator_or_admin")
redirect_to root_path
end
end
# This sort of assumes there are only two roles
def reassign_issue(issue)
role = (Issue::ASSIGNED_ROLES - [issue.assigned_role]).first
issue.assigned_role = role
issue.save!
end
end

View file

@ -0,0 +1,90 @@
class IssuesController < ApplicationController
layout "site"
before_action :authorize_web
before_action :require_user
before_action :check_permission
before_action :find_issue, :only => [:show, :resolve, :reopen, :ignore]
def index
@title = t ".title"
@issue_types = []
@issue_types.concat %w[Note] if current_user.moderator?
@issue_types.concat %w[DiaryEntry DiaryComment User] if current_user.administrator?
@users = User.joins(:roles).where(:user_roles => { :role => current_user.roles.map(&:role) }).distinct
@issues = Issue.where(:assigned_role => current_user.roles.map(&:role))
# If search
if params[:search_by_user] && params[:search_by_user].present?
@find_user = User.find_by(:display_name => params[:search_by_user])
if @find_user
@issues = @issues.where(:reported_user_id => @find_user.id)
else
@issues = @issues.none
flash.now[:warning] = t(".user_not_found")
end
end
@issues = @issues.where(:status => params[:status]) if params[:status] && params[:status].present?
@issues = @issues.where(:reportable_type => params[:issue_type]) if params[:issue_type] && params[:issue_type].present?
if params[:last_updated_by] && params[:last_updated_by].present?
last_updated_by = params[:last_updated_by].to_s == "nil" ? nil : params[:last_updated_by].to_i
@issues = @issues.where(:updated_by => last_updated_by)
end
end
def show
@read_reports = @issue.read_reports
@unread_reports = @issue.unread_reports
@comments = @issue.comments
@related_issues = @issue.reported_user.issues.where(:assigned_role => current_user.roles.map(&:role)) if @issue.reported_user
@new_comment = IssueComment.new(:issue => @issue)
end
# Status Transistions
def resolve
if @issue.resolve
@issue.save!
redirect_to @issue, :notice => t(".resolved")
else
render :show
end
end
def ignore
if @issue.ignore
@issue.updated_by = current_user.id
@issue.save!
redirect_to @issue, :notice => t(".ignored")
else
render :show
end
end
def reopen
if @issue.reopen
@issue.updated_by = current_user.id
@issue.save!
redirect_to @issue, :notice => t(".reopened")
else
render :show
end
end
private
def find_issue
@issue = Issue.find(params[:id])
end
def check_permission
unless current_user.administrator? || current_user.moderator?
flash[:error] = t("application.require_moderator_or_admin.not_a_moderator_or_admin")
redirect_to root_path
end
end
end

View file

@ -0,0 +1,42 @@
class ReportsController < ApplicationController
layout "site"
before_action :authorize_web
before_action :require_user
def new
if required_new_report_params_present?
@report = Report.new
@report.issue = Issue.find_or_initialize_by(create_new_report_params)
else
redirect_to root_path, :notice => t(".missing_params")
end
end
def create
@report = current_user.reports.new(report_params)
@report.issue = Issue.find_or_initialize_by(:reportable_id => params[:report][:issue][:reportable_id], :reportable_type => params[:report][:issue][:reportable_type])
if @report.save
@report.issue.save
@report.issue.reopen! unless @report.issue.open?
redirect_to helpers.reportable_url(@report.issue.reportable), :notice => t(".successful_report")
else
redirect_to new_report_path(:reportable_type => @report.issue.reportable_type, :reportable_id => @report.issue.reportable_id), :notice => t(".provide_details")
end
end
private
def required_new_report_params_present?
create_new_report_params["reportable_id"].present? && create_new_report_params["reportable_type"].present?
end
def create_new_report_params
params.permit(:reportable_id, :reportable_type)
end
def report_params
params[:report].permit(:details, :category)
end
end

View file

@ -0,0 +1,27 @@
module IssuesHelper
def reportable_url(reportable)
case reportable
when DiaryEntry
url_for(:controller => reportable.class.name.underscore, :action => :view, :display_name => reportable.user.display_name, :id => reportable.id)
when User
url_for(:controller => reportable.class.name.underscore, :action => :view, :display_name => reportable.display_name)
when DiaryComment
url_for(:controller => reportable.diary_entry.class.name.underscore, :action => :view, :display_name => reportable.diary_entry.user.display_name, :id => reportable.diary_entry.id, :anchor => "comment#{reportable.id}")
when Note
url_for(:controller => :browse, :action => :note, :id => reportable.id)
end
end
def reportable_title(reportable)
case reportable
when DiaryEntry
reportable.title
when User
reportable.display_name
when DiaryComment
I18n.t("issues.helper.reportable_title.diary_comment", :entry_title => reportable.diary_entry.title, :comment_id => reportable.id)
when Note
I18n.t("issues.helper.reportable_title.note", :note_id => reportable.id)
end
end
end

View file

@ -0,0 +1,5 @@
module ReportsHelper
def report_link(name, reportable)
link_to name, new_report_url(:reportable_id => reportable.id, :reportable_type => reportable.class.name)
end
end

104
app/models/issue.rb Normal file
View file

@ -0,0 +1,104 @@
# == Schema Information
#
# Table name: issues
#
# id :integer not null, primary key
# reportable_type :string not null
# reportable_id :integer not null
# reported_user_id :integer
# status :enum default("open"), not null
# assigned_role :enum not null
# resolved_at :datetime
# resolved_by :integer
# updated_by :integer
# reports_count :integer default(0)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_issues_on_assigned_role (assigned_role)
# index_issues_on_reportable_type_and_reportable_id (reportable_type,reportable_id)
# index_issues_on_reported_user_id (reported_user_id)
# index_issues_on_status (status)
# index_issues_on_updated_by (updated_by)
#
# Foreign Keys
#
# issues_reported_user_id_fkey (reported_user_id => users.id)
# issues_resolved_by_fkey (resolved_by => users.id)
# issues_updated_by_fkey (updated_by => users.id)
#
class Issue < ActiveRecord::Base
belongs_to :reportable, :polymorphic => true
belongs_to :reported_user, :class_name => "User", :foreign_key => :reported_user_id
belongs_to :user_resolved, :class_name => "User", :foreign_key => :resolved_by
belongs_to :user_updated, :class_name => "User", :foreign_key => :updated_by
has_many :reports, :dependent => :destroy
has_many :comments, :class_name => "IssueComment", :dependent => :destroy
validates :reportable_id, :uniqueness => { :scope => [:reportable_type] }
ASSIGNED_ROLES = %w[administrator moderator].freeze
validates :assigned_role, :presence => true, :inclusion => ASSIGNED_ROLES
before_validation :set_default_assigned_role
before_validation :set_reported_user
scope :with_status, ->(issue_status) { where(:status => statuses[issue_status]) }
def read_reports
resolved_at.present? ? reports.where("updated_at < ?", resolved_at) : nil
end
def unread_reports
resolved_at.present? ? reports.where("updated_at >= ?", resolved_at) : reports
end
include AASM
aasm :column => :status, :no_direct_assignment => true do
state :open, :initial => true
state :ignored
state :resolved
event :ignore do
transitions :from => :open, :to => :ignored
end
event :resolve do
transitions :from => :open, :to => :resolved
after do
self.resolved_at = Time.now.getutc
end
end
event :reopen do
transitions :from => :resolved, :to => :open
transitions :from => :ignored, :to => :open
end
end
private
def set_reported_user
self.reported_user = case reportable.class.name
when "User"
reportable
when "Note"
reportable.author
else
reportable.user
end
end
def set_default_assigned_role
if assigned_role.blank?
self.assigned_role = case reportable
when Note then "moderator"
else "administrator"
end
end
end
end

View file

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: issue_comments
#
# id :integer not null, primary key
# issue_id :integer not null
# user_id :integer not null
# body :text not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_issue_comments_on_issue_id (issue_id)
# index_issue_comments_on_user_id (user_id)
#
# Foreign Keys
#
# issue_comments_issue_id_fkey (issue_id => issues.id)
# issue_comments_user_id_fkey (user_id => users.id)
#
class IssueComment < ActiveRecord::Base
belongs_to :issue
belongs_to :user
validates :body, :presence => true
validates :user, :presence => true
validates :issue, :presence => true
end

42
app/models/report.rb Normal file
View file

@ -0,0 +1,42 @@
# == Schema Information
#
# Table name: reports
#
# id :integer not null, primary key
# issue_id :integer not null
# user_id :integer not null
# details :text not null
# category :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_reports_on_issue_id (issue_id)
# index_reports_on_user_id (user_id)
#
# Foreign Keys
#
# reports_issue_id_fkey (issue_id => issues.id)
# reports_user_id_fkey (user_id => users.id)
#
class Report < ActiveRecord::Base
belongs_to :issue, :counter_cache => true
belongs_to :user
validates :issue, :presence => true
validates :user, :presence => true
validates :details, :presence => true
validates :category, :presence => true
def self.categories_for(reportable)
case reportable.class.name
when "DiaryEntry" then %w[spam offensive threat other]
when "DiaryComment" then %w[spam offensive threat other]
when "User" then %w[spam offensive threat vandal other]
when "Note" then %w[spam personal abusive other]
else %w[other]
end
end
end

View file

@ -75,6 +75,11 @@ class User < ActiveRecord::Base
has_many :roles, :class_name => "UserRole"
has_many :issues, :class_name => "Issue", :foreign_key => :reported_user_id
has_many :issue_comments
has_many :reports
scope :visible, -> { where(:status => %w[pending active confirmed]) }
scope :active, -> { where(:status => %w[active confirmed]) }
scope :identifiable, -> { where(:data_public => true) }

View file

@ -23,6 +23,10 @@
<p class='warning'><%= t "javascripts.notes.show.anonymous_warning" %></p>
<% end -%>
<% if current_user && current_user != @note.author %>
<p class="deemphasize"><%= report_link(t(".report"), @note) %></p>
<% end %>
<% if @note_comments.length > 1 %>
<div class='note-comments'>
<ul>

View file

@ -1,6 +1,11 @@
<div class="clearfix diary-comment">
<%= user_thumbnail diary_comment.user %>
<p class="deemphasize comment-heading" id="comment<%= diary_comment.id %>"><%= raw(t('.comment_from', :link_user => (link_to h(diary_comment.user.display_name), user_path(diary_comment.user)), :comment_created_at => link_to(l(diary_comment.created_at, :format => :friendly), :anchor => "comment#{diary_comment.id}"))) %></p>
<p class="deemphasize comment-heading" id="comment<%= diary_comment.id %>"><%= raw(t('.comment_from', :link_user => (link_to h(diary_comment.user.display_name), user_path(diary_comment.user)), :comment_created_at => link_to(l(diary_comment.created_at, :format => :friendly), :anchor => "comment#{diary_comment.id}"))) %>
<% if current_user and diary_comment.user.id != current_user.id %>
| <%= report_link(t(".report"), diary_comment) %>
<% end %>
</p>
<div class="richtext"><%= diary_comment.body.to_html %></div>
<% if current_user && current_user.administrator? %>
<span>

View file

@ -31,8 +31,16 @@
<li><%= link_to t('.edit_link'), :action => 'edit', :display_name => diary_entry.user.display_name, :id => diary_entry.id %></li>
<% end %>
<% if current_user and diary_entry.user != current_user %>
<li>
<%= report_link(t(".report"), diary_entry) %>
</li>
<% end %>
<% if current_user && current_user.administrator? %>
<li><%= link_to t('.hide_link'), hide_diary_entry_path(:display_name => diary_entry.user.display_name, :id => diary_entry.id), :method => :post, :data => { :confirm => t('.confirm') } %></li>
<li>
<%= link_to t('.hide_link'), hide_diary_entry_path(:display_name => diary_entry.user.display_name, :id => diary_entry.id), :method => :post, :data => { :confirm => t('.confirm') } %>
</li>
<% end %>
</ul>
</div>

View file

@ -0,0 +1,25 @@
<div class="issue-comments">
<% comments.each do |comment| %>
<div class="comment">
<div style="float:left">
<%= link_to user_thumbnail(comment.user), user_path(comment.user.display_name) %>
</div>
<b> <%= link_to comment.user.display_name, user_path(comment.user.display_name) %> </b> <br/>
<%= comment.body %>
</div>
<span class="deemphasize">
<%= t(".created_at", :datetime => l(comment.created_at.to_datetime, :format => :friendly)) %>
</span>
<hr>
<% end %>
</div>
<br/>
<div class="comment">
<%= form_for @new_comment, url: issue_comments_path(@issue) do |f| %>
<%= richtext_area :issue_comment, :body, :cols => 10, :rows => 8, :required => true %>
<%= label_tag :reassign, t('.reassign_param') %> <%= check_box_tag :reassign, true %>
<br/>
<br/>
<%= submit_tag 'Submit' %>
<% end %>
</div>

View file

@ -0,0 +1,16 @@
<% reports.each do |report| %>
<div class="report">
<div style="float:left">
<%= link_to user_thumbnail(report.user), user_path(report.user.display_name) %>
</div>
<%= t ".reported_by_html", :category => report.category, :user => link_to(report.user.display_name, user_path(report.user.display_name)) %>
<br/>
<span class="deemphasize">
<%= t(".updated_at", :datetime => l(report.updated_at.to_datetime, :format => :friendly)) %>
</span>
<br/>
<%= report.details %>
<br/>
</div>
<hr>
<% end %>

View file

@ -0,0 +1,51 @@
<% content_for :heading do %>
<h1><%= t ".title" %></h1>
<% end %>
<%= form_tag(issues_path, :method => :get) do %>
<p><%= t ".search_guidance" %></p>
<%= select_tag :status, options_for_select(Issue.aasm.states.map(&:name).map{|state| [t(".states.#{state}"), state]}, params[:status]), :include_blank => t(".select_status"), :data => { :behavior => 'category_dropdown' } %>
<%= select_tag :issue_type, options_for_select(@issue_types, params[:issue_type]), :include_blank => t(".select_type"), :data => { :behavior => 'category_dropdown' } %>
<%= text_field_tag :search_by_user, params[:search_by_user], placeholder: t(".reported_user") %>
<%= select_tag :last_updated_by, options_for_select(@users.all.collect{|f| [f.display_name, f.id]} << [ t(".not_updated"), "nil"], params[:last_updated_by]), :include_blank => t(".select_last_updated_by"), :data => { :behavior => 'category_dropdown' } %>
<%= submit_tag t(".search"), :name => nil %>
<% end %>
<br/>
<% if @issues.length == 0 %>
<p><%= t ".issues_not_found" %></p>
<% end %>
<br/>
<table class="issues-list">
<thead>
<tr>
<th><%= t ".status" %></th>
<th><%= t ".reports" %></th>
<th><%= t ".reported_item" %></th>
<th><%= t ".reported_user" %></th>
<th><%= t ".last_updated" %></th>
</tr>
</thead>
<tbody>
<% @issues.each do |issue| %>
<tr>
<td><%= t ".states.#{issue.status}" %></td>
<td><%= link_to t(".reports_count", :count => issue.reports_count), issue %></td>
<td><%= link_to reportable_title(issue.reportable), reportable_url(issue.reportable) %></td>
<td><%= link_to issue.reported_user.display_name, user_path(issue.reported_user.display_name) if issue.reported_user %></td>
<td>
<% if issue.user_updated %>
<%= t ".last_updated_time_user_html", :user => link_to(issue.user_updated.display_name, user_path(issue.user_updated.display_name)),
:time => distance_of_time_in_words_to_now(issue.updated_at),
:title => l(issue.updated_at) %>
<% else %>
<%= t ".last_updated_time_html", :time => distance_of_time_in_words_to_now(issue.updated_at),
:title => l(issue.updated_at) %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>

View file

@ -0,0 +1,62 @@
<% content_for :heading do %>
<h2><%= t ".title", :status => @issue.status.humanize, :issue_id => @issue.id %></h2>
<p><%= @issue.reportable.model_name.human %> : <%= link_to reportable_title(@issue.reportable), reportable_url(@issue.reportable) %></p>
<p class="deemphasize">
<small>
<%= @issue.assigned_role %>
| <%= t ".reports", :count => @issue.reports.count %>
| <%= t ".report_created_at", :datetime => l(@issue.created_at.to_datetime, :format => :friendly) %>
<%= " | " + t(".last_resolved_at", :datetime => l(@issue.resolved_at.to_datetime, :format =>:friendly)) if @issue.resolved_at? %>
<%= " | " + t(".last_updated_at", :datetime => l(@issue.updated_at.to_datetime, :format => :friendly), :displayname => @issue.user_updated.display_name ) if @issue.user_updated %>
</small>
</p>
<p>
<%= link_to t('.resolve'), resolve_issue_url(@issue), :method => :post if @issue.may_resolve? %>
<% if @issue.may_ignore? %>
| <%= link_to t('.ignore'), ignore_issue_url(@issue), :method => :post %>
<% end %>
</p>
<p><%= link_to t('.reopen'), reopen_issue_url(@issue), :method => :post if @issue.may_reopen? %></p>
<% end %>
<div class="report-related-block">
<div class="report-block">
<h3><%= t ".reports_of_this_issue" %></h3>
<% if @read_reports.present? %>
<div class="read-reports">
<h4><%= t ".read_reports" %></h4>
<%= render 'reports', reports: @read_reports %>
</div>
<% end %>
<% if @unread_reports.any? %>
<div class="unread-reports">
<h4><%= t ".new_reports" %></h4>
<%= render 'reports', reports: @unread_reports %>
</div>
<% end %>
<br/>
</div>
<% if @issue.reported_user %>
<div class="related-reports">
<h3><%= t ".other_issues_against_this_user" %></h3>
<% if @related_issues.count > 1 %>
<ul>
<% @related_issues.each do |issue| %>
<% if issue.id != @issue.id %>
<li><%= link_to reportable_title(issue.reportable), issue %></li>
<% end %>
<% end %>
</ul>
<% else %>
<p><%= t ".no_other_issues" %></p>
<% end %>
</div>
<% end %>
</div>
<h3><%= t ".comments_on_this_issue" %></h3>
<%= render 'comments', comments: @comments %>

View file

@ -38,6 +38,9 @@
</nav>
<nav class='secondary'>
<ul>
<% if current_user and ( current_user.administrator? or current_user.moderator? ) %>
<li class="compact-hide <%= current_page_class(issues_path) %>"><%= link_to t('layouts.issues'), issues_path(:status => 'open') %></li>
<% end %>
<li class="compact-hide <%= current_page_class(traces_path) %>"><%= link_to t('layouts.gps_traces'), traces_path %></li>
<li class="compact-hide <%= current_page_class(diary_path) %>"><%= link_to t('layouts.user_diaries'), diary_path %></li>
<li class="compact-hide <%= current_page_class(copyright_path) %>"><%= link_to t('layouts.copyright'), copyright_path %></li>

View file

@ -0,0 +1,42 @@
<% content_for :heading do %>
<h1><%= t ".title_html", :link => link_to(reportable_title(@report.issue.reportable), reportable_url(@report.issue.reportable)) %></h1>
<% end %>
<div class="report-disclaimer">
<%= t('.disclaimer.intro') %>
<ul>
<li> <%= t('.disclaimer.not_just_mistake') %> </li>
<li> <%= t('.disclaimer.unable_to_fix') %> </li>
<li> <%= t('.disclaimer.resolve_with_user') %> </li>
</ul>
</div>
<%= form_for(@report) do |f| %>
<%= f.error_messages %>
<fieldset>
<%= f.fields_for @report.issue do |issue_form| %>
<%= issue_form.hidden_field :reportable_id %>
<%= issue_form.hidden_field :reportable_type %>
<% end %>
<div class='form-row'>
<p><%= t('.select') %></p>
<ul>
<% Report.categories_for(@report.issue.reportable).each do |c| %>
<li>
<%= radio_button :report, :category, c, :required => true %>
<%= label_tag "report_category_#{c}", t(".categories.#{@report.issue.reportable.class.name.underscore}.#{c}") %> <br/>
</li>
<% end %>
</ul>
</div>
<div class='form-row'>
<%= text_area :report, :details, :cols => 20, :rows => 5, placeholder: t('.details'), :required => true %>
</div>
<div class='buttons'>
<%= f.submit %>
</div>
</fieldset>
<% end %>

View file

@ -102,6 +102,11 @@
</li>
<% end %>
<% if current_user and @user.id != current_user.id %>
<li>
<%= report_link(t(".report"), @user) %>
</li>
<% end %>
</ul>
<% end %>

View file

@ -207,6 +207,7 @@ en:
reopened_by: "Reactivated by %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
reopened_by_anonymous: "Reactivated by anonymous <abbr title='%{exact_time}'>%{when} ago</abbr>"
hidden_by: "Hidden by %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
report: Report this note
query:
title: "Query Features"
introduction: "Click on the map to find nearby features."
@ -297,10 +298,12 @@ en:
edit_link: Edit this entry
hide_link: Hide this entry
confirm: Confirm
report: Report this entry
diary_comment:
comment_from: "Comment from %{link_user} on %{comment_created_at}"
hide_link: Hide this comment
confirm: Confirm
report: Report this comment
location:
location: "Location:"
view: "View"
@ -918,6 +921,110 @@ en:
results:
no_results: "No results found"
more_results: "More results"
issues:
index:
title: Issues
select_status: Select Status
select_type: Select Type
select_last_updated_by: Select Last Updated By
reported_user: Reported User
not_updated: Not Updated
search: Search
search_guidance: "Search Issues:"
user_not_found: User does not exist
issues_not_found: No such issues found
status: Status
reports: Reports
last_updated: Last Updated
last_updated_time_html: "<abbr title='%{title}'>%{time} ago</abbr>"
last_updated_time_user_html: "<abbr title='%{title}'>%{time} ago</abbr> by %{user}"
link_to_reports: View Reports
reported_user: Reported User
reports_count:
one: "1 Report"
other: "%{count} Reports"
reported_item: Reported Item
states:
ignored: Ignored
open: Open
resolved: Resolved
update:
new_report: Your report has been registered sucessfully
successful_update: Your report has been updated successfully
provide_details: Please provide the required details
show:
title: "%{status} Issue #%{issue_id}"
reports:
zero: No reports
one: 1 report
other: "%{count} reports"
report_created_at: "First reported at %{datetime}"
last_resolved_at: "Last resolved at %{datetime}"
last_updated_at: "Last updated at %{datetime} by %{displayname}"
resolve: Resolve
ignore: Ignore
reopen: Reopen
reports_of_this_issue: Reports of this Issue
read_reports: Read Reports
new_reports: New Reports
other_issues_against_this_user: Other issues against this user
no_other_issues: No other issues against this user.
comments_on_this_issue: Comments on this issue
resolve:
resolved: Issue status has been set to 'Resolved'
ignore:
ignored: Issue status has been set to 'Ignored'
reopen:
reopened: Issue status has been set to 'Open'
comments:
created_at: "On %{datetime}"
reassign_param: Reassign Issue?
reports:
updated_at: "On %{datetime}"
reported_by_html: "Reported as %{category} by %{user}"
helper:
reportable_title:
diary_comment: "%{entry_title}, comment #%{comment_id}"
note: "Note #{note_id}"
issue_comments:
create:
comment_created: Your comment was successfully created
reports:
new:
title_html: "Report %{link}"
missing_params: "Cannot create a new report"
details: Please provide some more details about the problem (required).
select: "Select a reason for your report:"
disclaimer:
intro: "Before sending your report to the site moderators, please ensure that:"
not_just_mistake: You are certain that the problem is not just a mistake
unable_to_fix: You are unable to fix the problem yourself or with the help of your fellow community members
resolve_with_user: You have already tried to resolve the problem with the user concerned
categories:
diary_entry:
spam: This diary entry is/contains spam
offensive: This diary entry is obscene/offensive
threat: This diary entry contains a threat
other: Other
diary_comment:
spam: This diary comment is/contains spam
offensive: This diary comment is obscene/offensive
threat: This diary comment contains a threat
other: Other
user:
spam: This user profile is/contains spam
offensive: This user profile is obscene/offensive
threat: This user profile contains a threat
vandal: This user is a vandal
other: Other
note:
spam: This note is spam
personal: This note contains personal data
abusive: This note is abusive
other: Other
create:
successful_report: Your report has been registered sucessfully
provide_details: Please provide the required details
layouts:
project_name:
# in <title>
@ -936,6 +1043,7 @@ en:
edit: Edit
history: History
export: Export
issues: Issues
data: Data
export_data: Export Data
gps_traces: GPS Traces
@ -1684,8 +1792,12 @@ en:
application:
require_cookies:
cookies_needed: "You appear to have cookies disabled - please enable cookies in your browser before continuing."
require_admin:
not_an_admin: You need to be an admin to perform that action.
require_moderator:
not_a_moderator: "You need to be a moderator to perform that action."
require_moderator_or_admin:
not_a_moderator_or_admin: You need to be a moderator or an admin to perform that action
setup_user_auth:
blocked_zero_hour: "You have an urgent message on the OpenStreetMap web site. You need to read the message before you will be able to save your edits."
blocked: "Your access to the API has been blocked. Please log-in to the web interface to find out more."
@ -1955,6 +2067,7 @@ en:
friends_diaries: "friends' diary entries"
nearby_changesets: "nearby user changesets"
nearby_diaries: "nearby user diary entries"
report: Report this User
popup:
your location: "Your location"
nearby mapper: "Nearby mapper"

View file

@ -297,6 +297,19 @@ OpenStreetMap::Application.routes.draw do
resources :user_blocks
match "/blocks/:id/revoke" => "user_blocks#revoke", :via => [:get, :post], :as => "revoke_user_block"
# issues and reports
resources :issues do
resources :comments, :controller => :issue_comments
member do
post "resolve"
post "assign"
post "ignore"
post "reopen"
end
end
resources :reports
# redactions
resources :redactions
end

View file

@ -0,0 +1,64 @@
require "migrate"
class CreateIssuesAndReports < ActiveRecord::Migration[5.0]
def up
create_enumeration :issue_status_enum, %w[open ignored resolved]
create_table :issues do |t|
t.string :reportable_type, :null => false
t.integer :reportable_id, :null => false
t.integer :reported_user_id
t.column :status, :issue_status_enum, :null => false, :default => "open"
t.column :assigned_role, :user_role_enum, :null => false
t.datetime :resolved_at
t.integer :resolved_by
t.integer :updated_by
t.integer :reports_count, :default => 0
t.timestamps :null => false
end
add_foreign_key :issues, :users, :column => :reported_user_id, :name => "issues_reported_user_id_fkey"
add_foreign_key :issues, :users, :column => :resolved_by, :name => "issues_resolved_by_fkey"
add_foreign_key :issues, :users, :column => :updated_by, :name => "issues_updated_by_fkey"
add_index :issues, [:reportable_type, :reportable_id]
add_index :issues, [:reported_user_id]
add_index :issues, [:status]
add_index :issues, [:assigned_role]
add_index :issues, [:updated_by]
create_table :reports do |t|
t.integer :issue_id, :null => false
t.integer :user_id, :null => false
t.text :details, :null => false
t.string :category, :null => false
t.timestamps :null => false
end
add_foreign_key :reports, :issues, :name => "reports_issue_id_fkey"
add_foreign_key :reports, :users, :column => :user_id, :name => "reports_user_id_fkey"
add_index :reports, :issue_id
add_index :reports, :user_id
create_table :issue_comments do |t|
t.integer :issue_id, :null => false
t.integer :user_id, :null => false
t.text :body, :null => false
t.timestamps :null => false
end
add_foreign_key :issue_comments, :issues, :name => "issue_comments_issue_id_fkey"
add_foreign_key :issue_comments, :users, :column => :user_id, :name => "issue_comments_user_id_fkey"
add_index :issue_comments, :issue_id
add_index :issue_comments, :user_id
end
def down
drop_table :issue_comments
drop_table :reports
drop_table :issues
drop_enumeration :issue_status_enum
end
end

View file

@ -60,6 +60,17 @@ CREATE TYPE gpx_visibility_enum AS ENUM (
);
--
-- Name: issue_status_enum; Type: TYPE; Schema: public; Owner: -
--
CREATE TYPE issue_status_enum AS ENUM (
'open',
'ignored',
'resolved'
);
--
-- Name: note_event_enum; Type: TYPE; Schema: public; Owner: -
--
@ -679,6 +690,78 @@ CREATE SEQUENCE gpx_files_id_seq
ALTER SEQUENCE gpx_files_id_seq OWNED BY gpx_files.id;
--
-- Name: issue_comments; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE issue_comments (
id integer NOT NULL,
issue_id integer NOT NULL,
user_id integer NOT NULL,
body text NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: issue_comments_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE issue_comments_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: issue_comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE issue_comments_id_seq OWNED BY issue_comments.id;
--
-- Name: issues; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE issues (
id integer NOT NULL,
reportable_type character varying NOT NULL,
reportable_id integer NOT NULL,
reported_user_id integer,
status issue_status_enum DEFAULT 'open'::public.issue_status_enum NOT NULL,
assigned_role user_role_enum NOT NULL,
resolved_at timestamp without time zone,
resolved_by integer,
updated_by integer,
reports_count integer DEFAULT 0,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: issues_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE issues_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: issues_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE issues_id_seq OWNED BY issues.id;
--
-- Name: languages; Type: TABLE; Schema: public; Owner: -
--
@ -980,6 +1063,40 @@ CREATE TABLE relations (
);
--
-- Name: reports; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE reports (
id integer NOT NULL,
issue_id integer NOT NULL,
user_id integer NOT NULL,
details text NOT NULL,
category character varying NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: reports_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE reports_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: reports_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE reports_id_seq OWNED BY reports.id;
--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
--
@ -1283,6 +1400,20 @@ ALTER TABLE ONLY gpx_file_tags ALTER COLUMN id SET DEFAULT nextval('gpx_file_tag
ALTER TABLE ONLY gpx_files ALTER COLUMN id SET DEFAULT nextval('gpx_files_id_seq'::regclass);
--
-- Name: issue_comments id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY issue_comments ALTER COLUMN id SET DEFAULT nextval('issue_comments_id_seq'::regclass);
--
-- Name: issues id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY issues ALTER COLUMN id SET DEFAULT nextval('issues_id_seq'::regclass);
--
-- Name: messages id; Type: DEFAULT; Schema: public; Owner: -
--
@ -1325,6 +1456,13 @@ ALTER TABLE ONLY oauth_tokens ALTER COLUMN id SET DEFAULT nextval('oauth_tokens_
ALTER TABLE ONLY redactions ALTER COLUMN id SET DEFAULT nextval('redactions_id_seq'::regclass);
--
-- Name: reports id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY reports ALTER COLUMN id SET DEFAULT nextval('reports_id_seq'::regclass);
--
-- Name: user_blocks id; Type: DEFAULT; Schema: public; Owner: -
--
@ -1505,6 +1643,22 @@ ALTER TABLE ONLY gpx_files
ADD CONSTRAINT gpx_files_pkey PRIMARY KEY (id);
--
-- Name: issue_comments issue_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issue_comments
ADD CONSTRAINT issue_comments_pkey PRIMARY KEY (id);
--
-- Name: issues issues_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issues
ADD CONSTRAINT issues_pkey PRIMARY KEY (id);
--
-- Name: languages languages_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -1601,6 +1755,14 @@ ALTER TABLE ONLY relations
ADD CONSTRAINT relations_pkey PRIMARY KEY (relation_id, version);
--
-- Name: reports reports_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY reports
ADD CONSTRAINT reports_pkey PRIMARY KEY (id);
--
-- Name: user_blocks user_blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -1875,6 +2037,55 @@ CREATE INDEX index_client_applications_on_user_id ON client_applications USING b
CREATE INDEX index_diary_entry_subscriptions_on_diary_entry_id ON diary_entry_subscriptions USING btree (diary_entry_id);
--
-- Name: index_issue_comments_on_issue_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issue_comments_on_issue_id ON issue_comments USING btree (issue_id);
--
-- Name: index_issue_comments_on_user_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issue_comments_on_user_id ON issue_comments USING btree (user_id);
--
-- Name: index_issues_on_assigned_role; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issues_on_assigned_role ON issues USING btree (assigned_role);
--
-- Name: index_issues_on_reportable_type_and_reportable_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issues_on_reportable_type_and_reportable_id ON issues USING btree (reportable_type, reportable_id);
--
-- Name: index_issues_on_reported_user_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issues_on_reported_user_id ON issues USING btree (reported_user_id);
--
-- Name: index_issues_on_status; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issues_on_status ON issues USING btree (status);
--
-- Name: index_issues_on_updated_by; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_issues_on_updated_by ON issues USING btree (updated_by);
--
-- Name: index_note_comments_on_body; Type: INDEX; Schema: public; Owner: -
--
@ -1910,6 +2121,20 @@ CREATE UNIQUE INDEX index_oauth_tokens_on_token ON oauth_tokens USING btree (tok
CREATE INDEX index_oauth_tokens_on_user_id ON oauth_tokens USING btree (user_id);
--
-- Name: index_reports_on_issue_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_reports_on_issue_id ON reports USING btree (issue_id);
--
-- Name: index_reports_on_user_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_reports_on_user_id ON reports USING btree (user_id);
--
-- Name: index_user_blocks_on_user_id; Type: INDEX; Schema: public; Owner: -
--
@ -2329,6 +2554,46 @@ ALTER TABLE ONLY gpx_files
ADD CONSTRAINT gpx_files_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
--
-- Name: issue_comments issue_comments_issue_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issue_comments
ADD CONSTRAINT issue_comments_issue_id_fkey FOREIGN KEY (issue_id) REFERENCES issues(id);
--
-- Name: issue_comments issue_comments_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issue_comments
ADD CONSTRAINT issue_comments_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
--
-- Name: issues issues_reported_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issues
ADD CONSTRAINT issues_reported_user_id_fkey FOREIGN KEY (reported_user_id) REFERENCES users(id);
--
-- Name: issues issues_resolved_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issues
ADD CONSTRAINT issues_resolved_by_fkey FOREIGN KEY (resolved_by) REFERENCES users(id);
--
-- Name: issues issues_updated_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY issues
ADD CONSTRAINT issues_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES users(id);
--
-- Name: messages messages_from_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@ -2441,6 +2706,22 @@ ALTER TABLE ONLY relations
ADD CONSTRAINT relations_redaction_id_fkey FOREIGN KEY (redaction_id) REFERENCES redactions(id);
--
-- Name: reports reports_issue_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY reports
ADD CONSTRAINT reports_issue_id_fkey FOREIGN KEY (issue_id) REFERENCES issues(id);
--
-- Name: reports reports_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY reports
ADD CONSTRAINT reports_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
--
-- Name: user_blocks user_blocks_moderator_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@ -2584,6 +2865,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20150111192335'),
('20150222101847'),
('20150818224516'),
('20160822153055'),
('20161002153425'),
('20161011010929'),
('20170222134109'),
@ -2632,5 +2914,3 @@ INSERT INTO "schema_migrations" (version) VALUES
('7'),
('8'),
('9');

View file

@ -0,0 +1,27 @@
require "test_helper"
class IssueCommentsControllerTest < ActionController::TestCase
def test_comment_by_normal_user
issue = create(:issue)
# Login as normal user
session[:user] = create(:user).id
post :create, :params => { :issue_id => issue.id }
assert_response :redirect
assert_redirected_to root_path
assert_equal 0, issue.comments.length
end
def test_comment
issue = create(:issue)
# Login as administrator
session[:user] = create(:administrator_user).id
post :create, :params => { :issue_id => issue.id, :issue_comment => { :body => "test comment" } }
assert_response :redirect
assert_redirected_to issue
assert_equal 1, issue.comments.length
end
end

View file

@ -0,0 +1,144 @@
require "test_helper"
class IssuesControllerTest < ActionController::TestCase
def test_index
# Access issues list without login
get :index
assert_response :redirect
assert_redirected_to login_path(:referer => issues_path)
# Access issues list as normal user
session[:user] = create(:user).id
get :index
assert_response :redirect
assert_redirected_to root_path
# Access issues list as administrator
session[:user] = create(:administrator_user).id
get :index
assert_response :success
# Access issues list as moderator
session[:user] = create(:moderator_user).id
get :index
assert_response :success
end
def test_show
target_user = create(:user)
issue = create(:issue, :reportable => target_user, :reported_user => target_user)
# Access issue without login
get :show, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to login_path(:referer => issue_path(issue))
# Access issue as normal user
session[:user] = create(:user).id
get :show, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to root_path
# Access issue as administrator
session[:user] = create(:administrator_user).id
get :show, :params => { :id => issue.id }
assert_response :success
# Access issue as moderator
session[:user] = create(:moderator_user).id
get :show, :params => { :id => issue.id }
assert_response :success
end
def test_resolve
target_user = create(:user)
issue = create(:issue, :reportable => target_user, :reported_user => target_user)
# Resolve issue without login
get :resolve, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to login_path(:referer => resolve_issue_path(issue))
# Resolve issue as normal user
session[:user] = create(:user).id
get :resolve, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to root_path
# Resolve issue as administrator
session[:user] = create(:administrator_user).id
get :resolve, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.resolved?
issue.reopen!
# Resolve issue as moderator
session[:user] = create(:moderator_user).id
get :resolve, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.resolved?
end
def test_ignore
target_user = create(:user)
issue = create(:issue, :reportable => target_user, :reported_user => target_user)
# Ignore issue without login
get :ignore, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to login_path(:referer => ignore_issue_path(issue))
# Ignore issue as normal user
session[:user] = create(:user).id
get :ignore, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to root_path
# Ignore issue as administrator
session[:user] = create(:administrator_user).id
get :ignore, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.ignored?
issue.reopen!
# Ignore issue as moderator
session[:user] = create(:moderator_user).id
get :ignore, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.ignored?
end
def test_reopen
target_user = create(:user)
issue = create(:issue, :reportable => target_user, :reported_user => target_user)
issue.resolve!
# Reopen issue without login
get :reopen, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to login_path(:referer => reopen_issue_path(issue))
# Reopen issue as normal user
session[:user] = create(:user).id
get :reopen, :params => { :id => issue.id }
assert_response :redirect
assert_redirected_to root_path
# Reopen issue as administrator
session[:user] = create(:administrator_user).id
get :reopen, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.open?
issue.resolve!
# Reopen issue as moderator
session[:user] = create(:moderator_user).id
get :reopen, :params => { :id => issue.id }
assert_response :redirect
assert_equal true, issue.reload.open?
end
end

View file

@ -0,0 +1,131 @@
require "test_helper"
class ReportsControllerTest < ActionController::TestCase
def test_new_report_without_login
target_user = create(:user)
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :redirect
assert_redirected_to login_path(:referer => new_report_path(:reportable_id => target_user.id, :reportable_type => "User"))
end
def test_new_report_after_login
target_user = create(:user)
session[:user] = create(:user).id
# Create an Issue and a report
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :success
assert_difference "Issue.count", 1 do
details = "Details of a report"
category = "other"
post :create,
:params => {
:report => {
:details => details,
:category => category,
:issue => { :reportable_id => target_user.id, :reportable_type => "User" }
}
}
end
assert_response :redirect
assert_redirected_to user_path(target_user.display_name)
end
def test_new_report_with_incomplete_details
# Test creation of a new issue and a new report
target_user = create(:user)
# Login
session[:user] = create(:user).id
# Create an Issue and a report
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :success
assert_difference "Issue.count", 1 do
details = "Details of a report"
category = "other"
post :create,
:params => {
:report => {
:details => details,
:category => category,
:issue => { :reportable_id => target_user.id, :reportable_type => "User" }
}
}
end
assert_response :redirect
assert_redirected_to user_path(target_user.display_name)
issue = Issue.last
assert_equal 1, issue.reports.count
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :success
# Report without details
assert_no_difference "Issue.count" do
category = "other"
post :create,
:params => {
:report => {
:category => category,
:issue => { :reportable_id => 1, :reportable_type => "User" }
}
}
end
assert_response :redirect
assert_equal 1, issue.reports.count
end
def test_new_report_with_complete_details
# Test creation of a new issue and a new report
target_user = create(:user)
# Login
session[:user] = create(:user).id
# Create an Issue and a report
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :success
assert_difference "Issue.count", 1 do
details = "Details of a report"
category = "other"
post :create,
:params => {
:report => {
:details => details,
:category => category,
:issue => { :reportable_id => target_user.id, :reportable_type => "User" }
}
}
end
assert_response :redirect
assert_redirected_to user_path(target_user.display_name)
issue = Issue.last
assert_equal 1, issue.reports.count
# Create a report for an existing Issue
get :new, :params => { :reportable_id => target_user.id, :reportable_type => "User" }
assert_response :success
assert_no_difference "Issue.count" do
details = "Details of another report under the same issue"
category = "other"
post :create,
:params => {
:report => {
:details => details,
:category => category,
:issue => { :reportable_id => target_user.id, :reportable_type => "User" }
}
}
end
assert_response :redirect
assert_equal 2, issue.reports.count
end
end

7
test/factories/issues.rb Normal file
View file

@ -0,0 +1,7 @@
FactoryBot.define do
factory :issue do
# Default to reporting users
association :reportable, :factory => :user
association :reported_user, :factory => :user
end
end

View file

@ -0,0 +1,8 @@
FactoryBot.define do
factory :report do
sequence(:details) { |n| "Report details #{n}" }
category "other"
issue
user
end
end

View file

@ -0,0 +1,7 @@
require "test_helper"
class IssueCommentTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

88
test/models/issue_test.rb Normal file
View file

@ -0,0 +1,88 @@
require "test_helper"
class IssueTest < ActiveSupport::TestCase
def test_assigned_role
issue = create(:issue)
assert issue.valid?
issue.assigned_role = "bogus"
assert_not issue.valid?
end
def test_reported_user
create(:language, :code => "en")
user = create(:user)
note = create(:note_comment, :author => create(:user)).note
anonymous_note = create(:note_comment, :author => nil).note
diary_entry = create(:diary_entry)
diary_comment = create(:diary_comment, :diary_entry => diary_entry)
issue = Issue.new(:reportable => user)
issue.save!
assert_equal issue.reported_user, user
issue = Issue.new(:reportable => note)
issue.save!
assert_equal issue.reported_user, note.author
issue = Issue.new(:reportable => anonymous_note)
issue.save!
assert_nil issue.reported_user
issue = Issue.new(:reportable => diary_entry)
issue.save!
assert_equal issue.reported_user, diary_entry.user
issue = Issue.new(:reportable => diary_comment)
issue.save!
assert_equal issue.reported_user, diary_comment.user
end
def test_default_assigned_role
create(:language, :code => "en")
user = create(:user)
note = create(:note_with_comments)
diary_entry = create(:diary_entry)
diary_comment = create(:diary_comment, :diary_entry => diary_entry)
issue = Issue.new(:reportable => user)
issue.save!
assert_equal "administrator", issue.assigned_role
issue = Issue.new(:reportable => note)
issue.save!
assert_equal "moderator", issue.assigned_role
issue = Issue.new(:reportable => diary_entry)
issue.save!
assert_equal "administrator", issue.assigned_role
issue = Issue.new(:reportable => diary_comment)
issue.save!
assert_equal "administrator", issue.assigned_role
end
def test_no_default_explicit_role
create(:language, :code => "en")
user = create(:user)
note = create(:note_with_comments)
diary_entry = create(:diary_entry)
diary_comment = create(:diary_comment, :diary_entry => diary_entry)
issue = Issue.new(:reportable => user, :assigned_role => "moderator")
issue.save!
assert_equal "moderator", issue.reload.assigned_role
issue = Issue.new(:reportable => note, :assigned_role => "administrator")
issue.save!
assert_equal "administrator", issue.reload.assigned_role
issue = Issue.new(:reportable => diary_entry, :assigned_role => "moderator")
issue.save!
assert_equal "moderator", issue.reload.assigned_role
issue = Issue.new(:reportable => diary_comment, :assigned_role => "moderator")
issue.save!
assert_equal "moderator", issue.reload.assigned_role
end
end

View file

@ -0,0 +1,35 @@
require "test_helper"
class ReportTest < ActiveSupport::TestCase
def test_issue_required
report = create(:report)
assert report.valid?
report.issue = nil
assert_not report.valid?
end
def test_user_required
report = create(:report)
assert report.valid?
report.user = nil
assert_not report.valid?
end
def test_details_required
report = create(:report)
assert report.valid?
report.details = ""
assert_not report.valid?
end
def test_category_required
report = create(:report)
assert report.valid?
report.category = ""
assert_not report.valid?
end
end

119
test/system/issues_test.rb Normal file
View file

@ -0,0 +1,119 @@
require "application_system_test_case"
class IssuesTest < ApplicationSystemTestCase
include IssuesHelper
def test_view_issues_not_logged_in
visit issues_path
assert page.has_content?(I18n.t("user.login.title"))
end
def test_view_issues_normal_user
sign_in_as(create(:user))
visit issues_path
assert page.has_content?(I18n.t("application.require_moderator_or_admin.not_a_moderator_or_admin"))
end
def test_view_no_issues
sign_in_as(create(:moderator_user))
visit issues_path
assert page.has_content?(I18n.t("issues.index.issues_not_found"))
end
def test_view_issues
sign_in_as(create(:moderator_user))
issues = create_list(:issue, 3, :assigned_role => "moderator")
visit issues_path
assert page.has_content?(issues.first.reported_user.display_name)
end
def test_view_issues_with_no_reported_user
sign_in_as(create(:moderator_user))
anonymous_note = create(:note_with_comments)
issue = create(:issue, :reportable => anonymous_note)
visit issues_path
assert page.has_content?(reportable_title(anonymous_note))
visit issue_path(issue)
assert page.has_content?(reportable_title(anonymous_note))
end
def test_search_issues_by_user
good_user = create(:user)
bad_user = create(:user)
create(:issue, :reportable => bad_user, :reported_user => bad_user, :assigned_role => "administrator")
sign_in_as(create(:administrator_user))
# No issues against the user
visit issues_path
fill_in "search_by_user", :with => good_user.display_name
click_on "Search"
assert_not page.has_content?(I18n.t("issues.index.user_not_found"))
assert page.has_content?(I18n.t("issues.index.issues_not_found"))
# User doesn't exist
visit issues_path
fill_in "search_by_user", :with => "Nonexistant User"
click_on "Search"
assert page.has_content?(I18n.t("issues.index.user_not_found"))
assert page.has_content?(I18n.t("issues.index.issues_not_found"))
# Find Issue against bad_user
visit issues_path
fill_in "search_by_user", :with => bad_user.display_name
click_on "Search"
assert_not page.has_content?(I18n.t("issues.index.user_not_found"))
assert_not page.has_content?(I18n.t("issues.index.issues_not_found"))
end
def test_commenting
issue = create(:issue)
sign_in_as(create(:moderator_user))
visit issue_path(issue)
fill_in :issue_comment_body, :with => "test comment"
click_on "Submit"
assert page.has_content?(I18n.t("issue_comments.create.comment_created"))
assert page.has_content?("test comment")
issue.reload
assert_equal issue.comments.first.body, "test comment"
end
def test_reassign_issue
issue = create(:issue)
assert_equal "administrator", issue.assigned_role
sign_in_as(create(:administrator_user))
visit issue_path(issue)
fill_in :issue_comment_body, :with => "reassigning to moderators"
check :reassign
click_on "Submit"
issue.reload
assert_equal "moderator", issue.assigned_role
end
def test_issue_index_with_multiple_roles
user1 = create(:user)
user2 = create(:user)
issue1 = create(:issue, :reportable => user1, :assigned_role => "administrator")
issue2 = create(:issue, :reportable => user2, :assigned_role => "moderator")
user = create(:administrator_user)
create(:user_role, :user => user, :role => "moderator")
sign_in_as(user)
visit issues_path
assert page.has_link?(I18n.t("issues.index.reports_count", :count => issue1.reports_count), :href => issue_path(issue1))
assert page.has_link?(I18n.t("issues.index.reports_count", :count => issue2.reports_count), :href => issue_path(issue2))
end
end

View file

@ -0,0 +1,36 @@
require "application_system_test_case"
class ReportDiaryCommentTest < ApplicationSystemTestCase
def setup
create(:language, :code => "en")
@diary_entry = create(:diary_entry)
@comment = create(:diary_comment, :diary_entry => @diary_entry)
end
def test_no_link_when_not_logged_in
visit diary_entry_path(@diary_entry.user.display_name, @diary_entry)
assert page.has_content?(@comment.body)
assert_not page.has_content?(I18n.t("diary_entry.diary_comment.report"))
end
def test_it_works
sign_in_as(create(:user))
visit diary_entry_path(@diary_entry.user.display_name, @diary_entry)
assert page.has_content? @diary_entry.title
click_on I18n.t("diary_entry.diary_comment.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.diary_comment.spam")
fill_in "report_details", :with => "This comment is spam"
assert_difference "Issue.count", 1 do
click_on "Create Report"
end
assert page.has_content? "Your report has been registered sucessfully"
assert_equal @comment, Issue.last.reportable
end
end

View file

@ -0,0 +1,64 @@
require "application_system_test_case"
class ReportDiaryEntryTest < ApplicationSystemTestCase
def setup
create(:language, :code => "en")
@diary_entry = create(:diary_entry)
end
def test_no_link_when_not_logged_in
visit diary_entry_path(@diary_entry.user.display_name, @diary_entry)
assert page.has_content?(@diary_entry.title)
assert_not page.has_content?(I18n.t("diary_entry.diary_entry.report"))
end
def test_it_works
sign_in_as(create(:user))
visit diary_entry_path(@diary_entry.user.display_name, @diary_entry)
assert page.has_content? @diary_entry.title
click_on I18n.t("diary_entry.diary_entry.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.diary_entry.spam")
fill_in "report_details", :with => "This is advertising"
assert_difference "Issue.count", 1 do
click_on "Create Report"
end
assert page.has_content? "Your report has been registered sucessfully"
assert_equal @diary_entry, Issue.last.reportable
end
def test_it_reopens_issue
issue = create(:issue, :reportable => @diary_entry)
issue.resolve!
sign_in_as(create(:user))
visit diary_entry_path(@diary_entry.user.display_name, @diary_entry)
assert page.has_content? @diary_entry.title
click_on I18n.t("diary_entry.diary_entry.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.diary_entry.spam")
fill_in "report_details", :with => "This is advertising"
assert_no_difference "Issue.count" do
click_on "Create Report"
end
issue.reload
assert_not issue.resolved?
assert issue.open?
end
def test_missing_report_params
sign_in_as(create(:user))
visit new_report_path
assert page.has_content? I18n.t("reports.new.missing_params")
end
end

View file

@ -0,0 +1,51 @@
require "application_system_test_case"
class ReportNoteTest < ApplicationSystemTestCase
def test_no_link_when_not_logged_in
note = create(:note_with_comments)
visit browse_note_path(note)
assert page.has_content?(note.comments.first.body)
assert_not page.has_content?(I18n.t("browse.note.report"))
end
def test_can_report_anonymous_notes
note = create(:note_with_comments)
sign_in_as(create(:user))
visit browse_note_path(note)
click_on I18n.t("browse.note.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.note.spam")
fill_in "report_details", :with => "This is spam"
assert_difference "Issue.count", 1 do
click_on "Create Report"
end
assert page.has_content? "Your report has been registered sucessfully"
assert_equal note, Issue.last.reportable
end
def test_can_report_notes_with_author
note = create(:note_comment, :author => create(:user)).note
sign_in_as(create(:user))
visit browse_note_path(note)
click_on I18n.t("browse.note.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.note.spam")
fill_in "report_details", :with => "This is spam"
assert_difference "Issue.count", 1 do
click_on "Create Report"
end
assert page.has_content? "Your report has been registered sucessfully"
assert_equal note, Issue.last.reportable
end
end

View file

@ -0,0 +1,31 @@
require "application_system_test_case"
class ReportUserTest < ApplicationSystemTestCase
def test_no_link_when_not_logged_in
note = create(:note_with_comments)
visit browse_note_path(note)
assert page.has_content?(note.comments.first.body)
assert_not page.has_content?(I18n.t("user.view.report"))
end
def test_can_report_user
user = create(:user)
sign_in_as(create(:user))
visit user_path(user.display_name)
click_on I18n.t("user.view.report")
assert page.has_content? "Report"
assert page.has_content? I18n.t("reports.new.disclaimer.intro")
choose I18n.t("reports.new.categories.user.vandal")
fill_in "report_details", :with => "This user is a vandal"
assert_difference "Issue.count", 1 do
click_on "Create Report"
end
assert page.has_content? "Your report has been registered sucessfully"
assert_equal user, Issue.last.reportable
end
end