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}} +
+ {{i18n "admin.reseed.modal.categories"}} + + {{#each categories as |category|}} + + {{/each}} +
+ {{/if}} + +
+ + {{#if topics}} +
+ {{i18n "admin.reseed.modal.topics"}} + + {{#each topics as |topic|}} + + {{/each}} +
+ {{/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")}} -
+
+ {{d-button action=(route-action "showReseedModal") + class="btn-default" + label="admin.reseed.action.label" + title="admin.reseed.action.title" + icon="sync"}} +
+ +

-

+

{{#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"