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:
parent
8c48285145
commit
7be53b1588
|
@ -143,10 +143,15 @@ const Composer = RestModel.extend({
|
||||||
const oldCategoryId = this._categoryId;
|
const oldCategoryId = this._categoryId;
|
||||||
|
|
||||||
if (isEmpty(categoryId)) {
|
if (isEmpty(categoryId)) {
|
||||||
// Set General as the default category
|
// Check if there is a default composer category to set
|
||||||
const generalCategoryId = this.siteSettings.general_category_id;
|
const defaultComposerCategoryId = parseInt(
|
||||||
|
this.siteSettings.default_composer_category,
|
||||||
|
10
|
||||||
|
);
|
||||||
categoryId =
|
categoryId =
|
||||||
generalCategoryId && generalCategoryId > 0 ? generalCategoryId : null;
|
defaultComposerCategoryId && defaultComposerCategoryId > 0
|
||||||
|
? defaultComposerCategoryId
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
this._categoryId = categoryId;
|
this._categoryId = categoryId;
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ acceptance("Composer", function (needs) {
|
||||||
needs.settings({
|
needs.settings({
|
||||||
enable_whispers: true,
|
enable_whispers: true,
|
||||||
general_category_id: 1,
|
general_category_id: 1,
|
||||||
|
default_composer_category: 1,
|
||||||
});
|
});
|
||||||
needs.site({
|
needs.site({
|
||||||
can_tag_topics: true,
|
can_tag_topics: true,
|
||||||
|
@ -90,7 +91,7 @@ acceptance("Composer", function (needs) {
|
||||||
test("Composer is opened", async function (assert) {
|
test("Composer is opened", async function (assert) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click("#create-topic");
|
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(selectKit(".category-chooser").header().value(), "1");
|
||||||
|
|
||||||
assert.strictEqual(
|
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
|
||||||
|
|
|
@ -126,9 +126,9 @@ module(
|
||||||
assert.strictEqual(this.subject.header().label(), "category…");
|
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.allow_uncategorized_topics = false;
|
||||||
this.siteSettings.general_category_id = 4;
|
this.siteSettings.default_composer_category = 4;
|
||||||
|
|
||||||
await render(hbs`
|
await render(hbs`
|
||||||
<CategoryChooser
|
<CategoryChooser
|
||||||
|
@ -143,9 +143,9 @@ module(
|
||||||
assert.strictEqual(this.subject.header().label(), "");
|
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.allow_uncategorized_topics = false;
|
||||||
this.siteSettings.general_category_id = -1;
|
this.siteSettings.default_composer_category = -1;
|
||||||
|
|
||||||
await render(hbs`
|
await render(hbs`
|
||||||
<CategoryChooser
|
<CategoryChooser
|
||||||
|
|
|
@ -44,8 +44,11 @@ export default ComboBoxComponent.extend({
|
||||||
) {
|
) {
|
||||||
return Category.findUncategorized();
|
return Category.findUncategorized();
|
||||||
} else {
|
} else {
|
||||||
const generalCategoryId = this.siteSettings.general_category_id;
|
const defaultCategoryId = parseInt(
|
||||||
if (!generalCategoryId || generalCategoryId < 0) {
|
this.siteSettings.default_composer_category,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
if (!defaultCategoryId || defaultCategoryId < 0) {
|
||||||
return this.defaultItem(null, htmlSafe(I18n.t("category.choose")));
|
return this.defaultItem(null, htmlSafe(I18n.t("category.choose")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1495,6 +1495,7 @@ en:
|
||||||
search_ignore_accents: "Ignore accents when searching for text."
|
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_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."
|
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_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: "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."
|
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."
|
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."
|
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}"
|
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:
|
placeholder:
|
||||||
discourse_connect_provider_secrets:
|
discourse_connect_provider_secrets:
|
||||||
|
|
|
@ -2339,6 +2339,11 @@ uncategorized:
|
||||||
uncategorized_category_id:
|
uncategorized_category_id:
|
||||||
default: -1
|
default: -1
|
||||||
hidden: true
|
hidden: true
|
||||||
|
default_composer_category:
|
||||||
|
client: true
|
||||||
|
type: category
|
||||||
|
default: ""
|
||||||
|
validator: "DefaultComposerCategoryValidator"
|
||||||
|
|
||||||
notify_about_flags_after:
|
notify_about_flags_after:
|
||||||
type: float
|
type: float
|
||||||
|
|
|
@ -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
|
|
@ -87,7 +87,8 @@ module SeedData
|
||||||
text_color: 'FFFFFF',
|
text_color: 'FFFFFF',
|
||||||
permissions: { everyone: :full },
|
permissions: { everyone: :full },
|
||||||
force_permissions: true,
|
force_permissions: true,
|
||||||
sidebar: true
|
sidebar: true,
|
||||||
|
default_composer_category: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ module SeedData
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_category(site_setting_name:, name:, description:, position:, color:, text_color:,
|
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)
|
category_id = SiteSetting.get(site_setting_name)
|
||||||
|
|
||||||
if should_create_category?(category_id, force_existence)
|
if should_create_category?(category_id, force_existence)
|
||||||
|
@ -123,6 +124,10 @@ module SeedData
|
||||||
sidebar_categories << category.id
|
sidebar_categories << category.id
|
||||||
SiteSetting.set('default_sidebar_categories', sidebar_categories.join('|'))
|
SiteSetting.set('default_sidebar_categories', sidebar_categories.join('|'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if default_composer_category
|
||||||
|
SiteSetting.set('default_composer_category', category.id)
|
||||||
|
end
|
||||||
elsif category = Category.find_by(id: category_id)
|
elsif category = Category.find_by(id: category_id)
|
||||||
if description.present? && (category.topic_id.blank? || !Topic.exists?(category.topic_id))
|
if description.present? && (category.topic_id.blank? || !Topic.exists?(category.topic_id))
|
||||||
category.description = description
|
category.description = description
|
||||||
|
|
|
@ -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
|
|
@ -107,6 +107,7 @@ RSpec.describe SeedData::Categories do
|
||||||
.and change { Topic.count }
|
.and change { Topic.count }
|
||||||
|
|
||||||
expect(Category.last.name).to eq("General")
|
expect(Category.last.name).to eq("General")
|
||||||
|
expect(SiteSetting.default_composer_category).to eq(Category.last.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "adds default categories SiteSetting.default_sidebar_categories" do
|
it "adds default categories SiteSetting.default_sidebar_categories" do
|
||||||
|
|
Loading…
Reference in New Issue