FEATURE: Default Composer Category Site Setting (#18967)

* FEATURE: Default Composer Category Site Setting

- Create the default_composer_category site setting
- Replace general_category_id logic for auto selecting the composer
  category
- Prevent Uncategorized from being selected if not allowed
- Add default_composer_category option to seeded categories
- Create a migration to populate the default_composer_category site
  setting if there is a general_category_id populated
- Added some tests

* Add missing translation for the new site setting

* fix some js tests

* Just check that the header value is null
This commit is contained in:
Blake Erickson 2022-11-14 11:09:57 -07:00 committed by GitHub
parent 8c48285145
commit 7be53b1588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 181 additions and 12 deletions

View File

@ -143,10 +143,15 @@ const Composer = RestModel.extend({
const oldCategoryId = this._categoryId;
if (isEmpty(categoryId)) {
// Set General as the default category
const generalCategoryId = this.siteSettings.general_category_id;
// Check if there is a default composer category to set
const defaultComposerCategoryId = parseInt(
this.siteSettings.default_composer_category,
10
);
categoryId =
generalCategoryId && generalCategoryId > 0 ? generalCategoryId : null;
defaultComposerCategoryId && defaultComposerCategoryId > 0
? defaultComposerCategoryId
: null;
}
this._categoryId = categoryId;

View File

@ -41,6 +41,7 @@ acceptance("Composer", function (needs) {
needs.settings({
enable_whispers: true,
general_category_id: 1,
default_composer_category: 1,
});
needs.site({
can_tag_topics: true,
@ -90,7 +91,7 @@ acceptance("Composer", function (needs) {
test("Composer is opened", async function (assert) {
await visit("/");
await click("#create-topic");
// Check that General category is selected
// Check that the default category is selected
assert.strictEqual(selectKit(".category-chooser").header().value(), "1");
assert.strictEqual(
@ -1201,3 +1202,109 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
);
});
});
// Default Composer Category tests
acceptance("Composer - Default category", function (needs) {
needs.user();
needs.settings({
general_category_id: 1,
default_composer_category: 2,
});
needs.site({
categories: [
{
id: 1,
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
},
{
id: 2,
name: "test too",
slug: "test-too",
permission: 1,
topic_template: null,
},
],
});
test("Default category is selected over general category", async function (assert) {
await visit("/");
await click("#create-topic");
assert.strictEqual(selectKit(".category-chooser").header().value(), "2");
assert.strictEqual(
selectKit(".category-chooser").header().name(),
"test too"
);
});
});
acceptance("Composer - Uncategorized category", function (needs) {
needs.user();
needs.settings({
general_category_id: -1, // For sites that never had this seeded
default_composer_category: -1, // For sites that never had this seeded
allow_uncategorized_topics: true,
});
needs.site({
categories: [
{
id: 1,
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
},
{
id: 2,
name: "test too",
slug: "test-too",
permission: 1,
topic_template: null,
},
],
});
test("Uncategorized category is selected", async function (assert) {
await visit("/");
await click("#create-topic");
assert.strictEqual(selectKit(".category-chooser").header().value(), null);
});
});
acceptance("Composer - default category not set", function (needs) {
needs.user();
needs.settings({
default_composer_category: "",
});
needs.site({
categories: [
{
id: 1,
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
},
{
id: 2,
name: "test too",
slug: "test-too",
permission: 1,
topic_template: null,
},
],
});
test("Nothing is selected", async function (assert) {
await visit("/");
await click("#create-topic");
assert.strictEqual(selectKit(".category-chooser").header().value(), null);
assert.strictEqual(
selectKit(".category-chooser").header().name(),
"category…"
);
});
});
// END: Default Composer Category tests

View File

@ -126,9 +126,9 @@ module(
assert.strictEqual(this.subject.header().label(), "category…");
});
test("with allowUncategorized=null and generalCategoryId present", async function (assert) {
test("with allowUncategorized=null and defaultComposerCategory present", async function (assert) {
this.siteSettings.allow_uncategorized_topics = false;
this.siteSettings.general_category_id = 4;
this.siteSettings.default_composer_category = 4;
await render(hbs`
<CategoryChooser
@ -143,9 +143,9 @@ module(
assert.strictEqual(this.subject.header().label(), "");
});
test("with allowUncategorized=null and generalCategoryId present, but not set", async function (assert) {
test("with allowUncategorized=null and defaultComposerCategory present, but not set", async function (assert) {
this.siteSettings.allow_uncategorized_topics = false;
this.siteSettings.general_category_id = -1;
this.siteSettings.default_composer_category = -1;
await render(hbs`
<CategoryChooser

View File

@ -44,8 +44,11 @@ export default ComboBoxComponent.extend({
) {
return Category.findUncategorized();
} else {
const generalCategoryId = this.siteSettings.general_category_id;
if (!generalCategoryId || generalCategoryId < 0) {
const defaultCategoryId = parseInt(
this.siteSettings.default_composer_category,
10
);
if (!defaultCategoryId || defaultCategoryId < 0) {
return this.defaultItem(null, htmlSafe(I18n.t("category.choose")));
}
}

View File

@ -1495,6 +1495,7 @@ en:
search_ignore_accents: "Ignore accents when searching for text."
category_search_priority_low_weight: "Weight applied to ranking for low category search priority."
category_search_priority_high_weight: "Weight applied to ranking for high category search priority."
default_composer_category: "The category used to pre-populate the category dropdown when creating a new topic."
allow_uncategorized_topics: "Allow topics to be created without a category. WARNING: If there are any uncategorized topics, you must recategorize them before turning this off."
allow_duplicate_topic_titles: "Allow topics with identical, duplicate titles."
allow_duplicate_topic_titles_category: "Allow topics with identical, duplicate titles if the category is different. allow_duplicate_topic_titles must be disabled."
@ -2437,6 +2438,7 @@ en:
search_tokenize_japanese_enabled: "You must disable 'search_tokenize_japanese' before enabling this setting."
discourse_connect_cannot_be_enabled_if_second_factor_enforced: "You cannot enable DiscourseConnect if 2FA is enforced."
delete_rejected_email_after_days: "This setting cannot be set lower than the delete_email_logs_after_days setting or greater than %{max}"
invalid_uncategorized_category_setting: "The Uncategorized category cannot be selected if allow uncategorized topics is not allowed"
placeholder:
discourse_connect_provider_secrets:

View File

@ -2339,6 +2339,11 @@ uncategorized:
uncategorized_category_id:
default: -1
hidden: true
default_composer_category:
client: true
type: category
default: ""
validator: "DefaultComposerCategoryValidator"
notify_about_flags_after:
type: float

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
## If the general_category_id has a value, set the *new*
# default_composer_category site setting to match
class PopulateDefaultComposerCategory < ActiveRecord::Migration[7.0]
def up
general_category_id = DB.query_single("SELECT value FROM site_settings WHERE name = 'general_category_id'")
return if general_category_id.blank? || general_category_id[0].to_i < 0
default_composer_category = DB.query_single("SELECT value FROM site_settings where name = 'default_composer_category'")
return if !default_composer_category.blank?
DB.exec(
"INSERT INTO site_settings(name, value, data_type, created_at, updated_at)
VALUES('default_composer_category', :setting, '16', NOW(), NOW())",
setting: general_category_id[0].to_i
)
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -87,7 +87,8 @@ module SeedData
text_color: 'FFFFFF',
permissions: { everyone: :full },
force_permissions: true,
sidebar: true
sidebar: true,
default_composer_category: true
}
]
@ -99,7 +100,7 @@ module SeedData
end
def create_category(site_setting_name:, name:, description:, position:, color:, text_color:,
permissions:, force_permissions:, force_existence: false, sidebar: false)
permissions:, force_permissions:, force_existence: false, sidebar: false, default_composer_category: false)
category_id = SiteSetting.get(site_setting_name)
if should_create_category?(category_id, force_existence)
@ -123,6 +124,10 @@ module SeedData
sidebar_categories << category.id
SiteSetting.set('default_sidebar_categories', sidebar_categories.join('|'))
end
if default_composer_category
SiteSetting.set('default_composer_category', category.id)
end
elsif category = Category.find_by(id: category_id)
if description.present? && (category.topic_id.blank? || !Topic.exists?(category.topic_id))
category.description = description

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class DefaultComposerCategoryValidator
def initialize(opts = {})
@opts = opts
end
def valid_value?(val)
category_id = val.to_i
unless SiteSetting.allow_uncategorized_topics
return false if category_id == SiteSetting.uncategorized_category_id
end
true
end
def error_message
I18n.t('site_settings.errors.invalid_uncategorized_category_setting')
end
end

View File

@ -107,6 +107,7 @@ RSpec.describe SeedData::Categories do
.and change { Topic.count }
expect(Category.last.name).to eq("General")
expect(SiteSetting.default_composer_category).to eq(Category.last.id)
end
it "adds default categories SiteSetting.default_sidebar_categories" do