From 3fd04df781f1cb736228e3212ef79ab2ee20326e Mon Sep 17 00:00:00 2001
From: Gerhard Schlager
Date: Mon, 18 Mar 2019 21:09:13 +0100
Subject: [PATCH] FEATURE: Locale support for seeded categories and topics
(#7110)
---
.../controllers/modals/admin-reseed.js.es6 | 42 ++++
.../admin/routes/admin-site-text-index.js.es6 | 8 +
.../admin/templates/modal/admin-reseed.hbs | 36 ++++
.../admin/templates/site-text-index.hbs | 12 +-
.../stylesheets/common/admin/admin_base.scss | 5 +-
.../stylesheets/common/admin/customize.scss | 12 ++
.../admin/site_texts_controller.rb | 25 +++
config/locales/client.en.yml | 11 +
config/locales/server.en.yml | 4 +-
config/routes.rb | 3 +
config/site_settings.yml | 9 +
db/fixtures/001_categories.rb | 27 ---
db/fixtures/001_refresh.rb | 3 +
db/fixtures/500_categories.rb | 3 +
db/fixtures/500_lounge_category.rb | 43 ----
db/fixtures/501_meta_category.rb | 31 ---
db/fixtures/502_staff_category.rb | 40 ----
db/fixtures/990_topics.rb | 68 +-----
.../20140120155706_add_lounge_category.rb | 37 +---
.../20140122043508_add_meta_category.rb | 33 +--
.../20140227201005_add_staff_category.rb | 32 +--
...0035_add_missing_topic_id_site_settings.rb | 67 ++++++
lib/introduction_updater.rb | 6 +-
lib/seed_data/categories.rb | 174 ++++++++++++++++
lib/seed_data/topics.rb | 197 ++++++++++++++++++
lib/tasks/i18n.rake | 13 ++
lib/tasks/topics.rake | 36 ----
lib/wizard/builder.rb | 24 ++-
spec/lib/introduction_updater_spec.rb | 4 +-
spec/lib/seed_data/categories_spec.rb | 153 ++++++++++++++
spec/lib/seed_data/topics_spec.rb | 122 +++++++++++
.../admin/site_texts_controller_spec.rb | 56 +++++
.../acceptance/admin-site-text-test.js.es6 | 2 +-
33 files changed, 985 insertions(+), 353 deletions(-)
create mode 100644 app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
create mode 100644 app/assets/javascripts/admin/templates/modal/admin-reseed.hbs
delete mode 100644 db/fixtures/001_categories.rb
create mode 100644 db/fixtures/001_refresh.rb
create mode 100644 db/fixtures/500_categories.rb
delete mode 100644 db/fixtures/500_lounge_category.rb
delete mode 100644 db/fixtures/501_meta_category.rb
delete mode 100644 db/fixtures/502_staff_category.rb
create mode 100644 db/migrate/20190227210035_add_missing_topic_id_site_settings.rb
create mode 100644 lib/seed_data/categories.rb
create mode 100644 lib/seed_data/topics.rb
create mode 100644 spec/lib/seed_data/categories_spec.rb
create mode 100644 spec/lib/seed_data/topics_spec.rb
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
new file mode 100644
index 00000000000..f71c7eaf2e7
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
@@ -0,0 +1,42 @@
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import { ajax } from "discourse/lib/ajax";
+
+export default Ember.Controller.extend(ModalFunctionality, {
+ loading: true,
+ reseeding: false,
+ categories: null,
+ topics: null,
+
+ onShow() {
+ ajax("/admin/customize/reseed")
+ .then(result => {
+ this.setProperties({
+ categories: result.categories,
+ topics: result.topics
+ });
+ })
+ .finally(() => this.set("loading", false));
+ },
+
+ _extractSelectedIds(items) {
+ return items.filter(item => item.selected).map(item => item.id);
+ },
+
+ actions: {
+ reseed() {
+ this.set("reseeding", true);
+ ajax("/admin/customize/reseed", {
+ data: {
+ category_ids: this._extractSelectedIds(this.categories),
+ topic_ids: this._extractSelectedIds(this.topics)
+ },
+ method: "POST"
+ })
+ .then(
+ () => this.send("closeModal"),
+ () => bootbox.alert(I18n.t("generic_error"))
+ )
+ .finally(() => this.set("reseeding", false));
+ }
+ }
+});
diff --git a/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6 b/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6
index 4c3e826954c..dfec2f64d38 100644
--- a/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6
@@ -1,3 +1,5 @@
+import showModal from "discourse/lib/show-modal";
+
export default Ember.Route.extend({
queryParams: {
q: { replace: true },
@@ -13,5 +15,11 @@ export default Ember.Route.extend({
setupController(controller, model) {
controller.set("siteTexts", model);
+ },
+
+ actions: {
+ showReseedModal() {
+ showModal("admin-reseed", { admin: true });
+ }
}
});
diff --git a/app/assets/javascripts/admin/templates/modal/admin-reseed.hbs b/app/assets/javascripts/admin/templates/modal/admin-reseed.hbs
new file mode 100644
index 00000000000..0095bdf3228
--- /dev/null
+++ b/app/assets/javascripts/admin/templates/modal/admin-reseed.hbs
@@ -0,0 +1,36 @@
+{{#d-modal-body title="admin.reseed.modal.title" subtitle="admin.reseed.modal.subtitle" class="reseed-modal"}}
+ {{#conditional-loading-spinner condition=loading}}
+ {{#if categories}}
+
+ {{/if}}
+
+
+
+ {{#if topics}}
+
+ {{/if}}
+ {{/conditional-loading-spinner}}
+{{/d-modal-body}}
+
+
diff --git a/app/assets/javascripts/admin/templates/site-text-index.hbs b/app/assets/javascripts/admin/templates/site-text-index.hbs
index e75b4a1af91..de6b4e707de 100644
--- a/app/assets/javascripts/admin/templates/site-text-index.hbs
+++ b/app/assets/javascripts/admin/templates/site-text-index.hbs
@@ -7,12 +7,20 @@
autofocus="true"
key-up=(action "search")}}
-
+
{{#conditional-loading-spinner condition=searching}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index a6821b00750..d040fbe3840 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -204,11 +204,8 @@ $mobile-breakpoint: 700px;
font-size: $font-0;
width: 50%;
}
- .extra-options {
+ .reseed {
float: right;
- input[type="checkbox"] {
- margin-right: 0.5em;
- }
}
}
.text-highlight {
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index fbd4e40edbf..a5f6b9923a8 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -781,3 +781,15 @@
}
}
}
+
+.reseed-modal {
+ .options-group-title {
+ font-size: $font-up-2;
+ font-weight: bold;
+ margin: 8px 0;
+ }
+
+ .option {
+ margin-left: 1em;
+ }
+}
diff --git a/app/controllers/admin/site_texts_controller.rb b/app/controllers/admin/site_texts_controller.rb
index 1cd37e31715..46998cdfb8d 100644
--- a/app/controllers/admin/site_texts_controller.rb
+++ b/app/controllers/admin/site_texts_controller.rb
@@ -78,6 +78,31 @@ class Admin::SiteTextsController < Admin::AdminController
render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true)
end
+ def get_reseed_options
+ render_json_dump(
+ categories: SeedData::Categories.with_default_locale.reseed_options,
+ topics: SeedData::Topics.with_default_locale.reseed_options
+ )
+ end
+
+ def reseed
+ hijack do
+ if params[:category_ids].present?
+ SeedData::Categories.with_default_locale.update(
+ site_setting_names: params[:category_ids]
+ )
+ end
+
+ if params[:topic_ids].present?
+ SeedData::Topics.with_default_locale.update(
+ site_setting_names: params[:topic_ids]
+ )
+ end
+
+ render json: success_json
+ end
+ end
+
protected
def record_for(k, value = nil)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 37c711ef6c5..765a7750163 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4227,6 +4227,17 @@ en:
add: "Add"
filter: "Search (URL or External URL)"
+ reseed:
+ action:
+ label: "Reseed…"
+ title: "Update content created by Discourse with latest translations"
+
+ modal:
+ title: "Reseed"
+ subtitle: "Update seeded categories and topics with latest translations"
+ categories: "Categories"
+ topics: "Topics"
+
wizard_js:
wizard:
done: "Done"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 28a20a794c7..b1724d15ed5 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -578,6 +578,8 @@ en:
[trust]: https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/
+ admin_quick_start_title: "READ ME FIRST: Admin Quick Start Guide"
+
category:
topic_prefix: "About the %{category} category"
replace_paragraph: "(Replace this first paragraph with a brief description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.)"
@@ -1266,7 +1268,7 @@ en:
site_settings:
censored_words: "Words that will be automatically replaced with ■■■■"
delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days."
- default_locale: "The default language of this Discourse instance"
+ default_locale: "The default language of this Discourse instance. You can reseed system generated categories and topics at Customize / Text Content."
allow_user_locale: "Allow users to choose their own language interface preference"
set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers. (EXPERIMENTAL, does not work with anonymous cache)"
support_mixed_text_direction: "Support mixed left-to-right and right-to-left text directions."
diff --git a/config/routes.rb b/config/routes.rb
index 49e7f26d1dd..93d854e232a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -226,6 +226,9 @@ Discourse::Application.routes.draw do
delete 'site_texts/:id.json' => 'site_texts#revert', constraints: { id: /[\w.\-\+]+/i }
delete 'site_texts/:id' => 'site_texts#revert', constraints: { id: /[\w.\-\+]+/i }
+ get 'reseed' => 'site_texts#get_reseed_options'
+ post 'reseed' => 'site_texts#reseed'
+
get 'email_templates' => 'email_templates#index'
get 'email_templates/(:id)' => 'email_templates#show', constraints: { id: /[0-9a-z_.]+/ }
put 'email_templates/(:id)' => 'email_templates#update', constraints: { id: /[0-9a-z_.]+/ }
diff --git a/config/site_settings.yml b/config/site_settings.yml
index d1f07c624ea..5b3c3e5c0cb 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1771,6 +1771,15 @@ uncategorized:
privacy_topic_id:
default: -1
hidden: true
+ welcome_topic_id:
+ default: -1
+ hidden: true
+ lounge_welcome_topic_id:
+ default: -1
+ hidden: true
+ admin_quick_start_topic_id:
+ default: -1
+ hidden: true
bootstrap_mode_min_users:
default: 50
diff --git a/db/fixtures/001_categories.rb b/db/fixtures/001_categories.rb
deleted file mode 100644
index 397b7f3201b..00000000000
--- a/db/fixtures/001_categories.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'migration/column_dropper'
-
-# fix any bust caches post initial migration
-ActiveRecord::Base.send(:subclasses).each { |m| m.reset_column_information }
-
-SiteSetting.refresh!
-uncat_id = SiteSetting.uncategorized_category_id
-uncat_id = -1 unless Numeric === uncat_id
-
-if uncat_id == -1 || !Category.exists?(uncat_id)
- puts "Seeding uncategorized category!"
-
- count = DB.exec "SELECT 1 FROM categories WHERE lower(name) = 'uncategorized'"
- name = 'Uncategorized'
- name << SecureRandom.hex if count > 0
-
- result = DB.query_single "INSERT INTO categories
- (name,color,slug,description,text_color, user_id, created_at, updated_at, position, name_lower)
- VALUES ('#{name}', '0088CC', 'uncategorized', '', 'FFFFFF', -1, now(), now(), 1, '#{name.downcase}' )
- RETURNING id
- "
- category_id = result.first.to_i
-
- DB.exec "DELETE FROM site_settings where name = 'uncategorized_category_id'"
- DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
- VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())"
-end
diff --git a/db/fixtures/001_refresh.rb b/db/fixtures/001_refresh.rb
new file mode 100644
index 00000000000..c206aee9083
--- /dev/null
+++ b/db/fixtures/001_refresh.rb
@@ -0,0 +1,3 @@
+# fix any bust caches post initial migration
+ActiveRecord::Base.send(:subclasses).each { |m| m.reset_column_information }
+SiteSetting.refresh!
diff --git a/db/fixtures/500_categories.rb b/db/fixtures/500_categories.rb
new file mode 100644
index 00000000000..5a6376f0821
--- /dev/null
+++ b/db/fixtures/500_categories.rb
@@ -0,0 +1,3 @@
+if !Rails.env.test?
+ SeedData::Categories.with_default_locale.create
+end
diff --git a/db/fixtures/500_lounge_category.rb b/db/fixtures/500_lounge_category.rb
deleted file mode 100644
index 2aa42f21cc8..00000000000
--- a/db/fixtures/500_lounge_category.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-unless Rails.env.test?
- lounge = Category.find_by(id: SiteSetting.lounge_category_id)
- if lounge && lounge.created_at == lounge.updated_at &&
- !lounge.group_ids.include?(Group[:trust_level_3].id)
-
- # The category for users with trust level 3 has been created.
- # Add initial permissions and description. They can be changed later.
-
- Category.transaction do
- lounge.group_names = ['trust_level_3']
- unless lounge.save
- puts lounge.errors.full_messages
- raise "Failed to set permissions on trust level 3 lounge category!"
- end
-
- if lounge.topic_id.nil?
- creator = PostCreator.new(Discourse.system_user,
- raw: I18n.t('vip_category_description'),
- title: I18n.t('category.topic_prefix', category: lounge.name),
- category: lounge.name,
- archetype: Archetype.default,
- skip_validations: true
- )
- post = creator.create
-
- unless post && post.id
- puts post.errors.full_messages if post
- puts creator.errors.inspect
- raise "Failed to create description for trust level 3 lounge!"
- end
-
- lounge.topic_id = post.topic.id
- unless lounge.save
- puts lounge.errors.full_messages
- puts "Failed to set the lounge description topic!"
- end
-
- # Reset topic count because we don't count the description topic
- DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{lounge.id}"
- end
- end
- end
-end
diff --git a/db/fixtures/501_meta_category.rb b/db/fixtures/501_meta_category.rb
deleted file mode 100644
index 38f89856bb9..00000000000
--- a/db/fixtures/501_meta_category.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-unless Rails.env.test?
- meta = Category.find_by(id: SiteSetting.meta_category_id)
- if meta && !meta.topic_id
-
- Category.transaction do
- creator = PostCreator.new(Discourse.system_user,
- raw: I18n.t('meta_category_description'),
- title: I18n.t('category.topic_prefix', category: meta.name),
- category: meta.name,
- archetype: Archetype.default
- )
- post = creator.create
-
- unless post && post.id
- puts post.errors.full_messages if post
- puts creator.errors.inspect
- raise "Failed meta topic"
- end
-
- meta.set_permissions(everyone: :full)
- meta.topic_id = post.topic.id
- unless meta.save
- puts meta.errors.full_messages
- puts "Failed to set the meta description and permission!"
- end
-
- # Reset topic count because we don't count the description topic
- DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{meta.id}"
- end
- end
-end
diff --git a/db/fixtures/502_staff_category.rb b/db/fixtures/502_staff_category.rb
deleted file mode 100644
index 18b5c54337d..00000000000
--- a/db/fixtures/502_staff_category.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-unless Rails.env.test?
- staff = Category.find_by(id: SiteSetting.staff_category_id)
- if staff && !staff.group_ids.include?(Group[:staff].id)
-
- # Add permissions and a description to the Staff category.
-
- Category.transaction do
- staff.group_names = ['staff']
- unless staff.save
- puts staff.errors.full_messages
- raise "Failed to set permissions on the Staff category!"
- end
-
- if staff.topic_id.nil?
- creator = PostCreator.new(Discourse.system_user,
- raw: I18n.t('staff_category_description'),
- title: I18n.t('category.topic_prefix', category: staff.name),
- category: staff.name,
- archetype: Archetype.default
- )
- post = creator.create
-
- unless post && post.id
- puts post.errors.full_messages if post
- puts creator.errors.inspect
- raise "Failed to create description for Staff category!"
- end
-
- staff.topic_id = post.topic.id
- unless staff.save
- puts staff.errors.full_messages
- puts "Failed to set the Staff category description topic!"
- end
-
- # Reset topic count because we don't count the description topic
- DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{staff.id}"
- end
- end
- end
-end
diff --git a/db/fixtures/990_topics.rb b/db/fixtures/990_topics.rb
index 1ae8dd2726d..0a5ede29346 100644
--- a/db/fixtures/990_topics.rb
+++ b/db/fixtures/990_topics.rb
@@ -2,64 +2,14 @@ User.reset_column_information
Topic.reset_column_information
Post.reset_column_information
-staff = Category.find_by(id: SiteSetting.staff_category_id)
-seed_welcome_topics = (Topic.where('id NOT IN (SELECT topic_id from categories where topic_id is not null)').count == 0 && !Rails.env.test?)
+if !Rails.env.test?
+ topics_exist = Topic.where(<<~SQL).exists?
+ id NOT IN (
+ SELECT topic_id
+ FROM categories
+ WHERE topic_id IS NOT NULL
+ )
+ SQL
-unless Rails.env.test?
- def create_static_page_topic(site_setting_key, title_key, body_key, body_override, category, description, params = {})
- unless SiteSetting.send(site_setting_key) > 0
- creator = PostCreator.new(Discourse.system_user,
- title: I18n.t(title_key, default: I18n.t(title_key, locale: :en)),
- raw: body_override.present? ? body_override : I18n.t(body_key, params.merge(default: I18n.t(body_key, params.merge(locale: :en)))),
- skip_validations: true,
- category: category ? category.name : nil)
- post = creator.create
-
- raise "Failed to create the #{description} topic! #{creator.errors.full_messages.join('. ')}" if creator.errors.present?
-
- SiteSetting.send("#{site_setting_key}=", post.topic_id)
-
- _reply = PostCreator.create(Discourse.system_user,
- raw: I18n.t('static_topic_first_reply', page_name: I18n.t(title_key, default: I18n.t(title_key, locale: :en))),
- skip_validations: true,
- topic_id: post.topic_id)
- end
- end
-
- create_static_page_topic('tos_topic_id', 'tos_topic.title', "tos_topic.body", nil, staff, "terms of service",
- company_name: SiteSetting.company_name.presence || "company_name",
- base_url: Discourse.base_url,
- contact_email: SiteSetting.contact_email.presence || "contact_email",
- governing_law: SiteSetting.governing_law.presence || "governing_law",
- city_for_disputes: SiteSetting.city_for_disputes.presence || "city_for_disputes")
-
- create_static_page_topic('guidelines_topic_id', 'guidelines_topic.title', "guidelines_topic.body", nil, staff, "guidelines", base_path: Discourse.base_path)
-
- create_static_page_topic('privacy_topic_id', 'privacy_topic.title', "privacy_topic.body", nil, staff, "privacy policy")
-end
-
-if seed_welcome_topics
- puts "Seeding welcome topics"
-
- post = PostCreator.create(Discourse.system_user, raw: I18n.t('discourse_welcome_topic.body', base_path: Discourse.base_path), title: I18n.t('discourse_welcome_topic.title'), skip_validations: true)
- post.topic.update_pinned(true, true)
- TopicCustomField.create(topic_id: post.topic.id, name: "is_welcome_topic", value: "true")
-
- lounge = Category.find_by(id: SiteSetting.lounge_category_id)
- if lounge
- post = PostCreator.create(Discourse.system_user, raw: I18n.t('lounge_welcome.body', base_path: Discourse.base_path), title: I18n.t('lounge_welcome.title'), skip_validations: true, category: lounge.name)
- post.topic.update_pinned(true)
- end
-
- filename = DiscoursePluginRegistry.seed_data["admin_quick_start_filename"]
- if filename.nil? || !File.exists?(filename)
- filename = Rails.root + 'docs/ADMIN-QUICK-START-GUIDE.md'
- end
-
- welcome = File.read(filename)
- PostCreator.create(Discourse.system_user,
- raw: welcome,
- title: DiscoursePluginRegistry.seed_data["admin_quick_start_title"] || "READ ME FIRST: Admin Quick Start Guide",
- skip_validations: true,
- category: staff ? staff.name : nil)
+ SeedData::Topics.with_default_locale.create(include_welcome_topics: !topics_exist)
end
diff --git a/db/migrate/20140120155706_add_lounge_category.rb b/db/migrate/20140120155706_add_lounge_category.rb
index fea475a7d3b..ea9a065b45b 100644
--- a/db/migrate/20140120155706_add_lounge_category.rb
+++ b/db/migrate/20140120155706_add_lounge_category.rb
@@ -1,38 +1,5 @@
class AddLoungeCategory < ActiveRecord::Migration[4.2]
- def up
- return if Rails.env.test?
-
- I18n.overrides_disabled do
- result = DB.exec "SELECT 1 FROM site_settings where name = 'lounge_category_id'"
- if result == 0
- description = I18n.t('vip_category_description')
-
- default_name = I18n.t('vip_category_name')
- name = if DB.exec("SELECT 1 FROM categories where name = '#{default_name}'") == 0
- default_name
- else
- "CHANGE_ME"
- end
-
- result = DB.query_single "INSERT INTO categories
- (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
- VALUES (:name, 'A461EF', '652D90', now(), now(), -1, '', :description, true, 3)
- RETURNING id", name: name, description: description
-
- category_id = result.first.to_i
-
- DB.exec "UPDATE categories SET slug = :slug
- WHERE id = :category_id",
- slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
-
- execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
- VALUES ('lounge_category_id', 3, #{category_id.to_i}, now(), now())"
- end
- end
- end
-
- def down
- # Don't reverse this change. There is so much logic around deleting a category that it's messy
- # to try to do in sql. The up method will just make sure never to create the category twice.
+ def change
+ # replaced by fixture
end
end
diff --git a/db/migrate/20140122043508_add_meta_category.rb b/db/migrate/20140122043508_add_meta_category.rb
index c4ee6b6b6bb..f139dd78dfa 100644
--- a/db/migrate/20140122043508_add_meta_category.rb
+++ b/db/migrate/20140122043508_add_meta_category.rb
@@ -1,34 +1,5 @@
class AddMetaCategory < ActiveRecord::Migration[4.2]
- def up
- return if Rails.env.test?
-
- I18n.overrides_disabled do
- result = DB.exec "SELECT 1 FROM site_settings where name = 'meta_category_id'"
- if result == 0
- description = I18n.t('meta_category_description')
- name = I18n.t('meta_category_name')
-
- if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0
- result = DB.query_single "INSERT INTO categories
- (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
- VALUES (:name, '808281', 'FFFFFF', now(), now(), -1, :slug, :description, true, 1)
- RETURNING id", name: name, slug: '', description: description
-
- category_id = result.first.to_i
-
- DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id",
- slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
-
- execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
- VALUES ('meta_category_id', 3, #{category_id}, now(), now())"
- end
-
- end
- end
- end
-
- def down
- # Don't reverse this change. There is so much logic around deleting a category that it's messy
- # to try to do in sql. The up method will just make sure never to create the category twice.
+ def change
+ # replaced by fixture
end
end
diff --git a/db/migrate/20140227201005_add_staff_category.rb b/db/migrate/20140227201005_add_staff_category.rb
index 9a990806b96..e18105b566f 100644
--- a/db/migrate/20140227201005_add_staff_category.rb
+++ b/db/migrate/20140227201005_add_staff_category.rb
@@ -1,33 +1,5 @@
class AddStaffCategory < ActiveRecord::Migration[4.2]
- def up
- return if Rails.env.test?
-
- I18n.overrides_disabled do
- result = DB.exec "SELECT 1 FROM site_settings where name = 'staff_category_id'"
- if result == 0
- description = I18n.t('staff_category_description')
- name = I18n.t('staff_category_name')
-
- if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0
-
- result = DB.query_single "INSERT INTO categories
- (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
- VALUES (:name, 'E45735', 'FFFFFF', now(), now(), -1, '', :description, true, 2)
- RETURNING id", name: name, description: description
-
- category_id = result.first.to_i
-
- DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id",
- slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
-
- DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
- VALUES ('staff_category_id', 3, #{category_id.to_i}, now(), now())"
- end
- end
- end
- end
-
- def down
- # Do nothing
+ def change
+ # replaced by fixture
end
end
diff --git a/db/migrate/20190227210035_add_missing_topic_id_site_settings.rb b/db/migrate/20190227210035_add_missing_topic_id_site_settings.rb
new file mode 100644
index 00000000000..afd04850149
--- /dev/null
+++ b/db/migrate/20190227210035_add_missing_topic_id_site_settings.rb
@@ -0,0 +1,67 @@
+class AddMissingTopicIdSiteSettings < ActiveRecord::Migration[5.2]
+ def up
+ # Welcome Topic
+ execute <<~SQL
+ INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
+ SELECT 'welcome_topic_id', 3, topic_id, created_at, updated_at
+ FROM topic_custom_fields
+ WHERE name = 'is_welcome_topic' AND value = 'true' AND NOT EXISTS(
+ SELECT 1
+ FROM site_settings
+ WHERE name = 'welcome_topic_id'
+ )
+ LIMIT 1
+ SQL
+
+ execute <<~SQL
+ DELETE FROM topic_custom_fields
+ WHERE name = 'is_welcome_topic' AND value = 'true'
+ SQL
+
+ # Lounge Welcome Topic
+ execute <<~SQL
+ INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
+ SELECT 'lounge_welcome_topic_id', 3, id, created_at, updated_at
+ FROM topics
+ WHERE title = 'Welcome to the Lounge' AND NOT EXISTS(
+ SELECT 1
+ FROM site_settings
+ WHERE name = 'lounge_welcome_topic_id'
+ ) AND category_id = (
+ SELECT value::INT
+ FROM site_settings
+ WHERE name = 'lounge_category_id'
+ )
+ ORDER BY created_at
+ LIMIT 1
+ SQL
+
+ # Admin Quick Start Guide
+ execute <<~SQL
+ INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
+ SELECT 'admin_quick_start_topic_id', 3, id, created_at, updated_at
+ FROM topics
+ WHERE title IN ('READ ME FIRST: Admin Quick Start Guide', 'READ ME FIRST: Getting Started') AND NOT EXISTS(
+ SELECT 1
+ FROM site_settings
+ WHERE name = 'admin_quick_start_topic_id'
+ )
+ ORDER BY created_at
+ LIMIT 1
+ SQL
+ end
+
+ def down
+ execute <<~SQL
+ INSERT INTO topic_custom_fields(topic_id, name, value, created_at, updated_at)
+ SELECT value::INTEGER, 'is_welcome_topic', 'true', created_at, updated_at
+ FROM site_settings
+ WHERE name = 'welcome_topic_id'
+ SQL
+
+ execute <<~SQL
+ DELETE FROM site_settings
+ WHERE name IN ('welcome_topic_id', 'lounge_welcome_topic_id', 'admin_quick_start_topic_id')
+ SQL
+ end
+end
diff --git a/lib/introduction_updater.rb b/lib/introduction_updater.rb
index 4f46ef1a920..3def6a42242 100644
--- a/lib/introduction_updater.rb
+++ b/lib/introduction_updater.rb
@@ -29,11 +29,9 @@ class IntroductionUpdater
end
def find_welcome_post
- topic_id = TopicCustomField
- .where(name: "is_welcome_topic", value: "true")
- .pluck(:topic_id)
+ topic_id = SiteSetting.welcome_topic_id
- if topic_id.blank?
+ if topic_id <= 0
title = I18n.t("discourse_welcome_topic.title")
topic_id = find_topic_id(title)
end
diff --git a/lib/seed_data/categories.rb b/lib/seed_data/categories.rb
new file mode 100644
index 00000000000..8460328e0e8
--- /dev/null
+++ b/lib/seed_data/categories.rb
@@ -0,0 +1,174 @@
+module SeedData
+ class Categories
+ def self.with_default_locale
+ SeedData::Categories.new(SiteSetting.default_locale)
+ end
+
+ def initialize(locale)
+ @locale = locale
+ end
+
+ def create(site_setting_names: nil)
+ I18n.with_locale(@locale) do
+ categories(site_setting_names).each { |params| create_category(params) }
+ end
+ end
+
+ def update(site_setting_names: nil, skip_changed: false)
+ I18n.with_locale(@locale) do
+ categories(site_setting_names).each do |params|
+ params.slice!(:site_setting_name, :name, :description)
+ params[:skip_changed] = skip_changed
+ update_category(params)
+ end
+ end
+ end
+
+ def reseed_options
+ I18n.with_locale(@locale) do
+ categories.map do |params|
+ category = find_category(params[:site_setting_name])
+ next unless category
+
+ {
+ id: params[:site_setting_name],
+ name: category.name,
+ selected: unchanged?(category)
+ }
+ end.compact
+ end
+ end
+
+ private
+
+ def categories(site_setting_names = nil)
+ categories = [
+ {
+ site_setting_name: 'uncategorized_category_id',
+ name: I18n.t('uncategorized_category_name'),
+ description: nil,
+ position: 0,
+ color: '0088CC',
+ text_color: 'FFFFFF',
+ permissions: { everyone: :full },
+ force_permissions: true,
+ force_existence: true
+ },
+ {
+ site_setting_name: 'meta_category_id',
+ name: I18n.t('meta_category_name'),
+ description: I18n.t('meta_category_description'),
+ position: 1,
+ color: '808281',
+ text_color: 'FFFFFF',
+ permissions: { everyone: :full },
+ force_permissions: true
+ },
+ {
+ site_setting_name: 'staff_category_id',
+ name: I18n.t('staff_category_name'),
+ description: I18n.t('staff_category_description'),
+ position: 2,
+ color: 'E45735',
+ text_color: 'FFFFFF',
+ permissions: { staff: :full },
+ force_permissions: true
+ },
+ {
+ site_setting_name: 'lounge_category_id',
+ name: I18n.t('vip_category_name'),
+ description: I18n.t('vip_category_description'),
+ position: 3,
+ color: 'A461EF',
+ text_color: '652D90',
+ permissions: { trust_level_3: :full },
+ force_permissions: false
+ }
+ ]
+
+ if site_setting_names
+ categories.select! { |c| site_setting_names.include?(c[:site_setting_name]) }
+ end
+
+ categories
+ end
+
+ def create_category(site_setting_name:, name:, description:, position:, color:, text_color:,
+ permissions:, force_permissions:, force_existence: false)
+ category_id = SiteSetting.send(site_setting_name)
+
+ if should_create_category?(category_id, force_existence)
+ category = Category.new(
+ name: unused_category_name(category_id, name),
+ description: description,
+ user_id: Discourse::SYSTEM_USER_ID,
+ position: position,
+ color: color,
+ text_color: text_color
+ )
+
+ category.skip_category_definition = true if description.blank?
+ category.set_permissions(permissions)
+ category.save!
+
+ SiteSetting.send("#{site_setting_name}=", category.id)
+ elsif category = Category.find_by(id: category_id)
+ if description.present? && (category.topic_id.blank? || !Topic.exists?(category.topic_id))
+ category.description = description
+ category.create_category_definition
+ end
+
+ if force_permissions
+ category.set_permissions(permissions)
+ category.save! if category.changed?
+ end
+ end
+ end
+
+ def should_create_category?(category_id, force_existence)
+ if category_id > 0
+ force_existence ? !Category.exists?(category_id) : false
+ else
+ true
+ end
+ end
+
+ def unused_category_name(category_id, name)
+ category_exists = Category.where(
+ 'id <> :id AND LOWER(name) = :name',
+ id: category_id,
+ name: name.downcase
+ ).exists?
+
+ category_exists ? "#{name}#{SecureRandom.hex}" : name
+ end
+
+ def update_category(site_setting_name:, name:, description:, skip_changed:)
+ category = find_category(site_setting_name)
+ return if !category || (skip_changed && !unchanged?(category))
+
+ name = unused_category_name(category.id, name)
+ category.name = name
+ category.slug = Slug.for(name, '')
+ category.save!
+
+ if description.present? && description_post = category&.topic&.first_post
+ changes = { title: I18n.t("category.topic_prefix", category: name), raw: description }
+ description_post.revise(Discourse.system_user, changes, skip_validations: true)
+ end
+ end
+
+ def find_category(site_setting_name)
+ category_id = SiteSetting.send(site_setting_name)
+ Category.find_by(id: category_id) if category_id > 0
+ end
+
+ def unchanged?(category)
+ if description_post = category&.topic&.first_post
+ return description_post.last_editor_id == Discourse::SYSTEM_USER_ID
+ end
+
+ true
+ end
+ end
+end
diff --git a/lib/seed_data/topics.rb b/lib/seed_data/topics.rb
new file mode 100644
index 00000000000..bbf48dac1e8
--- /dev/null
+++ b/lib/seed_data/topics.rb
@@ -0,0 +1,197 @@
+module SeedData
+ class Topics
+ def self.with_default_locale
+ SeedData::Topics.new(SiteSetting.default_locale)
+ end
+
+ def initialize(locale)
+ @locale = locale
+ end
+
+ def create(site_setting_names: nil, include_welcome_topics: true)
+ I18n.with_locale(@locale) do
+ topics(site_setting_names, include_welcome_topics).each do |params|
+ create_topic(params)
+ end
+ end
+ end
+
+ def update(site_setting_names: nil, skip_changed: false)
+ I18n.with_locale(@locale) do
+ topics(site_setting_names).each do |params|
+ params.except!(:category, :after_create)
+ params[:skip_changed] = skip_changed
+ update_topic(params)
+ end
+ end
+ end
+
+ def reseed_options
+ I18n.with_locale(@locale) do
+ topics.map do |params|
+ post = find_post(params[:site_setting_name])
+ next unless post
+
+ {
+ id: params[:site_setting_name],
+ name: post.topic.title,
+ selected: unchanged?(post)
+ }
+ end.compact
+ end
+ end
+
+ private
+
+ def topics(site_setting_names = nil, include_welcome_topics = true)
+ staff_category = Category.find_by(id: SiteSetting.staff_category_id)
+
+ topics = [
+ # Terms of Service
+ {
+ site_setting_name: 'tos_topic_id',
+ title: I18n.t('tos_topic.title'),
+ raw: I18n.t('tos_topic.body',
+ company_name: setting_value('company_name'),
+ base_url: Discourse.base_url,
+ contact_email: setting_value('contact_email'),
+ governing_law: setting_value('governing_law'),
+ city_for_disputes: setting_value('city_for_disputes')
+ ),
+ category: staff_category,
+ static_first_reply: true
+ },
+
+ # FAQ/Guidelines
+ {
+ site_setting_name: 'guidelines_topic_id',
+ title: I18n.t('guidelines_topic.title'),
+ raw: I18n.t('guidelines_topic.body', base_path: Discourse.base_path),
+ category: staff_category,
+ static_first_reply: true
+ },
+
+ # Privacy Policy
+ {
+ site_setting_name: 'privacy_topic_id',
+ title: I18n.t('privacy_topic.title'),
+ raw: I18n.t('privacy_topic.body'),
+ category: staff_category,
+ static_first_reply: true
+ }
+ ]
+
+ if include_welcome_topics
+ # Welcome Topic
+ topics << {
+ site_setting_name: 'welcome_topic_id',
+ title: I18n.t('discourse_welcome_topic.title'),
+ raw: I18n.t('discourse_welcome_topic.body', base_path: Discourse.base_path),
+ after_create: proc do |post|
+ post.topic.update_pinned(true, true)
+ end
+ }
+
+ # Lounge Welcome Topic
+ if lounge_category = Category.find_by(id: SiteSetting.lounge_category_id)
+ topics << {
+ site_setting_name: 'lounge_welcome_topic_id',
+ title: I18n.t('lounge_welcome.title'),
+ raw: I18n.t('lounge_welcome.body', base_path: Discourse.base_path),
+ category: lounge_category,
+ after_create: proc do |post|
+ post.topic.update_pinned(true)
+ end
+ }
+ end
+
+ # Admin Quick Start Guide
+ topics << {
+ site_setting_name: 'admin_quick_start_topic_id',
+ title: DiscoursePluginRegistry.seed_data['admin_quick_start_title'] || I18n.t('admin_quick_start_title'),
+ raw: admin_quick_start_raw,
+ category: staff_category
+ }
+ end
+
+ if site_setting_names
+ topics.select! { |t| site_setting_names.include?(t[:site_setting_name]) }
+ end
+
+ topics
+ end
+
+ def create_topic(site_setting_name:, title:, raw:, category: nil, static_first_reply: false, after_create: nil)
+ topic_id = SiteSetting.send(site_setting_name)
+ return if topic_id > 0 || Topic.find_by(id: topic_id)
+
+ post = PostCreator.create!(
+ Discourse.system_user,
+ title: title,
+ raw: raw,
+ skip_validations: true,
+ category: category&.name
+ )
+
+ if static_first_reply
+ PostCreator.create!(
+ Discourse.system_user,
+ raw: first_reply_raw(title),
+ skip_validations: true,
+ topic_id: post.topic_id
+ )
+ end
+
+ after_create&.call(post)
+
+ SiteSetting.send("#{site_setting_name}=", post.topic_id)
+ end
+
+ def update_topic(site_setting_name:, title:, raw:, static_first_reply: false, skip_changed:)
+ post = find_post(site_setting_name)
+ return if !post
+
+ if !skip_changed || unchanged?(post)
+ changes = { title: title, raw: raw }
+ post.revise(Discourse.system_user, changes, skip_validations: true)
+ end
+
+ if static_first_reply && (reply = first_reply(post)) && (!skip_changed || unchanged?(reply))
+ changes = { raw: first_reply_raw(title) }
+ reply.revise(Discourse.system_user, changes, skip_validations: true)
+ end
+ end
+
+ def find_post(site_setting_name)
+ topic_id = SiteSetting.send(site_setting_name)
+ Post.find_by(topic_id: topic_id, post_number: 1) if topic_id > 0
+ end
+
+ def unchanged?(post)
+ post.last_editor_id == Discourse::SYSTEM_USER_ID
+ end
+
+ def setting_value(site_setting_key)
+ SiteSetting.send(site_setting_key).presence || "#{site_setting_key}"
+ end
+
+ def first_reply(post)
+ Post.find_by(topic_id: post.topic_id, post_number: 2, user_id: Discourse::SYSTEM_USER_ID)
+ end
+
+ def first_reply_raw(topic_title)
+ I18n.t('static_topic_first_reply', page_name: topic_title)
+ end
+
+ def admin_quick_start_raw
+ quick_start_filename = DiscoursePluginRegistry.seed_data["admin_quick_start_filename"]
+
+ if !quick_start_filename || !File.exist?(quick_start_filename)
+ # TODO Make the quick start guide translatable
+ quick_start_filename = File.join(Rails.root, 'docs', 'ADMIN-QUICK-START-GUIDE.md')
+ end
+
+ File.read(quick_start_filename)
+ end
+ end
+end
diff --git a/lib/tasks/i18n.rake b/lib/tasks/i18n.rake
index ddd7e25891f..e543917187d 100644
--- a/lib/tasks/i18n.rake
+++ b/lib/tasks/i18n.rake
@@ -55,3 +55,16 @@ task "i18n:check", [:locale] => [:environment] do |_, args|
puts ""
exit 1 unless failed_locales.empty?
end
+
+desc "Update seeded topics and categories with latest translations"
+task "i18n:reseed", [:locale] => [:environment] do |_, args|
+ locale = args[:locale]&.to_sym
+
+ if locale.blank? || !I18n.locale_available?(locale)
+ puts "ERROR: Expecting rake i18n:reseed[locale]"
+ exit 1
+ end
+
+ SeedData::Categories.new(locale).update
+ SeedData::Topics.new(locale).update
+end
diff --git a/lib/tasks/topics.rake b/lib/tasks/topics.rake
index ab07f04223d..bbbf56ac797 100644
--- a/lib/tasks/topics.rake
+++ b/lib/tasks/topics.rake
@@ -64,39 +64,3 @@ task "topics:apply_autoclose" => :environment do
puts "", "Done"
end
-
-def update_static_page_topic(locale, site_setting_key, title_key, body_key, params = {})
- topic = Topic.find(SiteSetting.send(site_setting_key))
-
- if (topic && post = topic.first_post)
- post.revise(Discourse.system_user,
- title: I18n.t(title_key, locale: locale),
- raw: I18n.t(body_key, params.merge(locale: locale)))
-
- puts "", "Topic for #{site_setting_key} updated"
- else
- puts "", "Topic for #{site_setting_key} not found"
- end
-end
-
-desc "Update static topics (ToS, Privacy, Guidelines) with latest translated content"
-task "topics:update_static", [:locale] => [:environment] do |_, args|
- locale = args[:locale]&.to_sym
-
- if locale.blank? || !I18n.locale_available?(locale)
- puts "ERROR: Expecting rake topics:update_static[locale]"
- exit 1
- end
-
- update_static_page_topic(locale, "tos_topic_id", "tos_topic.title", "tos_topic.body",
- company_name: SiteSetting.company_name.presence || "company_name",
- base_url: Discourse.base_url,
- contact_email: SiteSetting.contact_email.presence || "contact_email",
- governing_law: SiteSetting.governing_law.presence || "governing_law",
- city_for_disputes: SiteSetting.city_for_disputes.presence || "city_for_disputes")
-
- update_static_page_topic(locale, "guidelines_topic_id", "guidelines_topic.title", "guidelines_topic.body",
- base_path: Discourse.base_path)
-
- update_static_page_topic(locale, "privacy_topic_id", "privacy_topic.title", "privacy_topic.body")
-end
diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb
index 02214c7f95f..d5df1494943 100644
--- a/lib/wizard/builder.rb
+++ b/lib/wizard/builder.rb
@@ -1,5 +1,7 @@
require_dependency 'introduction_updater'
require_dependency 'emoji_set_site_setting'
+require_dependency 'seed_data/categories'
+require_dependency 'seed_data/topics'
class Wizard
class Builder
@@ -26,7 +28,15 @@ class Wizard
step.on_update do |updater|
old_locale = SiteSetting.default_locale
updater.apply_setting(:default_locale)
- updater.refresh_required = true if old_locale != updater.fields[:default_locale]
+
+ if old_locale != updater.fields[:default_locale]
+ Scheduler::Defer.later "Reseed" do
+ SeedData::Categories.with_default_locale.update(skip_changed: true)
+ SeedData::Topics.with_default_locale.update(skip_changed: true)
+ end
+
+ updater.refresh_required = true
+ end
end
end
@@ -95,7 +105,7 @@ class Wizard
step.on_update do |updater|
update_tos do |raw|
- replace_company(updater, raw, 'contact_email')
+ replace_setting_value(updater, raw, 'contact_email')
end
updater.apply_settings(:contact_email, :contact_url)
@@ -111,9 +121,9 @@ class Wizard
step.on_update do |updater|
update_tos do |raw|
- replace_company(updater, raw, 'company_name')
- replace_company(updater, raw, 'governing_law')
- replace_company(updater, raw, 'city_for_disputes')
+ replace_setting_value(updater, raw, 'company_name')
+ replace_setting_value(updater, raw, 'governing_law')
+ replace_setting_value(updater, raw, 'city_for_disputes')
end
updater.apply_settings(:company_name, :governing_law, :city_for_disputes)
@@ -273,14 +283,14 @@ class Wizard
protected
- def replace_company(updater, raw, field_name)
+ def replace_setting_value(updater, raw, field_name)
old_value = SiteSetting.send(field_name)
old_value = field_name if old_value.blank?
new_value = updater.fields[field_name.to_sym]
new_value = field_name if new_value.blank?
- raw.gsub!(old_value, new_value)
+ raw.gsub!("#{old_value}", new_value) || raw.gsub!(old_value, new_value)
end
def reserved_usernames
diff --git a/spec/lib/introduction_updater_spec.rb b/spec/lib/introduction_updater_spec.rb
index 025e116002d..3494e12deb5 100644
--- a/spec/lib/introduction_updater_spec.rb
+++ b/spec/lib/introduction_updater_spec.rb
@@ -12,8 +12,8 @@ describe IntroductionUpdater do
topic
end
- it "finds the welcome topic by custom field" do
- TopicCustomField.create(topic_id: welcome_topic.id, name: "is_welcome_topic", value: "true")
+ it "finds the welcome topic by site setting" do
+ SiteSetting.welcome_topic_id = welcome_topic.id
expect(subject.get_summary).to eq(welcome_post_raw)
end
diff --git a/spec/lib/seed_data/categories_spec.rb b/spec/lib/seed_data/categories_spec.rb
new file mode 100644
index 00000000000..76482e6419a
--- /dev/null
+++ b/spec/lib/seed_data/categories_spec.rb
@@ -0,0 +1,153 @@
+require 'rails_helper'
+require 'seed_data/categories'
+
+describe SeedData::Categories do
+ subject { SeedData::Categories.with_default_locale }
+
+ def create_category(name = "staff_category_id")
+ subject.create(site_setting_names: [name])
+ end
+
+ def description_post(category)
+ Post.find_by(topic_id: category.topic_id)
+ end
+
+ describe "#create" do
+ def permissions(group, type)
+ {
+ group_id: Group::AUTO_GROUPS[group],
+ permission_type: CategoryGroup.permission_types[type]
+ }
+ end
+
+ it "creates a missing category" do
+ expect { create_category }
+ .to change { Category.count }.by(1)
+ .and change { Topic.count }.by(1)
+
+ category = Category.last
+ expect(category.name).to eq(I18n.t("staff_category_name"))
+ expect(category.topic_id).to be_present
+ expect(category.user_id).to eq(Discourse::SYSTEM_USER_ID)
+ expect(category.category_groups.count).to eq(1)
+ expect(category.category_groups.first).to have_attributes(permissions(:staff, :full))
+ expect(Topic.exists?(category.topic_id))
+ expect(description_post(category).raw).to eq(I18n.t("staff_category_description"))
+ expect(SiteSetting.staff_category_id).to eq(category.id)
+ end
+
+ context "with existing category" do
+ before { create_category }
+
+ it "does not create another category" do
+ expect { create_category }
+ .to change { Category.count }.by(0)
+ .and change { Topic.count }.by(0)
+ end
+
+ it "creates a missing 'About Category' topic" do
+ category = Category.last
+ Topic.delete(category.topic_id)
+
+ expect { create_category }
+ .to change { Category.count }.by(0)
+ .and change { Topic.count }.by(1)
+
+ category.reload
+ expect(description_post(category).raw).to eq(I18n.t("staff_category_description"))
+ end
+
+ it "overwrites permissions when permissions are forced" do
+ category = Category.last
+ category.set_permissions(everyone: :full)
+ category.save!
+
+ expect(category.category_groups.count).to eq(0)
+
+ expect { create_category }
+ .to change { CategoryGroup.count }.by(1)
+
+ category.reload
+ expect(category.category_groups.count).to eq(1)
+ expect(category.category_groups.first).to have_attributes(permissions(:staff, :full))
+ end
+ end
+
+ it "does not override permissions of existing category when not forced" do
+ create_category("lounge_category_id")
+
+ category = Category.last
+ category.set_permissions(trust_level_2: :full)
+ category.save!
+
+ expect(category.category_groups.first).to have_attributes(permissions(:trust_level_2, :full))
+
+ expect { create_category("lounge_category_id") }
+ .to change { CategoryGroup.count }.by(0)
+
+ category.reload
+ expect(category.category_groups.first).to have_attributes(permissions(:trust_level_2, :full))
+ end
+ end
+
+ describe "#update" do
+ def update_category(name = "staff_category_id", skip_changed: false)
+ subject.update(site_setting_names: [name], skip_changed: skip_changed)
+ end
+
+ before do
+ create_category
+ Category.last.update!(name: "Foo", slug: "foo")
+ end
+
+ it "updates an existing category" do
+ category = Category.last
+ description_post(category).revise(Discourse.system_user, raw: "Description for Foo category.")
+
+ update_category
+
+ category.reload
+ expect(category.name).to eq(I18n.t("staff_category_name"))
+ expect(category.slug).to eq(Slug.for(I18n.t("staff_category_name")))
+ expect(description_post(category).raw).to eq(I18n.t("staff_category_description"))
+ end
+
+ it "skips category when `skip_changed` is true and description was changed" do
+ category = Category.last
+ description_post(category).revise(Fabricate(:admin), raw: "Description for Foo category.")
+
+ update_category(skip_changed: true)
+
+ category.reload
+ expect(category.name).to eq("Foo")
+ expect(category.slug).to eq("foo")
+ expect(description_post(category).raw).to eq("Description for Foo category.")
+ end
+
+ it "works when the category name is already used by another category" do
+ Fabricate(:category, name: I18n.t("staff_category_name"))
+
+ update_category
+
+ category = Category.find(SiteSetting.staff_category_id)
+ expect(category.name).to_not eq(I18n.t("staff_category_name"))
+ expect(category.name).to start_with(I18n.t("staff_category_name"))
+ end
+ end
+
+ describe "#reseed_options" do
+ it "returns only existing categories as options" do
+ create_category("meta_category_id")
+ create_category("lounge_category_id")
+ Post.last.revise(Fabricate(:admin), raw: "Hello world")
+
+ expected_options = [
+ { id: "uncategorized_category_id", name: I18n.t("uncategorized_category_name"), selected: true },
+ { id: "meta_category_id", name: I18n.t("meta_category_name"), selected: true },
+ { id: "lounge_category_id", name: I18n.t("vip_category_name"), selected: false }
+ ]
+
+ expect(subject.reseed_options).to eq(expected_options)
+ end
+ end
+end
diff --git a/spec/lib/seed_data/topics_spec.rb b/spec/lib/seed_data/topics_spec.rb
new file mode 100644
index 00000000000..30ed3d7bcfa
--- /dev/null
+++ b/spec/lib/seed_data/topics_spec.rb
@@ -0,0 +1,122 @@
+require 'rails_helper'
+require 'seed_data/topics'
+
+describe SeedData::Topics do
+ subject { SeedData::Topics.with_default_locale }
+
+ def create_topic(name = "welcome_topic_id")
+ subject.create(site_setting_names: [name])
+ end
+
+ describe "#create" do
+ it "creates a missing topic" do
+ expect { create_topic }
+ .to change { Topic.count }.by(1)
+ .and change { Post.count }.by(1)
+
+ topic = Topic.last
+ expect(topic.title).to eq(I18n.t("discourse_welcome_topic.title"))
+ expect(topic.first_post.raw).to eq(I18n.t('discourse_welcome_topic.body', base_path: Discourse.base_path).rstrip)
+ expect(topic.category_id).to eq(SiteSetting.uncategorized_category_id)
+ expect(topic.user_id).to eq(Discourse::SYSTEM_USER_ID)
+ expect(topic.pinned_globally).to eq(true)
+ expect(topic.pinned_at).to be_present
+ expect(topic.pinned_until).to be_nil
+ expect(SiteSetting.welcome_topic_id).to eq(topic.id)
+ end
+
+ it "creates a missing topic and a reply when `static_first_reply` is true" do
+ staff_category = Fabricate(:category, name: "Staff")
+ SiteSetting.staff_category_id = staff_category.id
+
+ expect { create_topic("privacy_topic_id") }
+ .to change { Topic.count }.by(1)
+ .and change { Post.count }.by(2)
+
+ topic = Topic.last
+ expect(topic.category_id).to eq(SiteSetting.staff_category_id)
+ expect(topic.posts_count).to eq(2)
+ expect(topic.pinned_globally).to eq(false)
+ expect(topic.pinned_at).to be_nil
+ expect(topic.pinned_until).to be_nil
+
+ post = Post.last
+ expect(post.topic_id).to eq(topic.id)
+ expect(post.user_id).to eq(Discourse::SYSTEM_USER_ID)
+ expect(post.raw).to eq(I18n.t("static_topic_first_reply", page_name: topic.title).rstrip)
+ end
+
+ it "does not create a topic when it already exists" do
+ topic = Fabricate(:topic)
+ SiteSetting.welcome_topic_id = topic.id
+
+ expect { create_topic }.to_not change { Topic.count }
+ end
+
+ it "does not create a topic when the site setting points to non-existent topic" do
+ SiteSetting.welcome_topic_id = (Topic.maximum(:id) || 0) + 1
+
+ expect { create_topic }.to_not change { Topic.count }
+ end
+ end
+
+ describe "#update" do
+ def update_topic(name = "welcome_topic_id", skip_changed: false)
+ subject.update(site_setting_names: [name], skip_changed: skip_changed)
+ end
+
+ it "updates the changed topic" do
+ create_topic
+
+ topic = Topic.last
+ topic.update!(title: "New topic title")
+ topic.first_post.revise(Discourse.system_user, raw: "New text of first post.")
+
+ update_topic
+ topic.reload
+
+ expect(topic.title).to eq(I18n.t("discourse_welcome_topic.title"))
+ expect(topic.first_post.raw).to eq(I18n.t('discourse_welcome_topic.body', base_path: Discourse.base_path).rstrip)
+ end
+
+ it "updates an existing first reply when `static_first_reply` is true" do
+ create_topic("privacy_topic_id")
+
+ post = Post.last
+ post.revise(Discourse.system_user, raw: "New text of first reply.")
+
+ update_topic("privacy_topic_id")
+ post.reload
+
+ expect(post.raw).to eq(I18n.t("static_topic_first_reply", page_name: I18n.t('privacy_topic.title')).rstrip)
+ end
+
+ it "does not update a change topic and `skip_changed` is true" do
+ create_topic
+
+ topic = Topic.last
+ topic.update!(title: "New topic title")
+ topic.first_post.revise(Fabricate(:admin), raw: "New text of first post.")
+
+ update_topic(skip_changed: true)
+
+ expect(topic.title).to eq("New topic title")
+ expect(topic.first_post.raw).to eq("New text of first post.")
+ end
+ end
+
+ describe "#reseed_options" do
+ it "returns only existing topics as options" do
+ create_topic("guidelines_topic_id")
+ create_topic("welcome_topic_id")
+ Post.last.revise(Fabricate(:admin), title: "Changed Topic Title", raw: "Hello world")
+
+ expected_options = [
+ { id: "guidelines_topic_id", name: I18n.t("guidelines_topic.title"), selected: true },
+ { id: "welcome_topic_id", name: "Changed Topic Title", selected: false }
+ ]
+
+ expect(subject.reseed_options).to eq(expected_options)
+ end
+ end
+end
diff --git a/spec/requests/admin/site_texts_controller_spec.rb b/spec/requests/admin/site_texts_controller_spec.rb
index 878ee5ce1e4..a66e64ac3b6 100644
--- a/spec/requests/admin/site_texts_controller_spec.rb
+++ b/spec/requests/admin/site_texts_controller_spec.rb
@@ -28,7 +28,11 @@ RSpec.describe Admin::SiteTextsController do
put "/admin/customize/site_texts/some_key.json", params: {
site_text: { value: 'foo' }
}
+ expect(response.status).to eq(404)
+ put "/admin/customize/reseed.json", params: {
+ category_ids: [], topic_ids: []
+ }
expect(response.status).to eq(404)
end
end
@@ -243,5 +247,57 @@ RSpec.describe Admin::SiteTextsController do
expect(json['site_text']['value']).to_not eq(ru_mf_text)
end
end
+
+ context "reseeding" do
+ before do
+ staff_category = Fabricate(
+ :category,
+ name: "Staff EN",
+ user: Discourse.system_user
+ )
+ SiteSetting.staff_category_id = staff_category.id
+
+ guidelines_topic = Fabricate(
+ :topic,
+ title: "The English Guidelines",
+ category: @staff_category,
+ user: Discourse.system_user
+ )
+ Fabricate(:post, topic: guidelines_topic, user: Discourse.system_user)
+ SiteSetting.guidelines_topic_id = guidelines_topic.id
+ end
+
+ describe '#get_reseed_options' do
+ it 'returns correct json' do
+ get "/admin/customize/reseed.json"
+ expect(response.status).to eq(200)
+
+ expected_reseed_options = {
+ categories: [
+ { id: "uncategorized_category_id", name: I18n.t("uncategorized_category_name"), selected: true },
+ { id: "staff_category_id", name: "Staff EN", selected: true }
+ ],
+ topics: [{ id: "guidelines_topic_id", name: "The English Guidelines", selected: true }]
+ }
+
+ expect(JSON.parse(response.body, symbolize_names: true)).to eq(expected_reseed_options)
+ end
+ end
+
+ describe '#reseed' do
+ it 'reseeds categories and topics' do
+ SiteSetting.default_locale = :de
+
+ post "/admin/customize/reseed.json", params: {
+ category_ids: ["staff_category_id"],
+ topic_ids: ["guidelines_topic_id"]
+ }
+ expect(response.status).to eq(200)
+
+ expect(Category.find(SiteSetting.staff_category_id).name).to eq(I18n.t("staff_category_name"))
+ expect(Topic.find(SiteSetting.guidelines_topic_id).title).to eq(I18n.t("guidelines_topic.title"))
+ end
+ end
+ end
end
end
diff --git a/test/javascripts/acceptance/admin-site-text-test.js.es6 b/test/javascripts/acceptance/admin-site-text-test.js.es6
index d37f486f1af..ea8c8c5d902 100644
--- a/test/javascripts/acceptance/admin-site-text-test.js.es6
+++ b/test/javascripts/acceptance/admin-site-text-test.js.es6
@@ -13,7 +13,7 @@ QUnit.test("search for a key", async assert => {
assert.ok(exists(".site-text.overridden"));
// Only show overridden
- await click(".extra-options input");
+ await click(".search-area .filter-options input");
assert.equal(
currentURL(),
"/admin/customize/site_texts?overridden=true&q=Test"