Skip to content

Commit

Permalink
FEATURE: Implement Flags on Post Voting Comments, Comment edit and de…
Browse files Browse the repository at this point in the history
…lete for moderators, only_allow_post_voting_in_this_category (#180)

* FEATURE: Implement Flags on Post Voting Comments, Comment edit and delete for moderators, only_allow_post_voting_in_this_category

* linter fix

* linter fix

* FEATURE: Implement Flags on Post Voting Comments

* linting issues

* added tests for only_post_voting_in_this_category

* remove debugging

* lint fix

* fixed failing system test

* fixed failing tests

* added proper title with link to ReviewablePostVotingComments

* fixed linter problem

* implemented requested changes

* implemented additional changes
  • Loading branch information
jdmartinez1062 authored Dec 21, 2023
1 parent 776b743 commit 44984bc
Show file tree
Hide file tree
Showing 31 changed files with 1,295 additions and 61 deletions.
34 changes: 31 additions & 3 deletions app/controllers/post_voting/comments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ def create
post_id: @post.id,
raw: comments_params[:raw],
)

if comment.errors.present?
render_json_error(comment.errors.full_messages, status: 403)
else
DiscourseEvent.trigger(:post_voting_comment_created, comment, comment.user)
render_serialized(comment, PostVotingCommentSerializer, root: false)
end
end
Expand All @@ -45,8 +45,8 @@ def update
params.require(:raw)

comment = find_comment(params[:comment_id])
@guardian.ensure_can_see!(comment.post)

@guardian.ensure_can_see!(comment.post)
raise Discourse::InvalidAccess if !@guardian.can_edit_comment?(comment)

if comment.update(raw: params[:raw])
Expand All @@ -58,7 +58,7 @@ def update
comment_cooked: comment.cooked,
)
end

DiscourseEvent.trigger(:post_voting_comment_edited, comment, comment.user)
render_serialized(comment, PostVotingCommentSerializer, root: false)
else
render_json_error(comment.errors.full_messages, status: 403)
Expand All @@ -85,6 +85,34 @@ def destroy
render json: success_json
end

def flag
RateLimiter.new(current_user, "flag_post_voting_comment", 4, 1.minutes).performed!
permitted_params =
params.permit(%i[comment_id flag_type_id message is_warning take_action queue_for_review])

comment = PostVotingComment.find(permitted_params[:comment_id])

flag_type_id = permitted_params[:flag_type_id].to_i

if !ReviewableScore.types.values.include?(flag_type_id)
raise Discourse::InvalidParameters.new(:flag_type_id)
end

result =
PostVoting::CommentReviewQueue.new.flag_comment(
comment,
guardian,
flag_type_id,
permitted_params,
)

if result[:success]
render json: success_json
else
render_json_error(result[:errors])
end
end

private

def comments_params
Expand Down
5 changes: 5 additions & 0 deletions app/models/post_voting_comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def self.cook(raw)
PrettyText.cook(raw, features_override: MARKDOWN_FEATURES, markdown_it_rules: MARKDOWN_IT_RULES)
end

def full_url
post = Post.find(post_id)
"#{Discourse.base_url}#{post.url}"
end

private

def cook_raw
Expand Down
151 changes: 151 additions & 0 deletions app/models/reviewable_post_voting_comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# frozen_string_literal: true

class ReviewablePostVotingComment < Reviewable
def serializer
ReviewablePostVotingCommentSerializer
end

def self.action_aliases
{
agree_and_keep_hidden: :agree_and_delete,
agree_and_silence: :agree_and_delete,
agree_and_suspend: :agree_and_delete,
delete_and_agree: :agree_and_delete,
}
end

def flagged_by_user_ids
@flagged_by_user_ids ||= reviewable_scores.map(&:user_id)
end

def post
nil
end

def comment
@comment ||= (target || PostVotingComment.with_deleted.find_by(id: target_id))
end

def comment_creator
@comment_creator ||= User.find_by(id: comment.user_id)
end

def build_actions(actions, guardian, args)
return unless pending?
return if comment.blank?

agree =
actions.add_bundle("#{id}-agree", icon: "thumbs-up", label: "reviewables.actions.agree.title")

if comment.deleted_at?
build_action(actions, :agree_and_restore, icon: "far-eye", bundle: agree)
build_action(actions, :agree_and_keep_deleted, icon: "thumbs-up", bundle: agree)
build_action(actions, :disagree_and_restore, icon: "thumbs-down")
else
build_action(actions, :agree_and_delete, icon: "far-eye-slash", bundle: agree)
build_action(actions, :agree_and_keep_comment, icon: "thumbs-up", bundle: agree)
build_action(actions, :disagree, icon: "thumbs-down")
end

if guardian.can_suspend?(comment_creator)
build_action(
actions,
:agree_and_suspend,
icon: "ban",
bundle: agree,
client_action: "suspend",
)
build_action(
actions,
:agree_and_silence,
icon: "microphone-slash",
bundle: agree,
client_action: "silence",
)
end

ignore_bundle = actions.add_bundle("#{id}-ignore", label: "reviewables.actions.ignore.title")

build_action(actions, :ignore, icon: "external-link-alt", bundle: ignore_bundle)

unless comment.deleted_at?
build_action(actions, :delete_and_agree, icon: "far-trash-alt", bundle: ignore_bundle)
end
end

def perform_agree_and_keep_comment(performed_by, args)
agree
end

def perform_agree_and_restore(performed_by, args)
agree { comment.recover! }
end

def perform_agree_and_delete(performed_by, args)
agree { comment.trash!(performed_by) }
end

def perform_disagree_and_restore(performed_by, args)
disagree { comment.recover! }
end

def perform_disagree(performed_by, args)
disagree
end

def perform_ignore(performed_by, args)
ignore
end

def perform_delete_and_ignore(performed_by, args)
ignore { comment.trash!(performed_by) }
end

private

def agree
yield if block_given?
create_result(:success, :approved) do |result|
result.update_flag_stats = { status: :agreed, user_ids: flagged_by_user_ids }
result.recalculate_score = true
end
end

def disagree
yield if block_given?

UserSilencer.unsilence(comment_creator)

create_result(:success, :rejected) do |result|
result.update_flag_stats = { status: :disagreed, user_ids: flagged_by_user_ids }
result.recalculate_score = true
end
end

def ignore
yield if block_given?
create_result(:success, :ignored) do |result|
result.update_flag_stats = { status: :ignored, user_ids: flagged_by_user_ids }
end
end

def build_action(
actions,
id,
icon:,
button_class: nil,
bundle: nil,
client_action: nil,
confirm: false
)
actions.add(id, bundle: bundle) do |action|
prefix = "reviewables.actions.#{id}"
action.icon = icon
action.button_class = button_class
action.label = "post_voting.comment.#{prefix}.title"
action.description = "post_voting.comment.#{prefix}.description"
action.client_action = client_action
action.confirm_message = "#{prefix}.confirm" if confirm
end
end
end
18 changes: 17 additions & 1 deletion app/serializers/post_voting_comment_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class PostVotingCommentSerializer < ApplicationSerializer
:raw,
:cooked,
:post_voting_vote_count,
:user_voted
:user_voted,
:available_flags,
:reviewable_id

attr_accessor :comments_user_voted

Expand All @@ -29,6 +31,20 @@ def user_voted
end
end

def reviewable_id
return @reviewable_id if defined?(@reviewable_id)
return @reviewable_id = nil unless @options && @options[:reviewable_ids]

@reviewable_id = @options[:reviewable_ids][object.id]
end

def available_flags
return [] if !scope.can_flag_post_voting_comment?(object)
return [] if reviewable_id.present? && user_flag_status == ReviewableScore.statuses[:pending]

PostActionType.flag_types.map { |sym, id| sym }
end

def post_voting_vote_count
object.qa_vote_count
end
Expand Down
21 changes: 21 additions & 0 deletions app/serializers/reviewable_post_voting_comments_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require_dependency "reviewable_serializer"

class ReviewablePostVotingCommentSerializer < ReviewableSerializer
target_attributes :cooked, :raw, :comment_cooked, :post_id
payload_attributes :comment_cooked, :transcript_topic_id, :cooked, :raw, :created_by
attributes :target_id, :comment_cooked

def created_from_flag?
true
end

def target_id
object.target&.id
end

def comment_cooked
object.payload["comment_cooked"]
end
end
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
<span class="post-voting-comment-actions">
<DButton
@display="link"
@class="post-voting-comment-actions-edit-link"
@action={{@updateComment}}
@icon="pencil-alt"
/>
<DButton
@display="link"
@class="post-voting-comment-actions-delete-link"
@action={{this.deleteConfirm}}
@icon="far-trash-alt"
/>
</span>
{{#if this.canEdit}}
<span class="post-voting-comment-actions">
<DButton
@display="link"
class="post-voting-comment-actions-edit-link"
@action={{@updateComment}}
@icon="pencil-alt"
/>
<DButton
@display="link"
class="post-voting-comment-actions-delete-link"
@action={{this.deleteConfirm}}
@icon="far-trash-alt"
/>

{{#if this.canFlag}}
<DButton
@display="link"
class="post-voting-comment-actions-flag-link"
@action={{this.showFlag}}
@icon="flag"
/>
{{/if}}
</span>
{{/if}}
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import FlagModal from "discourse/components/modal/flag";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import I18n from "I18n";
import PostVotingFlag from "../lib/post-voting-flag";

export default class PostVotingCommentActions extends Component {
@service dialog;
@service modal;
@service currentUser;
@service siteSettings;
@service site;

comment = this.args.comment;

hasPermission() {
return (
this.comment.user_id === this.currentUser.id ||
this.currentUser.admin ||
this.currentUser.moderator
);
}

get canEdit() {
return this.currentUser && this.hasPermission && !this.args.disabled;
}

get canFlag() {
return (
this.currentUser &&
(this.hasPermission ||
this.currentUser.trust_level >=
this.siteSettings.min_trust_to_flag_posts_voting_comments) &&
!this.args.disabled
);
}

@action
deleteConfirm() {
Expand All @@ -26,4 +56,17 @@ export default class PostVotingCommentActions extends Component {
},
});
}

@action
showFlag() {
this.comment.availableFlags = this.comment.available_flags;
this.modal.show(FlagModal, {
model: {
flagTarget: new PostVotingFlag(),
flagModel: this.comment,
setHidden: () => (this.comment.hidden = true),
site: this.site,
},
});
}
}
Loading

0 comments on commit 44984bc

Please sign in to comment.