Skip to content

feat: Allow users to report comments #3016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions app/assets/javascripts/request_for_comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,10 @@ $(document).on('turbo-migration:load', function () {
</div> \
<div class="comment-content">' + commentText + '</div> \
<textarea class="comment-editor">' + commentText + '</textarea> \
<div class="comment-actions' + (comment.editable ? '' : ' d-none') + '"> \
<button class="action-edit btn btn-sm btn-warning">' + I18n.t('shared.edit') + '</button> \
<button class="action-delete btn btn-sm btn-danger">' + I18n.t('shared.destroy') + '</button> \
<div class="comment-actions' + (comment.editable || comment.reportable ? '' : ' d-none') + '"> \
<button class="action-edit btn btn-sm btn-warning' + (comment.editable ? '' : ' d-none') + '">' + I18n.t('shared.edit') + '</button> \
<button class="action-delete btn btn-sm btn-danger' + (comment.editable ? '' : ' d-none') + '">' + I18n.t('shared.destroy') + '</button> \
<button class="action-report btn btn-light btn-sm' + (comment.reportable ? '' : ' d-none') + '">' + I18n.t('shared.report') + '</button> \
</div> \
</div>';
});
Expand Down Expand Up @@ -166,6 +167,17 @@ $(document).on('turbo-migration:load', function () {
})
}

function reportComment(commentId, callback) {
const jqxhr = $.ajax({
type: 'POST',
url: Routes.report_comment_path(commentId)
});
jqxhr.done(function () {
callback();
});
jqxhr.fail(ajaxError);
}

function deleteComment(commentId, editor, file_id, callback) {
const jqxhr = $.ajax({
type: 'DELETE',
Expand Down Expand Up @@ -313,6 +325,17 @@ $(document).on('turbo-migration:load', function () {
const container = otherComments.find('.container');
container.html(htmlContent);

const reportButtons = container.find('.action-report');
reportButtons.on('click', function (event) {
const button = $(event.target);
const parent = $(button).parent().parent();
const commentId = parent.data('comment-id');

reportComment(commentId, function () {
parent.html('<div class="comment-reported">' + I18n.t('comments.reported') + '</div>');
});
});

const deleteButtons = container.find('.action-delete');
deleteButtons.on('click', function (event) {
const button = $(event.target);
Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/request-for-comments.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ html[data-bs-theme="light"] {
button {
margin-right: 5px;
}

.action-report {
margin-left: auto;
}
}
}
}
Expand Down
17 changes: 10 additions & 7 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

class CommentsController < ApplicationController
before_action :set_comment, only: %i[show update destroy]
before_action :set_comment, only: %i[show update destroy report]

def authorize!
authorize(@comment || @comments)
Expand All @@ -15,12 +15,6 @@ def index
submission = Submission.find_by(id: file.context_id)
if submission
@comments = Comment.where(file_id: params[:file_id])
@comments.map do |comment|
comment.username = comment.user.displayname
comment.date = comment.created_at.strftime('%d.%m.%Y %k:%M')
comment.updated = (comment.created_at != comment.updated_at)
comment.editable = policy(comment).edit?
end
else
@comments = []
end
Expand Down Expand Up @@ -67,6 +61,15 @@ def destroy
head :no_content
end

# POST /comments/1/report.json
def report
authorize!

UserContentReportMailer.with(reported_content: @comment).report_content.deliver_later

head :no_content
end

private

# Use callbacks to share common setup or constraints between actions.
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/request_for_comments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ def create
def report
authorize!

ReportMailer.with(reported_content: @request_for_comment).report_content.deliver_later
UserContentReportMailer.with(reported_content: @request_for_comment).report_content.deliver_later

redirect_to @request_for_comment, notice: t('.report.reported'), status: :see_other
redirect_to @request_for_comment, notice: t('.reported'), status: :see_other
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Through unrelated changes in the locals, i18_task asked me to adjust the path. The message localization is unchanged.

end

private
Expand Down
18 changes: 0 additions & 18 deletions app/mailers/report_mailer.rb

This file was deleted.

11 changes: 11 additions & 0 deletions app/mailers/user_content_report_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class UserContentReportMailer < ApplicationMailer
default to: CodeOcean::Config.new(:code_ocean).read.dig(:content_moderation, :report_emails)

def report_content
@user_content_report = UserContentReport.new(reported_content: params.fetch(:reported_content))

mail(subject: I18n.t('user_content_report_mailer.report_content.subject', human_model_name: @user_content_report.human_model_name))
end
end
2 changes: 0 additions & 2 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ class Comment < ApplicationRecord
include Creation
include ActionCableHelper

attr_accessor :username, :date, :updated, :editable

belongs_to :file, class_name: 'CodeOcean::File'
has_one :submission, through: :file, source: :context, source_type: 'Submission'
has_one :request_for_comment, through: :submission
Expand Down
51 changes: 51 additions & 0 deletions app/models/user_content_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

class UserContentReport
def initialize(reported_content:)
unless [Comment, RequestForComment].include?(reported_content.class)
raise("#{reported_content.model_name} is not configured for spam reports.")
end

@reported_content = reported_content
end

# NOTE: This implementation assumes the course URL is static and does not vary per user.
# This is currently valid for a majority of use cases. However, in dynamic scenarios (such as
# content trees in openHPI used in conjunction with A/B/n testing) this assumption may no
# longer hold true.
def course_url = lti_parameters['launch_presentation_return_url']

def human_model_name = reported_content.model_name.human

def reported_message
case reported_content
when RequestForComment
reported_content.question
when Comment
reported_content.text
end
end

def related_request_for_comment
case reported_content
when RequestForComment
reported_content
when Comment
RequestForComment.find_by!(file: reported_content.file)
end
end

private

attr_reader :reported_content

def lti_parameters = LtiParameter.find_by(study_group:, exercise:)&.lti_parameters || {}

def study_group
reported_content.submission.study_group
end

def exercise
related_request_for_comment.exercise
end
end
6 changes: 6 additions & 0 deletions app/policies/comment_policy.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class CommentPolicy < ApplicationPolicy
REPORT_RECEIVER_CONFIGURED = CodeOcean::Config.new(:code_ocean).read.dig(:content_moderation, :report_emails).present?

def create?
everyone
end
Expand All @@ -16,4 +18,8 @@ def show?
def index?
everyone
end

def report?
REPORT_RECEIVER_CONFIGURED && everyone && !author?
end
end
7 changes: 6 additions & 1 deletion app/views/comments/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

json.array!(@comments) do |comment|
json.extract! comment, :id, :user_id, :file_id, :row, :column, :text, :username, :date, :updated, :editable
json.extract! comment, :id, :user_id, :file_id, :row, :column, :text
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please allow me this small refactor with this PR. I think this view-related field should be set in the template and not in the Controller and Model.

json.username comment.user.displayname
json.date comment.created_at.strftime('%d.%m.%Y %k:%M')
json.updated(comment.created_at != comment.updated_at)
json.editable policy(comment).edit?
json.reportable policy(comment).report?
json.url comment_url(comment, format: :json)
end
6 changes: 0 additions & 6 deletions app/views/report_mailer/report_content.html.slim

This file was deleted.

12 changes: 0 additions & 12 deletions app/views/report_mailer/report_content.text.slim

This file was deleted.

2 changes: 1 addition & 1 deletion app/views/request_for_comments/_report.html.slim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/# locals: (request_for_comment:)

- if policy(request_for_comment).report?
= button_to(t('.report'), report_request_for_comment_path(request_for_comment),
= button_to(t('shared.report'), report_request_for_comment_path(request_for_comment),
data: {confirm: t('.confirm')},
class: 'btn btn-light btn-sm',
form: {class: 'd-inline float-end'})
6 changes: 6 additions & 0 deletions app/views/user_content_report_mailer/report_content.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
h3 = t('.prolog')
blockquote style="white-space: pre-wrap;" = @user_content_report.reported_message
p = t('.take_action')
p = link_to(request_for_comment_url(@user_content_report.related_request_for_comment), request_for_comment_url(@user_content_report.related_request_for_comment))
- if @user_content_report.course_url.present?
p = link_to(t('.authentication'), @user_content_report.course_url)
12 changes: 12 additions & 0 deletions app/views/user_content_report_mailer/report_content.text.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
== t('.prolog')
== "\n\n"
== @user_content_report.reported_message.lines.map { "> #{it}" }.join
== "\n\n"
== t('.take_action')
== "\n\n"
== request_for_comment_url(@user_content_report.related_request_for_comment)
== "\n\n"
- if @user_content_report.course_url.present?
== t('.authentication')
== "\n\n"
== @user_content_report.course_url
1 change: 1 addition & 0 deletions config/locales/de/comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ de:
other: Kommentare
comments:
deleted: Gelöscht
reported: Kommentar ist gemeldet.
save_update: Speichern
1 change: 1 addition & 0 deletions config/locales/de/meta/shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ de:
object_destroyed: "%{model} wurde erfolgreich gelöscht."
object_updated: "%{model} wurde erfolgreich bearbeitet."
out_of: von
report: Melden
reset_filters: Filter zurücksetzen
resources: Ressourcen
show: Anzeigen
Expand Down
1 change: 0 additions & 1 deletion config/locales/de/request_for_comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ de:
passed: Erfolgreich
report:
confirm: Möchten Sie diesen Inhalt melden?
report: Melden
reported: Vielen Dank, dass Sie uns auf dieses Problem aufmerksam gemacht haben. Wir werden uns in Kürze darum kümmern.
runtime_output: Programmausgabe
send_thank_you_note: Senden
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
de:
report_mailer:
user_content_report_mailer:
report_content:
authentication: Kurs-URL für die LTI-Authentifizierung.
prolog: 'Die folgenden Inhalte wurden als unangemessen gemeldet:'
subject: 'Spam Report: Ein %{content_name} in CodeOcean wurde als unangemessen markiert.'
subject: 'Spam Report: Ein %{human_model_name} in CodeOcean wurde als unangemessen markiert.'
take_action: Bitte ergreifen Sie gegebenenfalls Maßnahmen.
1 change: 1 addition & 0 deletions config/locales/en/comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ en:
other: Comments
comments:
deleted: Deleted
reported: Comment is reported.
save_update: Save
1 change: 1 addition & 0 deletions config/locales/en/meta/shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ en:
object_destroyed: "%{model} has successfully been deleted."
object_updated: "%{model} has successfully been updated."
out_of: out of
report: Report
reset_filters: Reset filters
resources: Resources
show: Show
Expand Down
1 change: 0 additions & 1 deletion config/locales/en/request_for_comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ en:
passed: Passed
report:
confirm: Do you want to report this content?
report: Report
reported: Thank you for letting us know about this issue. We will look into the matter shortly.
runtime_output: Runtime Output
send_thank_you_note: Send
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
en:
report_mailer:
user_content_report_mailer:
report_content:
authentication: Course URL for LTI authentication.
prolog: 'The following content has been reported as inappropriate:'
subject: 'Spam Report: A %{content_name} on CodeOcean has been marked as inappropriate.'
subject: 'Spam Report: A %{human_model_name} on CodeOcean has been marked as inappropriate.'
take_action: Please take action if required.
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
post :report
end
end
resources :comments, defaults: {format: :json}
resources :comments, defaults: {format: :json} do
member do
post :report
end
end
get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#my_comment_requests'
get '/my_rfc_activity', as: 'my_rfc_activity', to: 'request_for_comments#rfcs_with_my_comments'
get '/exercises/:exercise_id/request_for_comments', as: 'exercise_request_for_comments', to: 'request_for_comments#rfcs_for_exercise'
Expand Down
4 changes: 2 additions & 2 deletions spec/mailers/previews/report_mailer_preview.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# frozen_string_literal: true

class ReportMailerPreview < ActionMailer::Preview
class UserContentReportMailerPreview < ActionMailer::Preview
def report
rfc = FactoryBot.build_stubbed(:rfc)

ReportMailer.with(reported_content: rfc).report_content
UserContentReportMailer.with(reported_content: rfc).report_content
end
end
Loading