FEATURE: Import and export watched word (#12444)

Find & Replace and Autotag watched words were not completely exported
and import did not work with these either. This commit changes the
input and output format to CSV, which allows for a secondary column.

This change is backwards compatible because a CSV file with only one
column has one value per line.
This commit is contained in:
Bianca Nenciu 2021-03-22 22:32:18 +02:00 committed by GitHub
parent fb4486d5f1
commit 437c9a554b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 8 deletions

View File

@ -1,5 +1,5 @@
<label class="btn btn-default {{if addDisabled "disabled"}}">
{{d-icon "upload"}}
{{i18n "admin.watched_words.form.upload"}}
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept="text/plain">
<input class="hidden-upload-field" disabled={{addDisabled}} type="file">
</label>

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'csv'
class Admin::WatchedWordsController < Admin::AdminController
skip_before_action :check_xhr, only: [:download]
@ -26,12 +28,20 @@ class Admin::WatchedWordsController < Admin::AdminController
def upload
file = params[:file] || params[:files].first
action_key = params[:action_key].to_sym
has_replacement = WatchedWord.has_replacement?(action_key)
Scheduler::Defer.later("Upload watched words") do
begin
File.open(file.tempfile, encoding: "bom|utf-8").each_line do |line|
WatchedWord.create_or_update_word(word: line, action_key: action_key) unless line.empty?
CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row|
if row[0].present? && (!has_replacement || row[1].present?)
WatchedWord.create_or_update_word(
word: row[0],
replacement: has_replacement ? row[1] : nil,
action_key: action_key
)
end
end
data = { url: '/ok' }
rescue => e
data = failed_json.merge(errors: [e.message])
@ -48,11 +58,17 @@ class Admin::WatchedWordsController < Admin::AdminController
action = WatchedWord.actions[name]
raise Discourse::NotFound if !action
content = WatchedWord.where(action: action).pluck(:word).join("\n")
content = WatchedWord.where(action: action)
if WatchedWord.has_replacement?(name)
content = content.pluck(:word, :replacement).map(&:to_csv).join
else
content = content.pluck(:word).join("\n")
end
headers['Content-Length'] = content.bytesize.to_s
send_data content,
filename: "#{Discourse.current_hostname}-watched-words-#{name}.txt",
content_type: "text/plain"
filename: "#{Discourse.current_hostname}-watched-words-#{name}.csv",
content_type: "text/csv"
end
def clear_all

View File

@ -46,6 +46,10 @@ class WatchedWord < ActiveRecord::Base
w
end
def self.has_replacement?(action)
action == :replace || action == :tag
end
def action_key=(arg)
self.action = self.class.actions[arg.to_sym]
end

View File

@ -8,6 +8,6 @@ class WatchedWordSerializer < ApplicationSerializer
end
def include_replacement?
action == :replace || action == :tag
WatchedWord.has_replacement?(action)
end
end

11
spec/fixtures/csv/words_tag.csv vendored Normal file
View File

@ -0,0 +1,11 @@
hello,"tag1,tag2"
,"tag1,tag3"
world,"tag2,tag3"
test,
1 hello tag1,tag2
2 tag1,tag3
3 world tag2,tag3
4 test

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require 'csv'
require 'rails_helper'
RSpec.describe Admin::WatchedWordsController do
@ -47,6 +48,23 @@ RSpec.describe Admin::WatchedWordsController do
expect(WatchedWord.pluck(:action).uniq).to eq([WatchedWord.actions[:flag]])
end
it 'creates the words from the file' do
post '/admin/logs/watched_words/upload.json', params: {
action_key: 'tag',
file: Rack::Test::UploadedFile.new(file_from_fixtures("words_tag.csv", "csv"))
}
expect(response.status).to eq(200)
expect(WatchedWord.count).to eq(2)
expect(WatchedWord.pluck(:word, :replacement)).to contain_exactly(
['hello', 'tag1,tag2'],
['world', 'tag2,tag3']
)
expect(WatchedWord.pluck(:action).uniq).to eq([WatchedWord.actions[:tag]])
end
end
end
@ -67,6 +85,8 @@ RSpec.describe Admin::WatchedWordsController do
block_word_1 = Fabricate(:watched_word, action: WatchedWord.actions[:block])
block_word_2 = Fabricate(:watched_word, action: WatchedWord.actions[:block])
censor_word_1 = Fabricate(:watched_word, action: WatchedWord.actions[:censor])
autotag_1 = Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: "tag1,tag2")
autotag_2 = Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: "tag3,tag2")
get "/admin/logs/watched_words/action/block/download"
expect(response.status).to eq(200)
@ -76,7 +96,15 @@ RSpec.describe Admin::WatchedWordsController do
get "/admin/logs/watched_words/action/censor/download"
expect(response.status).to eq(200)
censor_words = response.body.split("\n")
expect(censor_words).to eq([censor_word_1.word])
expect(censor_words).to contain_exactly(censor_word_1.word)
get "/admin/logs/watched_words/action/tag/download"
expect(response.status).to eq(200)
tag_words = response.body.split("\n").map(&:parse_csv)
expect(tag_words).to contain_exactly(
[autotag_1.word, autotag_1.replacement],
[autotag_2.word, autotag_2.replacement]
)
end
end
end