mirror of
https://github.com/discourse/discourse.git
synced 2026-03-03 23:54:20 +08:00
Following PR #36711 which enabled the refreshed review UI for all users, this commit removes the old feature flag infrastructure and cleans up legacy code that is no longer needed. Backend Changes: - Removed ReviewableActionLog model and its spec entirely - Removed CalculateFinalStatusFromLogs service and spec - Removed site settings: force_old_reviewable_ui, reviewable_old_moderator_actions - Simplified ReviewableActionBuilder — stripped out legacy action-building methods (build_user_actions_bundle, build_post_actions_bundle, build_new_separated_actions) - Cleaned up reviewable models (ReviewableFlaggedPost, ReviewablePost, ReviewableQueuedPost, ReviewableUser, Chat::ReviewableMessage, ReviewablePostVotingComment) — removed unused/legacy action definitions - Removed legacy specs for action builder, action logs, flagged post actions, post actions, user actions, and status-from-logs - Updated system tests and page objects to reflect the new UI structure Frontend Changes: - Deleted legacy components: reviewable-item.gjs, reviewable-user.gjs, review-index-legacy.gjs - Renamed reviewable-refresh/ → reviewable/ — moved all sub-components (created-by, flagged-post, item, post, queued-post, topic-link, user, etc.) out of the refresh directory into the canonical reviewable/ namespace - Simplified reviewable/item.gjs — removed feature flag conditionals and legacy code paths - Cleaned up review/index.gjs and review/show.gjs templates — removed branching between old/new UI - Updated plugin components (chat, AI, post-voting) to import from reviewable/ instead of reviewable-refresh/ - Removed acceptance tests (review-test.js) replaced by system tests - Renamed and updated integration tests from reviewable-refresh/* to reviewable/* Plan for next PRs: - Move plugins from Pages::RefreshedReview to Pages::Review - Move plugins to import from `reviewable/` and not `refreshed-reviewable/` - Move reviewable-user.js import in plugin to use `reviewable/user.js` - Remove unused settings like `reviewable_old_moderator_actions` from plugins - Delete `Pages::RefreshedReview` - Delete `reviewable-refresh/` directory - Delete `reviewable-user.js` component - Delete `reviewable_old_moderator_actions` site setting Plugins PRs: - https://github.com/discourse/discourse-akismet/pull/203 - https://github.com/discourse/discourse-antivirus/pull/98 - https://github.com/discourse/discourse-category-experts/pull/223
406 lines
14 KiB
Ruby
406 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "discourse_ip_info"
|
|
|
|
describe "Viewing reviewable item", type: :system do
|
|
fab!(:admin)
|
|
fab!(:moderator)
|
|
fab!(:group)
|
|
fab!(:reviewable_flagged_post) do
|
|
Fabricate(
|
|
:reviewable_flagged_post,
|
|
target_created_by: Fabricate(:user, email: "flagged@example.com"),
|
|
)
|
|
end
|
|
|
|
let(:review_page) { PageObjects::Pages::Review.new }
|
|
let(:review_note_form) { PageObjects::Components::ReviewNoteForm.new }
|
|
|
|
before do
|
|
group.add(admin)
|
|
sign_in(admin)
|
|
end
|
|
|
|
describe "when the reviewable item is a flagged post" do
|
|
it "shows the new reviewable UI" do
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
|
|
expect(page).to have_selector(".review-container")
|
|
end
|
|
|
|
it "shows the reviewable item with badges stating the flag reason and count" do
|
|
_spam_reviewable_score =
|
|
Fabricate(
|
|
:reviewable_score,
|
|
reviewable: reviewable_flagged_post,
|
|
reviewable_score_type: ReviewableScore.types[:spam],
|
|
)
|
|
|
|
_off_topic_reviewable_score =
|
|
Fabricate(
|
|
:reviewable_score,
|
|
reviewable: reviewable_flagged_post,
|
|
reviewable_score_type: ReviewableScore.types[:off_topic],
|
|
)
|
|
|
|
_illegal_reviewable_score =
|
|
Fabricate(
|
|
:reviewable_score,
|
|
reviewable: reviewable_flagged_post,
|
|
reviewable_score_type: ReviewableScore.types[:illegal],
|
|
)
|
|
|
|
_inappropriate_reviewable_score =
|
|
Fabricate(
|
|
:reviewable_score,
|
|
reviewable: reviewable_flagged_post,
|
|
reviewable_score_type: ReviewableScore.types[:inappropriate],
|
|
)
|
|
|
|
_needs_approval_reviewable_score =
|
|
Fabricate(
|
|
:reviewable_score,
|
|
reviewable: reviewable_flagged_post,
|
|
reviewable_score_type: ReviewableScore.types[:needs_approval],
|
|
)
|
|
|
|
flag_reason_component =
|
|
review_page.visit_reviewable(reviewable_flagged_post).flag_reason_component
|
|
|
|
expect(flag_reason_component).to have_spam_flag_reason(reviewable_flagged_post, count: 1)
|
|
expect(flag_reason_component).to have_off_topic_flag_reason(reviewable_flagged_post, count: 1)
|
|
expect(flag_reason_component).to have_illegal_flag_reason(reviewable_flagged_post, count: 1)
|
|
|
|
expect(flag_reason_component).to have_inappropriate_flag_reason(
|
|
reviewable_flagged_post,
|
|
count: 2,
|
|
)
|
|
|
|
expect(flag_reason_component).to have_needs_approval_flag_reason(
|
|
reviewable_flagged_post,
|
|
count: 1,
|
|
)
|
|
end
|
|
|
|
it "shows the topic status, title link, category badge and tags of the topic associated with the reviewable item correctly" do
|
|
post = reviewable_flagged_post.post
|
|
topic = reviewable_flagged_post.topic
|
|
category = Fabricate(:category)
|
|
topic.change_category_to_id(category.id)
|
|
tag_1 = Fabricate(:tag)
|
|
tag_2 = Fabricate(:tag)
|
|
topic.tags = [tag_1, tag_2]
|
|
topic.closed = true
|
|
topic.save!
|
|
|
|
topic_link_component =
|
|
review_page.visit_reviewable(reviewable_flagged_post).topic_link_component
|
|
|
|
expect(topic_link_component).to have_closed_topic_status
|
|
|
|
expect(topic_link_component).to have_topic_link(
|
|
topic_title: topic.title,
|
|
post_url: post.full_url,
|
|
)
|
|
|
|
expect(topic_link_component).to have_category_badge(category.name)
|
|
expect(topic_link_component).to have_tag_link(tag_name: tag_1.name, tag_url: tag_1.url)
|
|
expect(topic_link_component).to have_tag_link(tag_name: tag_2.name, tag_url: tag_2.url)
|
|
|
|
# TODO: Add test for watched words highlighting
|
|
end
|
|
|
|
it "allows to add notes and persists them when toggle tabs" do
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_timeline_tab
|
|
review_note_form.add_note("This is a review note.")
|
|
review_page.click_insights_tab
|
|
review_page.click_timeline_tab
|
|
expect(page).to have_text("This is a review note.")
|
|
end
|
|
|
|
it "shows confirmation dialog when navigating away with unsaved note, but not after clearing the note" do
|
|
dialog = PageObjects::Components::Dialog.new
|
|
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_timeline_tab
|
|
|
|
review_note_form.form.fill_in("content", with: "This is a draft note")
|
|
|
|
click_logo
|
|
|
|
expect(dialog).to be_open
|
|
expect(dialog).to have_content(I18n.t("js.form_kit.dirty_form"))
|
|
|
|
dialog.click_no
|
|
|
|
expect(page).to have_current_path("/review/#{reviewable_flagged_post.id}")
|
|
|
|
review_note_form.form.fill_in("content", with: "")
|
|
|
|
click_logo
|
|
|
|
expect(dialog).to be_closed
|
|
expect(page).to have_current_path("/")
|
|
end
|
|
|
|
it "displays the flagged user's email address in user activity" do
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(page).to have_text("flagged@example.com")
|
|
end
|
|
|
|
describe "Moderation history" do
|
|
fab!(:flagged_user) { reviewable_flagged_post.target_created_by }
|
|
|
|
it "displays the number of times the user has been silenced, suspended and number of rejected posts" do
|
|
UserHistory.create!(
|
|
action: UserHistory.actions[:silence_user],
|
|
target_user_id: flagged_user.id,
|
|
acting_user_id: admin.id,
|
|
)
|
|
UserHistory.create!(
|
|
action: UserHistory.actions[:silence_user],
|
|
target_user_id: flagged_user.id,
|
|
acting_user_id: admin.id,
|
|
)
|
|
UserHistory.create!(
|
|
action: UserHistory.actions[:suspend_user],
|
|
target_user_id: flagged_user.id,
|
|
acting_user_id: admin.id,
|
|
)
|
|
ReviewableQueuedPost.create!(
|
|
created_by: admin,
|
|
target_created_by: flagged_user,
|
|
status: Reviewable.statuses[:rejected],
|
|
payload: {
|
|
raw: "test post 1",
|
|
},
|
|
)
|
|
ReviewableQueuedPost.create!(
|
|
created_by: admin,
|
|
target_created_by: flagged_user,
|
|
status: Reviewable.statuses[:rejected],
|
|
payload: {
|
|
raw: "test post 2",
|
|
},
|
|
)
|
|
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(page).to have_text(
|
|
I18n.t("js.review.insights.moderation_history.silenced", count: 2),
|
|
)
|
|
expect(page).to have_text(
|
|
I18n.t("js.review.insights.moderation_history.suspended", count: 1),
|
|
)
|
|
expect(page).to have_text(
|
|
I18n.t("js.review.insights.moderation_history.rejected_posts", count: 2),
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "IP lookup" do
|
|
fab!(:reviewable_flagged_post)
|
|
|
|
before do
|
|
reviewable_flagged_post.target_created_by.update!(ip_address: "81.2.69.142")
|
|
|
|
DiscourseIpInfo.open_db(File.join(Rails.root, "spec", "fixtures", "mmdb"))
|
|
Resolv::DNS
|
|
.any_instance
|
|
.stubs(:getname)
|
|
.with("81.2.69.142")
|
|
.returns("ip-81-2-69-142.example.com")
|
|
end
|
|
|
|
it "shows IP lookup information when insights tab is viewed" do
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(review_page).to have_ip_lookup_info
|
|
end
|
|
|
|
it "displays IP location, hostname, and organization when available" do
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(review_page).to have_ip_location("London, England, United Kingdom")
|
|
expect(review_page).to have_ip_hostname("ip-81-2-69-142.example.com")
|
|
end
|
|
|
|
it "shows other accounts link when there are multiple accounts with same IP" do
|
|
other_user_1 = Fabricate(:user, ip_address: "81.2.69.142")
|
|
other_user_2 = Fabricate(:user, ip_address: "81.2.69.142")
|
|
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(review_page).to have_other_accounts_link(count: 2)
|
|
end
|
|
|
|
it "opens modal with account list when clicking other accounts link" do
|
|
other_user = Fabricate(:user, username: "suspicious_user", ip_address: "81.2.69.142")
|
|
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
review_page.click_other_accounts_link
|
|
|
|
expect(review_page).to have_ip_lookup_modal
|
|
expect(review_page).to have_account_in_modal(other_user.username)
|
|
end
|
|
|
|
context "when category moderator" do
|
|
fab!(:category)
|
|
fab!(:trust_level_1_user, :trust_level_1)
|
|
fab!(:category_moderation_group) do
|
|
Fabricate(
|
|
:category_moderation_group,
|
|
category: category,
|
|
group: trust_level_1_user.groups.last,
|
|
)
|
|
end
|
|
|
|
before do
|
|
SiteSetting.enable_category_group_moderation = true
|
|
reviewable_flagged_post.topic.change_category_to_id(category.id)
|
|
sign_in trust_level_1_user
|
|
end
|
|
|
|
it "does not show IP information" do
|
|
visit "/"
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
review_page.click_insights_tab
|
|
|
|
expect(page).not_to have_text("The requested URL or resource could not be found.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "when the reviewable item is a queued post" do
|
|
fab!(:reviewable_queued_post)
|
|
|
|
it "allows to edit post when old moderator actions are enabled" do
|
|
review_page.visit_reviewable(reviewable_queued_post)
|
|
|
|
expect(page).to have_text("hello world post contents.")
|
|
review_page.click_edit_post_button
|
|
review_page.fill_post_content("Hello world from system spec!")
|
|
review_page.save_post_edit
|
|
|
|
expect(page).to have_text("Hello world from system spec!")
|
|
expect(page).not_to have_text("hello world post contents.")
|
|
end
|
|
|
|
it "shows context question for rejected queued post" do
|
|
review_page.visit_reviewable(reviewable_queued_post)
|
|
|
|
expect(review_page).to have_context_question(
|
|
reviewable_queued_post,
|
|
I18n.t("js.review.context_question.approve_post"),
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "when the reviewable item is a user" do
|
|
fab!(:user)
|
|
let(:rejection_reason_modal) { PageObjects::Modals::RejectReasonReviewable.new }
|
|
let(:scrub_user_modal) { PageObjects::Modals::ScrubRejectedUser.new }
|
|
|
|
before do
|
|
SiteSetting.must_approve_users = true
|
|
Jobs.run_immediately!
|
|
user.update!(approved: false)
|
|
user.activate
|
|
end
|
|
|
|
it "Allow to delete and scrub user" do
|
|
reviewable = ReviewableUser.find_by_target_id(user.id)
|
|
|
|
review_page.visit_reviewable(reviewable)
|
|
|
|
expect(page).to have_text(user.name)
|
|
expect(page).to have_link(user.username, href: "/admin/users/#{user.id}/#{user.username}")
|
|
|
|
review_page.select_bundled_action(reviewable, "user-delete_user")
|
|
rejection_reason_modal.fill_in_rejection_reason("Spamming the site")
|
|
rejection_reason_modal.delete_user
|
|
|
|
expect(review_page).to have_reviewable_with_rejected_status(reviewable)
|
|
|
|
expect(page).to have_text(user.name)
|
|
|
|
review_page.click_scrub_user_button
|
|
|
|
scrubbing_reason = "a spammer who knows how to make GDPR requests"
|
|
scrub_user_modal.fill_in_scrub_reason(scrubbing_reason)
|
|
expect(scrub_user_modal.scrub_button).not_to be_disabled
|
|
scrub_user_modal.scrub_button.click
|
|
|
|
expect(review_page).to have_reviewable_with_scrubbed_by(reviewable, admin.username)
|
|
expect(review_page).to have_reviewable_with_scrubbed_reason(reviewable, scrubbing_reason)
|
|
expect(review_page).to have_reviewable_with_scrubbed_at(
|
|
reviewable,
|
|
reviewable.payload["scrubbed_at"],
|
|
)
|
|
expect(page).not_to have_text(user.name)
|
|
end
|
|
|
|
it "Allows to delete and block user" do
|
|
reviewable = ReviewableUser.find_by_target_id(user.id)
|
|
|
|
review_page.visit_reviewable(reviewable)
|
|
review_page.select_bundled_action(reviewable, "user-delete_user_block")
|
|
rejection_reason_modal.fill_in_rejection_reason("Spamming the site")
|
|
rejection_reason_modal.delete_user
|
|
expect(review_page).to have_reviewable_with_rejected_status(reviewable)
|
|
expect(review_page).to have_rejected_item_in_timeline(reviewable)
|
|
end
|
|
|
|
it "Allows to approve user" do
|
|
reviewable = ReviewableUser.find_by_target_id(user.id)
|
|
|
|
review_page.visit_reviewable(reviewable)
|
|
review_page.click_approve_user_button
|
|
|
|
expect(review_page).to have_reviewable_with_approved_status(reviewable)
|
|
expect(review_page).to have_approved_item_in_timeline(reviewable)
|
|
end
|
|
end
|
|
|
|
describe "moderator" do
|
|
before do
|
|
group.add(admin)
|
|
group.add(moderator)
|
|
sign_in(moderator)
|
|
end
|
|
|
|
it "shows claimed and unclaimed events in the timeline" do
|
|
SiteSetting.reviewable_claiming = "required"
|
|
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
expect(review_page).to have_history_items(count: 2)
|
|
expect(review_page).to have_created_at_history_item
|
|
|
|
review_page.click_claim_reviewable
|
|
page.refresh
|
|
expect(review_page).to have_history_items(count: 3)
|
|
expect(review_page).to have_claimed_history_item(moderator)
|
|
|
|
review_page.click_unclaim_reviewable
|
|
page.refresh
|
|
expect(review_page).to have_history_items(count: 4)
|
|
expect(review_page).to have_unclaimed_history_item(moderator)
|
|
|
|
# remove history items created by deleted users
|
|
UserDestroyer.new(admin).destroy(moderator)
|
|
sign_in(admin)
|
|
review_page.visit_reviewable(reviewable_flagged_post)
|
|
|
|
expect(review_page).to have_history_items(count: 2)
|
|
end
|
|
end
|
|
end
|