Add domain block notes (#11515)

* Add database columns for adding notes to domain blocks/restrctions

* Add admin UI to set private and public comments when blocking a domain

* Add text for private and public comments on domain blocks

* Show domain block comments in admin UI

* Add comments to the domain block undo page

* Make UnblockDomainService more robust regarding upgraded domain blocks

* Allow editing domain blocks

* Rename button from “undo domain block” to “view domain block” in account admin UI

* Change test to unsilence silenced users from upgraded blocks
signup-info-prompt
ThibG 2019-08-07 20:20:23 +02:00 committed by Eugen Rochko
parent 94c54997cf
commit bced70469a
17 changed files with 138 additions and 34 deletions

View File

@ -2,13 +2,17 @@
module Admin module Admin
class DomainBlocksController < BaseController class DomainBlocksController < BaseController
before_action :set_domain_block, only: [:show, :destroy] before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
def new def new
authorize :domain_block, :create? authorize :domain_block, :create?
@domain_block = DomainBlock.new(domain: params[:_domain]) @domain_block = DomainBlock.new(domain: params[:_domain])
end end
def edit
authorize :domain_block, :create?
end
def create def create
authorize :domain_block, :create? authorize :domain_block, :create?
@ -35,6 +39,22 @@ module Admin
end end
end end
def update
authorize :domain_block, :create?
@domain_block.update(update_params)
severity_changed = @domain_block.severity_changed?
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
log_action :create, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else
render :edit
end
end
def show def show
authorize @domain_block, :show? authorize @domain_block, :show?
end end
@ -52,8 +72,12 @@ module Admin
@domain_block = DomainBlock.find(params[:id]) @domain_block = DomainBlock.find(params[:id])
end end
def update_params
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment)
end
def resource_params def resource_params
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports) params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment)
end end
end end
end end

View File

@ -21,6 +21,8 @@ module Admin
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) @available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment
end end
private private

View File

@ -3,13 +3,15 @@
# #
# Table name: domain_blocks # Table name: domain_blocks
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# domain :string default(""), not null # domain :string default(""), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# severity :integer default("silence") # severity :integer default("silence")
# reject_media :boolean default(FALSE), not null # reject_media :boolean default(FALSE), not null
# reject_reports :boolean default(FALSE), not null # reject_reports :boolean default(FALSE), not null
# private_comment :text
# public_comment :text
# #
class DomainBlock < ApplicationRecord class DomainBlock < ApplicationRecord

View File

@ -3,13 +3,22 @@
class BlockDomainService < BaseService class BlockDomainService < BaseService
attr_reader :domain_block attr_reader :domain_block
def call(domain_block) def call(domain_block, update = false)
@domain_block = domain_block @domain_block = domain_block
process_domain_block! process_domain_block!
process_retroactive_updates! if update
end end
private private
def process_retroactive_updates!
# If the domain block severity has been changed, undo the appropriate limitations
scope = Account.by_domain_and_subdomains(domain_block.domain)
scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.silence?
scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) unless domain_block.suspend?
end
def process_domain_block! def process_domain_block!
clear_media! if domain_block.reject_media? clear_media! if domain_block.reject_media?

View File

@ -10,24 +10,9 @@ class UnblockDomainService < BaseService
end end
def process_retroactive_updates def process_retroactive_updates
blocked_accounts.in_batches.update_all(update_options) unless domain_block.noop?
end
def blocked_accounts
scope = Account.by_domain_and_subdomains(domain_block.domain) scope = Account.by_domain_and_subdomains(domain_block.domain)
if domain_block.silence? scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.noop?
scope.where(silenced_at: @domain_block.created_at) scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) if domain_block.suspend?
else
scope.where(suspended_at: @domain_block.created_at)
end
end
def update_options
{ domain_block_impact => nil }
end
def domain_block_impact
domain_block.silence? ? :silenced_at : :suspended_at
end end
end end

View File

@ -174,7 +174,7 @@
- unless @account.local? - unless @account.local?
- if DomainBlock.where(domain: @account.domain).exists? - if DomainBlock.where(domain: @account.domain).exists?
= link_to t('admin.domain_blocks.undo'), admin_instance_path(@account.domain), class: 'button' = link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
- else - else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'

View File

@ -0,0 +1,30 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do
= t('admin.domain_blocks.edit')
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :put do |f|
= render 'shared/error_messages', object: @domain_block
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('admin.domain_blocks.new.hint'), required: true, readonly: true, disabled: true
.fields-row__column.fields-row__column-6.fields-group
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html')
.fields-group
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
.fields-group
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
.field-group
= f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
.field-group
= f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
.actions
= f.button :button, t('generic.save_changes'), type: :submit

View File

@ -20,5 +20,11 @@
.fields-group .fields-group
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
.field-group
= f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
.field-group
= f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
.actions .actions
= f.button :button, t('.create'), type: :submit = f.button :button, t('.create'), type: :submit

View File

@ -1,6 +1,18 @@
- content_for :page_title do - content_for :page_title do
= t('admin.domain_blocks.show.title', domain: @domain_block.domain) = t('admin.domain_blocks.show.title', domain: @domain_block.domain)
- if @domain_block.private_comment.present?
.speech-bubble
.speech-bubble__bubble
= simple_format(h(@domain_block.private_comment))
.speech-bubble__owner= t 'admin.instances.private_comment'
- if @domain_block.public_comment.present?
.speech-bubble
.speech-bubble__bubble
= simple_format(h(@domain_block.public_comment))
.speech-bubble__owner= t 'admin.instances.public_comment'
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f| = simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
- unless (@domain_block.noop?) - unless (@domain_block.noop?)

View File

@ -31,6 +31,18 @@
= fa_icon 'times' = fa_icon 'times'
.dashboard__counters__label= t 'admin.instances.delivery_available' .dashboard__counters__label= t 'admin.instances.delivery_available'
- if @private_comment.present?
.speech-bubble
.speech-bubble__bubble
= simple_format(h(@private_comment))
.speech-bubble__owner= t 'admin.instances.private_comment'
- if @public_comment.present?
.speech-bubble
.speech-bubble__bubble
= simple_format(h(@public_comment))
.speech-bubble__owner= t 'admin.instances.public_comment'
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }
@ -41,6 +53,7 @@
- if @domain_allow - if @domain_allow
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
- elsif @domain_block - elsif @domain_block
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@domain_block), class: 'button'
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button' = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
- else - else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'

View File

@ -3,8 +3,8 @@
class DomainBlockWorker class DomainBlockWorker
include Sidekiq::Worker include Sidekiq::Worker
def perform(domain_block_id) def perform(domain_block_id, update = false)
BlockDomainService.new.call(DomainBlock.find(domain_block_id)) BlockDomainService.new.call(DomainBlock.find(domain_block_id), update)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
true true
end end

View File

@ -284,6 +284,7 @@ en:
created_msg: Domain block is now being processed created_msg: Domain block is now being processed
destroyed_msg: Domain block has been undone destroyed_msg: Domain block has been undone
domain: Domain domain: Domain
edit: Edit domain block
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first. existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
new: new:
create: Create block create: Create block
@ -294,6 +295,10 @@ en:
silence: Silence silence: Silence
suspend: Suspend suspend: Suspend
title: New domain block title: New domain block
private_comment: Private comment
private_comment_hint: Comment about this domain limitation for internal use by the moderators.
public_comment: Public comment
public_comment_hint: Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled.
reject_media: Reject media files reject_media: Reject media files
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
reject_reports: Reject reports reject_reports: Reject reports
@ -313,6 +318,7 @@ en:
title: Undo domain block for %{domain} title: Undo domain block for %{domain}
undo: Undo undo: Undo
undo: Undo domain block undo: Undo domain block
view: View domain block
email_domain_blocks: email_domain_blocks:
add_new: Add new add_new: Add new
created_msg: Successfully added e-mail domain to blacklist created_msg: Successfully added e-mail domain to blacklist
@ -336,6 +342,8 @@ en:
all: All all: All
limited: Limited limited: Limited
title: Moderation title: Moderation
private_comment: Private comment
public_comment: Public comment
title: Federation title: Federation
total_blocked_by_us: Blocked by us total_blocked_by_us: Blocked by us
total_followed_by_them: Followed by them total_followed_by_them: Followed by them

View File

@ -155,7 +155,11 @@ Rails.application.routes.draw do
get '/dashboard', to: 'dashboard#index' get '/dashboard', to: 'dashboard#index'
resources :domain_allows, only: [:new, :create, :show, :destroy] resources :domain_allows, only: [:new, :create, :show, :destroy]
resources :domain_blocks, only: [:new, :create, :show, :destroy] resources :domain_blocks, only: [:new, :create, :show, :destroy, :update] do
member do
get :edit
end
end
resources :email_domain_blocks, only: [:index, :new, :create, :destroy] resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
resources :action_logs, only: [:index] resources :action_logs, only: [:index]
resources :warning_presets, except: [:new] resources :warning_presets, except: [:new]

View File

@ -0,0 +1,7 @@
class AddCommentsToDomainBlocks < ActiveRecord::Migration[5.2]
def change
add_column :domain_blocks, :private_comment, :text
add_column :domain_blocks, :public_comment, :text
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_08_05_123746) do ActiveRecord::Schema.define(version: 2019_08_07_135426) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -259,6 +259,8 @@ ActiveRecord::Schema.define(version: 2019_08_05_123746) do
t.integer "severity", default: 0 t.integer "severity", default: 0
t.boolean "reject_media", default: false, null: false t.boolean "reject_media", default: false, null: false
t.boolean "reject_reports", default: false, null: false t.boolean "reject_reports", default: false, null: false
t.text "private_comment"
t.text "public_comment"
t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
end end

View File

@ -31,7 +31,7 @@ describe UnblockDomainService, type: :service do
subject.call(@domain_block) subject.call(@domain_block)
expect_deleted_domain_block expect_deleted_domain_block
expect(@suspended.reload.suspended?).to be false expect(@suspended.reload.suspended?).to be false
expect(@silenced.reload.silenced?).to be true expect(@silenced.reload.silenced?).to be false
expect(@independently_suspended.reload.suspended?).to be true expect(@independently_suspended.reload.suspended?).to be true
expect(@independently_silenced.reload.silenced?).to be true expect(@independently_silenced.reload.silenced?).to be true
end end

View File

@ -14,7 +14,7 @@ describe DomainBlockWorker do
result = subject.perform(domain_block.id) result = subject.perform(domain_block.id)
expect(result).to be_nil expect(result).to be_nil
expect(service).to have_received(:call).with(domain_block) expect(service).to have_received(:call).with(domain_block, false)
end end
it 'calls domain block service for relevant domain block' do it 'calls domain block service for relevant domain block' do